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
41import java.awt.geom.AffineTransform;
42import java.awt.geom.Point2D;
43
44public 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
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
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
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
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
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
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
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
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
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
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
343    public static boolean compare(double val1, double val2, double maxulps) {
344        return (ulps(val1, val2) <= maxulps);
345    }
346
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