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