1 /*
2  * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /**
25  * @test
26  * @bug 4980035
27  * @summary Unit test for new methods:
28  *
29  *          AffineTransform.getRotateInstance(double x, double y);
30  *          AffineTransform.setToRotation(double x, double y);
31  *          AffineTransform.rotate(double x, double y);
32  *
33  *          AffineTransform.getQuadrantRotateInstance(int numquads);
34  *          AffineTransform.setToQuadrantRotation(int numquads);
35  *          AffineTransform.quadrantRotate(int numquads);
36  *
37  * @author flar
38  * @run main TestRotateMethods
39  */
40 
41 import java.awt.geom.AffineTransform;
42 import java.awt.geom.Point2D;
43 
44 public class TestRotateMethods {
45     /* The maximum errors allowed, measured in double precision "ulps"
46      * Note that for most fields, the tests are extremely accurate - to
47      * within 3 ulps of the smaller value in the comparison
48      * For the translation components, the tests are still very accurate,
49      * but the absolute number of ulps can be noticeably higher when we
50      * use one of the rotate methods that takes an anchor point.
51      * Since a double precision value has 56 bits of precision, even
52      * 1024 ulps is extremely small as a ratio of the value.
53      */
54     public static final double MAX_ULPS = 3.0;
55     public static final double MAX_ANCHOR_TX_ULPS = 1024.0;
56     public static double MAX_TX_ULPS = MAX_ULPS;
57 
58     // Vectors for quadrant rotations
59     public static final double quadxvec[] = {  1.0,  0.0, -1.0,  0.0 };
60     public static final double quadyvec[] = {  0.0,  1.0,  0.0, -1.0 };
61 
62     // Run tests once for each type of method:
63     //     tx = AffineTransform.get<Rotate>Instance()
64     //     tx.set<Rotate>()
65     //     tx.<rotate>()
66     public static enum Mode { GET, SET, MOD };
67 
68     // Used to accumulate and report largest differences encountered by tests
69     public static double maxulps = 0.0;
70     public static double maxtxulps = 0.0;
71 
72     // Sample anchor points for testing.
73     public static Point2D zeropt = new Point2D.Double(0, 0);
74     public static Point2D testtxpts[] = {
75         new Point2D.Double(       5,      5),
76         new Point2D.Double(      20,    -10),
77         new Point2D.Double(-Math.PI, Math.E),
78     };
79 
main(String argv[])80     public static void main(String argv[]) {
81         test(Mode.GET);
82         test(Mode.SET);
83         test(Mode.MOD);
84 
85         System.out.println("Max scale and shear difference: "+maxulps+" ulps");
86         System.out.println("Max translate difference: "+maxtxulps+" ulps");
87     }
88 
test(Mode mode)89     public static void test(Mode mode) {
90         MAX_TX_ULPS = MAX_ULPS; // Stricter tx testing with no anchor point
91         test(mode, 0.5, null);
92         test(mode, 1.0, null);
93         test(mode, 3.0, null);
94 
95         // Anchor points make the tx values less reliable
96         MAX_TX_ULPS = MAX_ANCHOR_TX_ULPS;
97         for (int i = 0; i < testtxpts.length; i++) {
98             test(mode, 1.0, testtxpts[i]);
99         }
100         MAX_TX_ULPS = MAX_ULPS; // Restore to default
101     }
102 
verify(AffineTransform at1, AffineTransform at2, Mode mode, double vectorscale, Point2D txpt, String message, double num, String units)103     public static void verify(AffineTransform at1, AffineTransform at2,
104                               Mode mode, double vectorscale, Point2D txpt,
105                               String message, double num, String units)
106     {
107         if (!compare(at1, at2)) {
108             System.out.println("mode == "+mode);
109             System.out.println("vectorscale == "+vectorscale);
110             System.out.println("txpt == "+txpt);
111             System.out.println(at1+", type = "+at1.getType());
112             System.out.println(at2+", type = "+at2.getType());
113             System.out.println("ScaleX values differ by "+
114                                ulps(at1.getScaleX(), at2.getScaleX())+" ulps");
115             System.out.println("ScaleY values differ by "+
116                                ulps(at1.getScaleY(), at2.getScaleY())+" ulps");
117             System.out.println("ShearX values differ by "+
118                                ulps(at1.getShearX(), at2.getShearX())+" ulps");
119             System.out.println("ShearY values differ by "+
120                                ulps(at1.getShearY(), at2.getShearY())+" ulps");
121             System.out.println("TranslateX values differ by "+
122                                ulps(at1.getTranslateX(),
123                                     at2.getTranslateX())+" ulps");
124             System.out.println("TranslateY values differ by "+
125                                ulps(at1.getTranslateY(),
126                                     at2.getTranslateY())+" ulps");
127             throw new RuntimeException(message + num + units);
128         }
129     }
130 
test(Mode mode, double vectorscale, Point2D txpt)131     public static void test(Mode mode, double vectorscale, Point2D txpt) {
132         AffineTransform at1, at2, at3;
133 
134         for (int deg = -720; deg <= 720; deg++) {
135             if ((deg % 90) == 0) continue;
136             double radians = Math.toRadians(deg);
137             double vecy = Math.sin(radians) * vectorscale;
138             double vecx = Math.cos(radians) * vectorscale;
139 
140             at1 = makeAT(mode, txpt, radians);
141             at2 = makeAT(mode, txpt, vecx, vecy);
142             verify(at1, at2, mode, vectorscale, txpt,
143                    "vector and radians do not match for ", deg, " degrees");
144 
145             if (txpt == null) {
146                 // Make sure output was same as a with a 0,0 anchor point
147                 if (vectorscale == 1.0) {
148                     // Only need to test radians method for one scale factor
149                     at3 = makeAT(mode, zeropt, radians);
150                     verify(at1, at3, mode, vectorscale, zeropt,
151                            "radians not invariant with 0,0 translate at ",
152                            deg, " degrees");
153                 }
154                 // But test vector methods with all scale factors
155                 at3 = makeAT(mode, zeropt, vecx, vecy);
156                 verify(at2, at3, mode, vectorscale, zeropt,
157                        "vector not invariant with 0,0 translate at ",
158                        deg, " degrees");
159             }
160         }
161 
162         for (int quad = -8; quad <= 8; quad++) {
163             double degrees = quad * 90.0;
164             double radians = Math.toRadians(degrees);
165             double vecx = quadxvec[quad & 3] * vectorscale;
166             double vecy = quadyvec[quad & 3] * vectorscale;
167 
168             at1 = makeAT(mode, txpt, radians);
169             at2 = makeAT(mode, txpt, vecx, vecy);
170             verify(at1, at2, mode, vectorscale, txpt,
171                    "quadrant vector and radians do not match for ",
172                    degrees, " degrees");
173             at2 = makeQuadAT(mode, txpt, quad);
174             verify(at1, at2, mode, vectorscale, txpt,
175                    "quadrant and radians do not match for ",
176                    quad, " quadrants");
177             if (txpt == null) {
178                 at3 = makeQuadAT(mode, zeropt, quad);
179                 verify(at2, at3, mode, vectorscale, zeropt,
180                        "quadrant not invariant with 0,0 translate at ",
181                        quad, " quadrants");
182             }
183         }
184     }
185 
makeRandomAT()186     public static AffineTransform makeRandomAT() {
187         AffineTransform at = new AffineTransform();
188         at.scale(Math.random() * -10.0, Math.random() * 100.0);
189         at.rotate(Math.random() * Math.PI);
190         at.shear(Math.random(), Math.random());
191         at.translate(Math.random() * 300.0, Math.random() * -20.0);
192         return at;
193     }
194 
makeAT(Mode mode, Point2D txpt, double radians)195     public static AffineTransform makeAT(Mode mode, Point2D txpt,
196                                          double radians)
197     {
198         AffineTransform at;
199         double tx = (txpt == null) ? 0.0 : txpt.getX();
200         double ty = (txpt == null) ? 0.0 : txpt.getY();
201         switch (mode) {
202         case GET:
203             if (txpt != null) {
204                 at = AffineTransform.getRotateInstance(radians, tx, ty);
205             } else {
206                 at = AffineTransform.getRotateInstance(radians);
207             }
208             break;
209         case SET:
210             at = makeRandomAT();
211             if (txpt != null) {
212                 at.setToRotation(radians, tx, ty);
213             } else {
214                 at.setToRotation(radians);
215             }
216             break;
217         case MOD:
218             at = makeRandomAT();
219             at.setToIdentity();
220             if (txpt != null) {
221                 at.rotate(radians, tx, ty);
222             } else {
223                 at.rotate(radians);
224             }
225             break;
226         default:
227             throw new InternalError("unrecognized mode: "+mode);
228         }
229 
230         return at;
231     }
232 
makeAT(Mode mode, Point2D txpt, double vx, double vy)233     public static AffineTransform makeAT(Mode mode, Point2D txpt,
234                                          double vx, double vy)
235     {
236         AffineTransform at;
237         double tx = (txpt == null) ? 0.0 : txpt.getX();
238         double ty = (txpt == null) ? 0.0 : txpt.getY();
239         switch (mode) {
240         case GET:
241             if (txpt != null) {
242                 at = AffineTransform.getRotateInstance(vx, vy, tx, ty);
243             } else {
244                 at = AffineTransform.getRotateInstance(vx, vy);
245             }
246             break;
247         case SET:
248             at = makeRandomAT();
249             if (txpt != null) {
250                 at.setToRotation(vx, vy, tx, ty);
251             } else {
252                 at.setToRotation(vx, vy);
253             }
254             break;
255         case MOD:
256             at = makeRandomAT();
257             at.setToIdentity();
258             if (txpt != null) {
259                 at.rotate(vx, vy, tx, ty);
260             } else {
261                 at.rotate(vx, vy);
262             }
263             break;
264         default:
265             throw new InternalError("unrecognized mode: "+mode);
266         }
267 
268         return at;
269     }
270 
makeQuadAT(Mode mode, Point2D txpt, int quads)271     public static AffineTransform makeQuadAT(Mode mode, Point2D txpt,
272                                              int quads)
273     {
274         AffineTransform at;
275         double tx = (txpt == null) ? 0.0 : txpt.getX();
276         double ty = (txpt == null) ? 0.0 : txpt.getY();
277         switch (mode) {
278         case GET:
279             if (txpt != null) {
280                 at = AffineTransform.getQuadrantRotateInstance(quads, tx, ty);
281             } else {
282                 at = AffineTransform.getQuadrantRotateInstance(quads);
283             }
284             break;
285         case SET:
286             at = makeRandomAT();
287             if (txpt != null) {
288                 at.setToQuadrantRotation(quads, tx, ty);
289             } else {
290                 at.setToQuadrantRotation(quads);
291             }
292             break;
293         case MOD:
294             at = makeRandomAT();
295             at.setToIdentity();
296             if (txpt != null) {
297                 at.quadrantRotate(quads, tx, ty);
298             } else {
299                 at.quadrantRotate(quads);
300             }
301             break;
302         default:
303             throw new InternalError("unrecognized mode: "+mode);
304         }
305 
306         return at;
307     }
308 
compare(AffineTransform at1, AffineTransform at2)309     public static boolean compare(AffineTransform at1, AffineTransform at2) {
310         maxulps = Math.max(maxulps, ulps(at1.getScaleX(), at2.getScaleX()));
311         maxulps = Math.max(maxulps, ulps(at1.getScaleY(), at2.getScaleY()));
312         maxulps = Math.max(maxulps, ulps(at1.getShearX(), at2.getShearX()));
313         maxulps = Math.max(maxulps, ulps(at1.getShearY(), at2.getShearY()));
314         maxtxulps = Math.max(maxtxulps,
315                              ulps(at1.getTranslateX(), at2.getTranslateX()));
316         maxtxulps = Math.max(maxtxulps,
317                              ulps(at1.getTranslateY(), at2.getTranslateY()));
318         return (getModifiedType(at1) == getModifiedType(at2) &&
319                 (compare(at1.getScaleX(), at2.getScaleX(), MAX_ULPS)) &&
320                 (compare(at1.getScaleY(), at2.getScaleY(), MAX_ULPS)) &&
321                 (compare(at1.getShearX(), at2.getShearX(), MAX_ULPS)) &&
322                 (compare(at1.getShearY(), at2.getShearY(), MAX_ULPS)) &&
323                 (compare(at1.getTranslateX(),
324                          at2.getTranslateX(), MAX_TX_ULPS)) &&
325                 (compare(at1.getTranslateY(),
326                          at2.getTranslateY(), MAX_TX_ULPS)));
327     }
328 
getModifiedType(AffineTransform at)329     public static int getModifiedType(AffineTransform at) {
330         int type = at.getType();
331         // Some of the vector methods can introduce a tiny uniform scale
332         // at some angles...
333         if ((type & AffineTransform.TYPE_UNIFORM_SCALE) != 0) {
334             maxulps = Math.max(maxulps, ulps(at.getDeterminant(), 1.0));
335             if (ulps(at.getDeterminant(), 1.0) <= MAX_ULPS) {
336                 // Really tiny - we will ignore it
337                 type &= (~AffineTransform.TYPE_UNIFORM_SCALE);
338             }
339         }
340         return type;
341     }
342 
compare(double val1, double val2, double maxulps)343     public static boolean compare(double val1, double val2, double maxulps) {
344         return (ulps(val1, val2) <= maxulps);
345     }
346 
ulps(double val1, double val2)347     public static double ulps(double val1, double val2) {
348         double diff = Math.abs(val1 - val2);
349         double ulpmax = Math.min(Math.ulp(val1), Math.ulp(val2));
350         return (diff / ulpmax);
351     }
352 }
353