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 }