1 /*
   2  * Copyright (c) 2005, 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
  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 
  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(m02) < 1E-10) m02 = 0.0;
 192             if (Math.abs(m12) < 1E-10) m12 = 0.0;
 193             if (Math.abs(m01) < 1E-15) m01 = 0.0;
 194             if (Math.abs(m10) < 1E-15) m10 = 0.0;
 195             at.setTransform(m00, m10,
 196                             m01, m11,
 197                             m02, m12);
 198         }
 199 
 200         public void test(boolean full) {
 201             test(IdentityTx, full);
 202         }
 203 
 204         public void test(AffineTransform init, boolean full) {
 205             test(init, theVerifier, full);
 206         }
 207 
 208         public void test(AffineTransform init, Tester next, boolean full) {
 209             next.test(init, full);
 210         }
 211 
 212         public Tester chain(Tester next) {
 213             return new Chain(this, next);
 214         }
 215 
 216         /*
 217          * Utility node used to chain together two other nodes for
 218          * implementing the "chain" method.
 219          */
 220         public static class Chain extends Tester {
 221             Tester prev;
 222             Tester next;
 223 
 224             public Chain(Tester prev, Tester next) {
 225                 this.prev = prev;
 226                 this.next = next;
 227             }
 228 
 229             public void test(AffineTransform init, boolean full) {
 230                 prev.test(init, next, full);
 231             }
 232 
 233             public Tester chain(Tester next) {
 234                 this.next = this.next.chain(next);
 235                 return this;
 236             }
 237         }
 238 
 239         /*
 240          * Utility node for testing.
 241          */
 242         public static class Fail extends Tester {
 243             public void test(AffineTransform init, Tester next, boolean full) {
 244                 throw new RuntimeException("Debug: Forcing failure");
 245             }
 246         }
 247 
 248         /*
 249          * Utility node for testing that chaining works.
 250          */
 251         public static class Debug extends Tester {
 252             public void test(AffineTransform init, Tester next, boolean full) {
 253                 new Throwable().printStackTrace();
 254                 next.test(init, full);
 255             }
 256         }
 257 
 258         /*
 259          * NOP node.
 260          */
 261         public static class Identity extends Tester {
 262             public void test(AffineTransform init, Tester next, boolean full) {
 263                 if (verbose) System.out.println("*Identity = "+init);
 264                 next.test(init, full);
 265             }
 266         }
 267 
 268         /*
 269          * Affine rotation node.
 270          */
 271         public static class Rotate extends Tester {
 272             public void test(AffineTransform init, Tester next, boolean full) {
 273                 int inc = full ? 10 : 45;
 274                 for (int i = -720; i <= 720; i += inc) {
 275                     AffineTransform at2 = new AffineTransform(init);
 276                     at2.rotate(Math.toRadians(i));
 277                     if (verbose) System.out.println("*Rotate("+i+") = "+at2);
 278                     next.test(at2, full);
 279                 }
 280             }
 281         }
 282 
 283         public static final double SMALL_VALUE = .0001;
 284         public static final double LARGE_VALUE = 10000;
 285 
 286         /*
 287          * Affine scale node.
 288          */
 289         public static class Scale extends Tester {
 290             public double fullvals[] = {
 291                 // Noninvertibles
 292                 0.0, 0.0,
 293                 0.0, 1.0,
 294                 1.0, 0.0,
 295 
 296                 // Invertibles
 297                 SMALL_VALUE, SMALL_VALUE,
 298                 SMALL_VALUE, 1.0,
 299                 1.0, SMALL_VALUE,
 300 
 301                 SMALL_VALUE, LARGE_VALUE,
 302                 LARGE_VALUE, SMALL_VALUE,
 303 
 304                 LARGE_VALUE, LARGE_VALUE,
 305                 LARGE_VALUE, 1.0,
 306                 1.0, LARGE_VALUE,
 307 
 308                 0.5, 0.5,
 309                 1.0, 1.0,
 310                 2.0, 2.0,
 311                 Math.PI, Math.E,
 312             };
 313             public double abbrevvals[] = {
 314                 0.0, 0.0,
 315                 1.0, 1.0,
 316                 2.0, 3.0,
 317             };
 318 
 319             public void test(AffineTransform init, Tester next, boolean full) {
 320                 double scales[] = (full ? fullvals : abbrevvals);
 321                 for (int i = 0; i < scales.length; i += 2) {
 322                     AffineTransform at2 = new AffineTransform(init);
 323                     at2.scale(scales[i], scales[i+1]);
 324                     if (verbose) System.out.println("*Scale("+scales[i]+", "+
 325                                                     scales[i+1]+") = "+at2);
 326                     next.test(at2, full);
 327                 }
 328             }
 329         }
 330 
 331         /*
 332          * Affine shear node.
 333          */
 334         public static class Shear extends Tester {
 335             public double fullvals[] = {
 336                 0.0, 0.0,
 337                 0.0, 1.0,
 338                 1.0, 0.0,
 339 
 340                 // Noninvertible
 341                 1.0, 1.0,
 342 
 343                 SMALL_VALUE, SMALL_VALUE,
 344                 SMALL_VALUE, LARGE_VALUE,
 345                 LARGE_VALUE, SMALL_VALUE,
 346                 LARGE_VALUE, LARGE_VALUE,
 347 
 348                 Math.PI, Math.E,
 349             };
 350             public double abbrevvals[] = {
 351                 0.0, 0.0,
 352                 0.0, 1.0,
 353                 1.0, 0.0,
 354 
 355                 // Noninvertible
 356                 1.0, 1.0,
 357             };
 358 
 359             public void test(AffineTransform init, Tester next, boolean full) {
 360                 double shears[] = (full ? fullvals : abbrevvals);
 361                 for (int i = 0; i < shears.length; i += 2) {
 362                     AffineTransform at2 = new AffineTransform(init);
 363                     at2.shear(shears[i], shears[i+1]);
 364                     if (verbose) System.out.println("*Shear("+shears[i]+", "+
 365                                                     shears[i+1]+") = "+at2);
 366                     next.test(at2, full);
 367                 }
 368             }
 369         }
 370 
 371         /*
 372          * Affine translate node.
 373          */
 374         public static class Translate extends Tester {
 375             public double fullvals[] = {
 376                 0.0, 0.0,
 377                 0.0, 1.0,
 378                 1.0, 0.0,
 379 
 380                 SMALL_VALUE, SMALL_VALUE,
 381                 SMALL_VALUE, LARGE_VALUE,
 382                 LARGE_VALUE, SMALL_VALUE,
 383                 LARGE_VALUE, LARGE_VALUE,
 384 
 385                 Math.PI, Math.E,
 386             };
 387             public double abbrevvals[] = {
 388                 0.0, 0.0,
 389                 0.0, 1.0,
 390                 1.0, 0.0,
 391                 Math.PI, Math.E,
 392             };
 393 
 394             public void test(AffineTransform init, Tester next, boolean full) {
 395                 double translates[] = (full ? fullvals : abbrevvals);
 396                 for (int i = 0; i < translates.length; i += 2) {
 397                     AffineTransform at2 = new AffineTransform(init);
 398                     at2.translate(translates[i], translates[i+1]);
 399                     if (verbose) System.out.println("*Translate("+
 400                                                     translates[i]+", "+
 401                                                     translates[i+1]+") = "+at2);
 402                     next.test(at2, full);
 403                 }
 404             }
 405         }
 406     }
 407 
 408     public static void report(AffineTransform orig,
 409                               AffineTransform at1, AffineTransform at2,
 410                               String message)
 411     {
 412         System.out.println(orig+", type = "+orig.getType());
 413         System.out.println(at1+", type = "+at1.getType());
 414         System.out.println(at2+", type = "+at2.getType());
 415         System.out.println("ScaleX values differ by "+
 416                            ulps(at1.getScaleX(),
 417                                 at2.getScaleX())+" ulps");
 418         System.out.println("ScaleY values differ by "+
 419                            ulps(at1.getScaleY(),
 420                                 at2.getScaleY())+" ulps");
 421         System.out.println("ShearX values differ by "+
 422                            ulps(at1.getShearX(),
 423                                 at2.getShearX())+" ulps");
 424         System.out.println("ShearY values differ by "+
 425                            ulps(at1.getShearY(),
 426                                 at2.getShearY())+" ulps");
 427         System.out.println("TranslateX values differ by "+
 428                            ulps(at1.getTranslateX(),
 429                                 at2.getTranslateX())+" ulps");
 430         System.out.println("TranslateY values differ by "+
 431                            ulps(at1.getTranslateY(),
 432                                 at2.getTranslateY())+" ulps");
 433         throw new RuntimeException(message);
 434     }
 435 
 436     public static boolean compare(AffineTransform at1, AffineTransform at2) {
 437         maxulps = Math.max(maxulps, ulps(at1.getScaleX(), at2.getScaleX()));
 438         maxulps = Math.max(maxulps, ulps(at1.getScaleY(), at2.getScaleY()));
 439         maxulps = Math.max(maxulps, ulps(at1.getShearX(), at2.getShearX()));
 440         maxulps = Math.max(maxulps, ulps(at1.getShearY(), at2.getShearY()));
 441         maxtxulps = Math.max(maxtxulps,
 442                              ulps(at1.getTranslateX(), at2.getTranslateX()));
 443         maxtxulps = Math.max(maxtxulps,
 444                              ulps(at1.getTranslateY(), at2.getTranslateY()));
 445         return (getModifiedType(at1) == getModifiedType(at2) &&
 446                 (compare(at1.getScaleX(), at2.getScaleX(), MAX_ULPS)) &&
 447                 (compare(at1.getScaleY(), at2.getScaleY(), MAX_ULPS)) &&
 448                 (compare(at1.getShearX(), at2.getShearX(), MAX_ULPS)) &&
 449                 (compare(at1.getShearY(), at2.getShearY(), MAX_ULPS)) &&
 450                 (compare(at1.getTranslateX(),
 451                          at2.getTranslateX(), MAX_TX_ULPS)) &&
 452                 (compare(at1.getTranslateY(),
 453                          at2.getTranslateY(), MAX_TX_ULPS)));
 454     }
 455 
 456     public static final int ANY_SCALE_MASK =
 457         (AffineTransform.TYPE_UNIFORM_SCALE |
 458          AffineTransform.TYPE_GENERAL_SCALE);
 459     public static int getModifiedType(AffineTransform at) {
 460         int type = at.getType();
 461         // Some of the vector methods can introduce a tiny uniform scale
 462         // at some angles...
 463         if ((type & ANY_SCALE_MASK) != 0) {
 464             maxulps = Math.max(maxulps, ulps(at.getDeterminant(), 1.0));
 465             if (ulps(at.getDeterminant(), 1.0) <= MAX_ULPS) {
 466                 // Really tiny - we will ignore it
 467                 type &= ~ ANY_SCALE_MASK;
 468             }
 469         }
 470         return type;
 471     }
 472 
 473     public static boolean compare(double val1, double val2, double maxulps) {
 474         if (Math.abs(val1 - val2) < 1E-15) return true;
 475         return (ulps(val1, val2) <= maxulps);
 476     }
 477 
 478     public static double ulps(double val1, double val2) {
 479         double diff = Math.abs(val1 - val2);
 480         double ulpmax = Math.min(Math.ulp(val1), Math.ulp(val2));
 481         return (diff / ulpmax);
 482     }
 483 }