1/*
2 * Copyright (c) 2005, 2014, 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 4987374 8062163
27 * @summary Unit test for inversion methods:
28 *
29 *          AffineTransform.createInverse();
30 *          AffineTransform.invert();
31 *
32 * @author flar
33 * @run main TestInvertMethods
34 */
35
36import java.awt.geom.AffineTransform;
37import java.awt.geom.NoninvertibleTransformException;
38
39/*
40 * Instances of the inner class Tester are "nodes" which take an input
41 * AffineTransform (AT), modify it in some way and pass the modified
42 * AT onto another Tester node.
43 *
44 * There is one particular Tester node of note called theVerifier.
45 * This is a leaf node which takes the input AT and tests the various
46 * inversion methods on that matrix.
47 *
48 * Most of the other Tester nodes will perform a single affine operation
49 * on their input, such as a rotate by various angles, or a scale by
50 * various predefined scale  values, and then pass the modified AT on
51 * to the next node in the chain which may be a verifier or another
52 * modifier.
53 *
54 * The Tester instances can also be chained together using the chain
55 * method so that we can test not only matrices modified by some single
56 * affine operation (scale, rotate, etc.) but also composite matrices
57 * that represent multiple operations concatenated together.
58 */
59public class TestInvertMethods {
60    public static boolean verbose;
61
62    public static final double MAX_ULPS = 2.0;
63    public static double MAX_TX_ULPS = MAX_ULPS;
64    public static double maxulps = 0.0;
65    public static double maxtxulps = 0.0;
66    public static int numtests = 0;
67
68    public static void main(String argv[]) {
69        Tester rotate = new Tester.Rotate();
70        Tester scale = new Tester.Scale();
71        Tester shear = new Tester.Shear();
72        Tester translate = new Tester.Translate();
73
74        if (argv.length > 1) {
75            // This next line verifies that chaining works correctly...
76            scale.chain(translate.chain(new Tester.Debug())).test(false);
77            return;
78        }
79
80        verbose = (argv.length > 0);
81
82        new Tester.Identity().test(true);
83        translate.test(true);
84        scale.test(true);
85        rotate.test(true);
86        shear.test(true);
87        scale.chain(translate).test(true);
88        rotate.chain(translate).test(true);
89        shear.chain(translate).test(true);
90        translate.chain(scale).test(true);
91        translate.chain(rotate).test(true);
92        translate.chain(shear).test(true);
93        translate.chain(scale.chain(rotate.chain(shear))).test(false);
94        shear.chain(rotate.chain(scale.chain(translate))).test(false);
95
96        System.out.println(numtests+" tests performed");
97        System.out.println("Max scale and shear difference: "+maxulps+" ulps");
98        System.out.println("Max translate difference: "+maxtxulps+" ulps");
99    }
100
101    public abstract static class Tester {
102        public static AffineTransform IdentityTx = new AffineTransform();
103
104        /*
105         * This is the leaf node that performs inversion testing
106         * on the incoming AffineTransform.
107         */
108        public static final Tester theVerifier = new Tester() {
109            public void test(AffineTransform at, boolean full) {
110                numtests++;
111                AffineTransform inv1, inv2;
112                boolean isinvertible =
113                    (Math.abs(at.getDeterminant()) >= Double.MIN_VALUE);
114                try {
115                    inv1 = at.createInverse();
116                    if (!isinvertible) missingNTE("createInverse", at);
117                } catch (NoninvertibleTransformException e) {
118                    inv1 = null;
119                    if (isinvertible) extraNTE("createInverse", at);
120                }
121                inv2 = new AffineTransform(at);
122                try {
123                    inv2.invert();
124                    if (!isinvertible) missingNTE("invert", at);
125                } catch (NoninvertibleTransformException e) {
126                    if (isinvertible) extraNTE("invert", at);
127                }
128                if (verbose) System.out.println("at = "+at);
129                if (isinvertible) {
130                    if (verbose) System.out.println(" inv1 = "+inv1);
131                    if (verbose) System.out.println(" inv2 = "+inv2);
132                    if (!inv1.equals(inv2)) {
133                        report(at, inv1, inv2,
134                               "invert methods do not agree");
135                    }
136                    inv1.concatenate(at);
137                    inv2.concatenate(at);
138                    // "Fix" some values that don't always behave
139                    // well with all the math that we've done up
140                    // to this point.
141                    // See the note on the concatfix method below.
142                    concatfix(inv1);
143                    concatfix(inv2);
144                    if (verbose) System.out.println("  at*inv1 = "+inv1);
145                    if (verbose) System.out.println("  at*inv2 = "+inv2);
146                    if (!compare(inv1, IdentityTx)) {
147                        report(at, inv1, IdentityTx,
148                               "createInverse() check failed");
149                    }
150                    if (!compare(inv2, IdentityTx)) {
151                        report(at, inv2, IdentityTx,
152                               "invert() check failed");
153                    }
154                } else {
155                    if (verbose) System.out.println(" is not invertible");
156                }
157                if (verbose) System.out.println();
158            }
159
160            void missingNTE(String methodname, AffineTransform at) {
161                throw new RuntimeException("Noninvertible was not "+
162                                           "thrown from "+methodname+
163                                           " for: "+at);
164            }
165
166            void extraNTE(String methodname, AffineTransform at) {
167                throw new RuntimeException("Unexpected Noninvertible "+
168                                           "thrown from "+methodname+
169                                           " for: "+at);
170            }
171        };
172
173        /*
174         * The inversion math may work out fairly exactly, but when
175         * we concatenate the inversions back with the original matrix
176         * in an attempt to restore them to the identity matrix,
177         * then we can end up compounding errors to a fairly high
178         * level, particularly if the component values had mantissas
179         * that were repeating fractions.  This function therefore
180         * "fixes" the results of concatenating the inversions back
181         * with their original matrices to get rid of small variations
182         * in the values that should have ended up being 0.0.
183         */
184        public void concatfix(AffineTransform at) {
185            double m00 = at.getScaleX();
186            double m10 = at.getShearY();
187            double m01 = at.getShearX();
188            double m11 = at.getScaleY();
189            double m02 = at.getTranslateX();
190            double m12 = at.getTranslateY();
191            if (Math.abs(m00-1.0) < 1E-10) m00 = 1.0;
192            if (Math.abs(m11-1.0) < 1E-10) m11 = 1.0;
193            if (Math.abs(m02) < 1E-10) m02 = 0.0;
194            if (Math.abs(m12) < 1E-10) m12 = 0.0;
195            if (Math.abs(m01) < 1E-15) m01 = 0.0;
196            if (Math.abs(m10) < 1E-15) m10 = 0.0;
197            at.setTransform(m00, m10,
198                            m01, m11,
199                            m02, m12);
200        }
201
202        public void test(boolean full) {
203            test(IdentityTx, full);
204        }
205
206        public void test(AffineTransform init, boolean full) {
207            test(init, theVerifier, full);
208        }
209
210        public void test(AffineTransform init, Tester next, boolean full) {
211            next.test(init, full);
212        }
213
214        public Tester chain(Tester next) {
215            return new Chain(this, next);
216        }
217
218        /*
219         * Utility node used to chain together two other nodes for
220         * implementing the "chain" method.
221         */
222        public static class Chain extends Tester {
223            Tester prev;
224            Tester next;
225
226            public Chain(Tester prev, Tester next) {
227                this.prev = prev;
228                this.next = next;
229            }
230
231            public void test(AffineTransform init, boolean full) {
232                prev.test(init, next, full);
233            }
234
235            public Tester chain(Tester next) {
236                this.next = this.next.chain(next);
237                return this;
238            }
239        }
240
241        /*
242         * Utility node for testing.
243         */
244        public static class Fail extends Tester {
245            public void test(AffineTransform init, Tester next, boolean full) {
246                throw new RuntimeException("Debug: Forcing failure");
247            }
248        }
249
250        /*
251         * Utility node for testing that chaining works.
252         */
253        public static class Debug extends Tester {
254            public void test(AffineTransform init, Tester next, boolean full) {
255                new Throwable().printStackTrace();
256                next.test(init, full);
257            }
258        }
259
260        /*
261         * NOP node.
262         */
263        public static class Identity extends Tester {
264            public void test(AffineTransform init, Tester next, boolean full) {
265                if (verbose) System.out.println("*Identity = "+init);
266                next.test(init, full);
267            }
268        }
269
270        /*
271         * Affine rotation node.
272         */
273        public static class Rotate extends Tester {
274            public void test(AffineTransform init, Tester next, boolean full) {
275                int inc = full ? 10 : 45;
276                for (int i = -720; i <= 720; i += inc) {
277                    AffineTransform at2 = new AffineTransform(init);
278                    at2.rotate(i / 180.0 * Math.PI);
279                    if (verbose) System.out.println("*Rotate("+i+") = "+at2);
280                    next.test(at2, full);
281                }
282            }
283        }
284
285        public static final double SMALL_VALUE = .0001;
286        public static final double LARGE_VALUE = 10000;
287
288        /*
289         * Affine scale node.
290         */
291        public static class Scale extends Tester {
292            public double fullvals[] = {
293                // Noninvertibles
294                0.0, 0.0,
295                0.0, 1.0,
296                1.0, 0.0,
297
298                // Invertibles
299                SMALL_VALUE, SMALL_VALUE,
300                SMALL_VALUE, 1.0,
301                1.0, SMALL_VALUE,
302
303                SMALL_VALUE, LARGE_VALUE,
304                LARGE_VALUE, SMALL_VALUE,
305
306                LARGE_VALUE, LARGE_VALUE,
307                LARGE_VALUE, 1.0,
308                1.0, LARGE_VALUE,
309
310                0.5, 0.5,
311                1.0, 1.0,
312                2.0, 2.0,
313                Math.PI, Math.E,
314            };
315            public double abbrevvals[] = {
316                0.0, 0.0,
317                1.0, 1.0,
318                2.0, 3.0,
319            };
320
321            public void test(AffineTransform init, Tester next, boolean full) {
322                double scales[] = (full ? fullvals : abbrevvals);
323                for (int i = 0; i < scales.length; i += 2) {
324                    AffineTransform at2 = new AffineTransform(init);
325                    at2.scale(scales[i], scales[i+1]);
326                    if (verbose) System.out.println("*Scale("+scales[i]+", "+
327                                                    scales[i+1]+") = "+at2);
328                    next.test(at2, full);
329                }
330            }
331        }
332
333        /*
334         * Affine shear node.
335         */
336        public static class Shear extends Tester {
337            public double fullvals[] = {
338                0.0, 0.0,
339                0.0, 1.0,
340                1.0, 0.0,
341
342                // Noninvertible
343                1.0, 1.0,
344
345                SMALL_VALUE, SMALL_VALUE,
346                SMALL_VALUE, LARGE_VALUE,
347                LARGE_VALUE, SMALL_VALUE,
348                LARGE_VALUE, LARGE_VALUE,
349
350                Math.PI, Math.E,
351            };
352            public double abbrevvals[] = {
353                0.0, 0.0,
354                0.0, 1.0,
355                1.0, 0.0,
356
357                // Noninvertible
358                1.0, 1.0,
359            };
360
361            public void test(AffineTransform init, Tester next, boolean full) {
362                double shears[] = (full ? fullvals : abbrevvals);
363                for (int i = 0; i < shears.length; i += 2) {
364                    AffineTransform at2 = new AffineTransform(init);
365                    at2.shear(shears[i], shears[i+1]);
366                    if (verbose) System.out.println("*Shear("+shears[i]+", "+
367                                                    shears[i+1]+") = "+at2);
368                    next.test(at2, full);
369                }
370            }
371        }
372
373        /*
374         * Affine translate node.
375         */
376        public static class Translate extends Tester {
377            public double fullvals[] = {
378                0.0, 0.0,
379                0.0, 1.0,
380                1.0, 0.0,
381
382                SMALL_VALUE, SMALL_VALUE,
383                SMALL_VALUE, LARGE_VALUE,
384                LARGE_VALUE, SMALL_VALUE,
385                LARGE_VALUE, LARGE_VALUE,
386
387                Math.PI, Math.E,
388            };
389            public double abbrevvals[] = {
390                0.0, 0.0,
391                0.0, 1.0,
392                1.0, 0.0,
393                Math.PI, Math.E,
394            };
395
396            public void test(AffineTransform init, Tester next, boolean full) {
397                double translates[] = (full ? fullvals : abbrevvals);
398                for (int i = 0; i < translates.length; i += 2) {
399                    AffineTransform at2 = new AffineTransform(init);
400                    at2.translate(translates[i], translates[i+1]);
401                    if (verbose) System.out.println("*Translate("+
402                                                    translates[i]+", "+
403                                                    translates[i+1]+") = "+at2);
404                    next.test(at2, full);
405                }
406            }
407        }
408    }
409
410    public static void report(AffineTransform orig,
411                              AffineTransform at1, AffineTransform at2,
412                              String message)
413    {
414        System.out.println(orig+", type = "+orig.getType());
415        System.out.println(at1+", type = "+at1.getType());
416        System.out.println(at2+", type = "+at2.getType());
417        System.out.println("ScaleX values differ by "+
418                           ulps(at1.getScaleX(),
419                                at2.getScaleX())+" ulps");
420        System.out.println("ScaleY values differ by "+
421                           ulps(at1.getScaleY(),
422                                at2.getScaleY())+" ulps");
423        System.out.println("ShearX values differ by "+
424                           ulps(at1.getShearX(),
425                                at2.getShearX())+" ulps");
426        System.out.println("ShearY values differ by "+
427                           ulps(at1.getShearY(),
428                                at2.getShearY())+" ulps");
429        System.out.println("TranslateX values differ by "+
430                           ulps(at1.getTranslateX(),
431                                at2.getTranslateX())+" ulps");
432        System.out.println("TranslateY values differ by "+
433                           ulps(at1.getTranslateY(),
434                                at2.getTranslateY())+" ulps");
435        throw new RuntimeException(message);
436    }
437
438    public static boolean compare(AffineTransform at1, AffineTransform at2) {
439        maxulps = Math.max(maxulps, ulps(at1.getScaleX(), at2.getScaleX()));
440        maxulps = Math.max(maxulps, ulps(at1.getScaleY(), at2.getScaleY()));
441        maxulps = Math.max(maxulps, ulps(at1.getShearX(), at2.getShearX()));
442        maxulps = Math.max(maxulps, ulps(at1.getShearY(), at2.getShearY()));
443        maxtxulps = Math.max(maxtxulps,
444                             ulps(at1.getTranslateX(), at2.getTranslateX()));
445        maxtxulps = Math.max(maxtxulps,
446                             ulps(at1.getTranslateY(), at2.getTranslateY()));
447        return (getModifiedType(at1) == getModifiedType(at2) &&
448                (compare(at1.getScaleX(), at2.getScaleX(), MAX_ULPS)) &&
449                (compare(at1.getScaleY(), at2.getScaleY(), MAX_ULPS)) &&
450                (compare(at1.getShearX(), at2.getShearX(), MAX_ULPS)) &&
451                (compare(at1.getShearY(), at2.getShearY(), MAX_ULPS)) &&
452                (compare(at1.getTranslateX(),
453                         at2.getTranslateX(), MAX_TX_ULPS)) &&
454                (compare(at1.getTranslateY(),
455                         at2.getTranslateY(), MAX_TX_ULPS)));
456    }
457
458    public static final int ANY_SCALE_MASK =
459        (AffineTransform.TYPE_UNIFORM_SCALE |
460         AffineTransform.TYPE_GENERAL_SCALE);
461    public static int getModifiedType(AffineTransform at) {
462        int type = at.getType();
463        // Some of the vector methods can introduce a tiny uniform scale
464        // at some angles...
465        if ((type & ANY_SCALE_MASK) != 0) {
466            maxulps = Math.max(maxulps, ulps(at.getDeterminant(), 1.0));
467            if (ulps(at.getDeterminant(), 1.0) <= MAX_ULPS) {
468                // Really tiny - we will ignore it
469                type &= ~ ANY_SCALE_MASK;
470            }
471        }
472        return type;
473    }
474
475    public static boolean compare(double val1, double val2, double maxulps) {
476        if (Math.abs(val1 - val2) < 1E-15) return true;
477        return (ulps(val1, val2) <= maxulps);
478    }
479
480    public static double ulps(double val1, double val2) {
481        double diff = Math.abs(val1 - val2);
482        double ulpmax = Math.min(Math.ulp(val1), Math.ulp(val2));
483        return (diff / ulpmax);
484    }
485}
486