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 
36 import java.awt.geom.AffineTransform;
37 import 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  */
59 public 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 
main(String argv[])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          */
concatfix(AffineTransform at)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 
test(boolean full)202         public void test(boolean full) {
203             test(IdentityTx, full);
204         }
205 
test(AffineTransform init, boolean full)206         public void test(AffineTransform init, boolean full) {
207             test(init, theVerifier, full);
208         }
209 
test(AffineTransform init, Tester next, boolean full)210         public void test(AffineTransform init, Tester next, boolean full) {
211             next.test(init, full);
212         }
213 
chain(Tester next)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 
Chain(Tester prev, Tester next)226             public Chain(Tester prev, Tester next) {
227                 this.prev = prev;
228                 this.next = next;
229             }
230 
test(AffineTransform init, boolean full)231             public void test(AffineTransform init, boolean full) {
232                 prev.test(init, next, full);
233             }
234 
chain(Tester next)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 {
test(AffineTransform init, Tester next, boolean full)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 {
test(AffineTransform init, Tester next, boolean full)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 {
test(AffineTransform init, Tester next, boolean full)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 {
test(AffineTransform init, Tester next, boolean full)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 
test(AffineTransform init, Tester next, boolean full)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 
test(AffineTransform init, Tester next, boolean full)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 
test(AffineTransform init, Tester next, boolean full)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 
report(AffineTransform orig, AffineTransform at1, AffineTransform at2, String message)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 
compare(AffineTransform at1, AffineTransform at2)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);
getModifiedType(AffineTransform at)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 
compare(double val1, double val2, double maxulps)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 
ulps(double val1, double val2)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