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 
  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 }