1 /*
   2  * Copyright (c) 2004, 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 4980035
  27  * @summary Unit test for new methods:
  28  *
  29  *          AffineTransform.getRotateInstance(double x, double y);
  30  *          AffineTransform.setToRotation(double x, double y);
  31  *          AffineTransform.rotate(double x, double y);
  32  *
  33  *          AffineTransform.getQuadrantRotateInstance(int numquads);
  34  *          AffineTransform.setToQuadrantRotation(int numquads);
  35  *          AffineTransform.quadrantRotate(int numquads);
  36  *
  37  * @author flar
  38  * @run main TestRotateMethods
  39  * @key randomness
  40  */
  41 
  42 import java.awt.geom.AffineTransform;
  43 import java.awt.geom.Point2D;
  44 
  45 public class TestRotateMethods {
  46     /* The maximum errors allowed, measured in double precision "ulps"
  47      * Note that for most fields, the tests are extremely accurate - to
  48      * within 3 ulps of the smaller value in the comparison
  49      * For the translation components, the tests are still very accurate,
  50      * but the absolute number of ulps can be noticeably higher when we
  51      * use one of the rotate methods that takes an anchor point.
  52      * Since a double precision value has 56 bits of precision, even
  53      * 1024 ulps is extremely small as a ratio of the value.
  54      */
  55     public static final double MAX_ULPS = 3.0;
  56     public static final double MAX_ANCHOR_TX_ULPS = 1024.0;
  57     public static double MAX_TX_ULPS = MAX_ULPS;
  58 
  59     // Vectors for quadrant rotations
  60     public static final double quadxvec[] = {  1.0,  0.0, -1.0,  0.0 };
  61     public static final double quadyvec[] = {  0.0,  1.0,  0.0, -1.0 };
  62 
  63     // Run tests once for each type of method:
  64     //     tx = AffineTransform.get<Rotate>Instance()
  65     //     tx.set<Rotate>()
  66     //     tx.<rotate>()
  67     public static enum Mode { GET, SET, MOD };
  68 
  69     // Used to accumulate and report largest differences encountered by tests
  70     public static double maxulps = 0.0;
  71     public static double maxtxulps = 0.0;
  72 
  73     // Sample anchor points for testing.
  74     public static Point2D zeropt = new Point2D.Double(0, 0);
  75     public static Point2D testtxpts[] = {
  76         new Point2D.Double(       5,      5),
  77         new Point2D.Double(      20,    -10),
  78         new Point2D.Double(-Math.PI, Math.E),
  79     };
  80 
  81     public static void main(String argv[]) {
  82         test(Mode.GET);
  83         test(Mode.SET);
  84         test(Mode.MOD);
  85 
  86         System.out.println("Max scale and shear difference: "+maxulps+" ulps");
  87         System.out.println("Max translate difference: "+maxtxulps+" ulps");
  88     }
  89 
  90     public static void test(Mode mode) {
  91         MAX_TX_ULPS = MAX_ULPS; // Stricter tx testing with no anchor point
  92         test(mode, 0.5, null);
  93         test(mode, 1.0, null);
  94         test(mode, 3.0, null);
  95 
  96         // Anchor points make the tx values less reliable
  97         MAX_TX_ULPS = MAX_ANCHOR_TX_ULPS;
  98         for (int i = 0; i < testtxpts.length; i++) {
  99             test(mode, 1.0, testtxpts[i]);
 100         }
 101         MAX_TX_ULPS = MAX_ULPS; // Restore to default
 102     }
 103 
 104     public static void verify(AffineTransform at1, AffineTransform at2,
 105                               Mode mode, double vectorscale, Point2D txpt,
 106                               String message, double num, String units)
 107     {
 108         if (!compare(at1, at2)) {
 109             System.out.println("mode == "+mode);
 110             System.out.println("vectorscale == "+vectorscale);
 111             System.out.println("txpt == "+txpt);
 112             System.out.println(at1+", type = "+at1.getType());
 113             System.out.println(at2+", type = "+at2.getType());
 114             System.out.println("ScaleX values differ by "+
 115                                ulps(at1.getScaleX(), at2.getScaleX())+" ulps");
 116             System.out.println("ScaleY values differ by "+
 117                                ulps(at1.getScaleY(), at2.getScaleY())+" ulps");
 118             System.out.println("ShearX values differ by "+
 119                                ulps(at1.getShearX(), at2.getShearX())+" ulps");
 120             System.out.println("ShearY values differ by "+
 121                                ulps(at1.getShearY(), at2.getShearY())+" ulps");
 122             System.out.println("TranslateX values differ by "+
 123                                ulps(at1.getTranslateX(),
 124                                     at2.getTranslateX())+" ulps");
 125             System.out.println("TranslateY values differ by "+
 126                                ulps(at1.getTranslateY(),
 127                                     at2.getTranslateY())+" ulps");
 128             throw new RuntimeException(message + num + units);
 129         }
 130     }
 131 
 132     public static void test(Mode mode, double vectorscale, Point2D txpt) {
 133         AffineTransform at1, at2, at3;
 134 
 135         for (int deg = -720; deg <= 720; deg++) {
 136             if ((deg % 90) == 0) continue;
 137             double radians = Math.toRadians(deg);
 138             double vecy = Math.sin(radians) * vectorscale;
 139             double vecx = Math.cos(radians) * vectorscale;
 140 
 141             at1 = makeAT(mode, txpt, radians);
 142             at2 = makeAT(mode, txpt, vecx, vecy);
 143             verify(at1, at2, mode, vectorscale, txpt,
 144                    "vector and radians do not match for ", deg, " degrees");
 145 
 146             if (txpt == null) {
 147                 // Make sure output was same as a with a 0,0 anchor point
 148                 if (vectorscale == 1.0) {
 149                     // Only need to test radians method for one scale factor
 150                     at3 = makeAT(mode, zeropt, radians);
 151                     verify(at1, at3, mode, vectorscale, zeropt,
 152                            "radians not invariant with 0,0 translate at ",
 153                            deg, " degrees");
 154                 }
 155                 // But test vector methods with all scale factors
 156                 at3 = makeAT(mode, zeropt, vecx, vecy);
 157                 verify(at2, at3, mode, vectorscale, zeropt,
 158                        "vector not invariant with 0,0 translate at ",
 159                        deg, " degrees");
 160             }
 161         }
 162 
 163         for (int quad = -8; quad <= 8; quad++) {
 164             double degrees = quad * 90.0;
 165             double radians = Math.toRadians(degrees);
 166             double vecx = quadxvec[quad & 3] * vectorscale;
 167             double vecy = quadyvec[quad & 3] * vectorscale;
 168 
 169             at1 = makeAT(mode, txpt, radians);
 170             at2 = makeAT(mode, txpt, vecx, vecy);
 171             verify(at1, at2, mode, vectorscale, txpt,
 172                    "quadrant vector and radians do not match for ",
 173                    degrees, " degrees");
 174             at2 = makeQuadAT(mode, txpt, quad);
 175             verify(at1, at2, mode, vectorscale, txpt,
 176                    "quadrant and radians do not match for ",
 177                    quad, " quadrants");
 178             if (txpt == null) {
 179                 at3 = makeQuadAT(mode, zeropt, quad);
 180                 verify(at2, at3, mode, vectorscale, zeropt,
 181                        "quadrant not invariant with 0,0 translate at ",
 182                        quad, " quadrants");
 183             }
 184         }
 185     }
 186 
 187     public static AffineTransform makeRandomAT() {
 188         AffineTransform at = new AffineTransform();
 189         at.scale(Math.random() * -10.0, Math.random() * 100.0);
 190         at.rotate(Math.random() * Math.PI);
 191         at.shear(Math.random(), Math.random());
 192         at.translate(Math.random() * 300.0, Math.random() * -20.0);
 193         return at;
 194     }
 195 
 196     public static AffineTransform makeAT(Mode mode, Point2D txpt,
 197                                          double radians)
 198     {
 199         AffineTransform at;
 200         double tx = (txpt == null) ? 0.0 : txpt.getX();
 201         double ty = (txpt == null) ? 0.0 : txpt.getY();
 202         switch (mode) {
 203         case GET:
 204             if (txpt != null) {
 205                 at = AffineTransform.getRotateInstance(radians, tx, ty);
 206             } else {
 207                 at = AffineTransform.getRotateInstance(radians);
 208             }
 209             break;
 210         case SET:
 211             at = makeRandomAT();
 212             if (txpt != null) {
 213                 at.setToRotation(radians, tx, ty);
 214             } else {
 215                 at.setToRotation(radians);
 216             }
 217             break;
 218         case MOD:
 219             at = makeRandomAT();
 220             at.setToIdentity();
 221             if (txpt != null) {
 222                 at.rotate(radians, tx, ty);
 223             } else {
 224                 at.rotate(radians);
 225             }
 226             break;
 227         default:
 228             throw new InternalError("unrecognized mode: "+mode);
 229         }
 230 
 231         return at;
 232     }
 233 
 234     public static AffineTransform makeAT(Mode mode, Point2D txpt,
 235                                          double vx, double vy)
 236     {
 237         AffineTransform at;
 238         double tx = (txpt == null) ? 0.0 : txpt.getX();
 239         double ty = (txpt == null) ? 0.0 : txpt.getY();
 240         switch (mode) {
 241         case GET:
 242             if (txpt != null) {
 243                 at = AffineTransform.getRotateInstance(vx, vy, tx, ty);
 244             } else {
 245                 at = AffineTransform.getRotateInstance(vx, vy);
 246             }
 247             break;
 248         case SET:
 249             at = makeRandomAT();
 250             if (txpt != null) {
 251                 at.setToRotation(vx, vy, tx, ty);
 252             } else {
 253                 at.setToRotation(vx, vy);
 254             }
 255             break;
 256         case MOD:
 257             at = makeRandomAT();
 258             at.setToIdentity();
 259             if (txpt != null) {
 260                 at.rotate(vx, vy, tx, ty);
 261             } else {
 262                 at.rotate(vx, vy);
 263             }
 264             break;
 265         default:
 266             throw new InternalError("unrecognized mode: "+mode);
 267         }
 268 
 269         return at;
 270     }
 271 
 272     public static AffineTransform makeQuadAT(Mode mode, Point2D txpt,
 273                                              int quads)
 274     {
 275         AffineTransform at;
 276         double tx = (txpt == null) ? 0.0 : txpt.getX();
 277         double ty = (txpt == null) ? 0.0 : txpt.getY();
 278         switch (mode) {
 279         case GET:
 280             if (txpt != null) {
 281                 at = AffineTransform.getQuadrantRotateInstance(quads, tx, ty);
 282             } else {
 283                 at = AffineTransform.getQuadrantRotateInstance(quads);
 284             }
 285             break;
 286         case SET:
 287             at = makeRandomAT();
 288             if (txpt != null) {
 289                 at.setToQuadrantRotation(quads, tx, ty);
 290             } else {
 291                 at.setToQuadrantRotation(quads);
 292             }
 293             break;
 294         case MOD:
 295             at = makeRandomAT();
 296             at.setToIdentity();
 297             if (txpt != null) {
 298                 at.quadrantRotate(quads, tx, ty);
 299             } else {
 300                 at.quadrantRotate(quads);
 301             }
 302             break;
 303         default:
 304             throw new InternalError("unrecognized mode: "+mode);
 305         }
 306 
 307         return at;
 308     }
 309 
 310     public static boolean compare(AffineTransform at1, AffineTransform at2) {
 311         maxulps = Math.max(maxulps, ulps(at1.getScaleX(), at2.getScaleX()));
 312         maxulps = Math.max(maxulps, ulps(at1.getScaleY(), at2.getScaleY()));
 313         maxulps = Math.max(maxulps, ulps(at1.getShearX(), at2.getShearX()));
 314         maxulps = Math.max(maxulps, ulps(at1.getShearY(), at2.getShearY()));
 315         maxtxulps = Math.max(maxtxulps,
 316                              ulps(at1.getTranslateX(), at2.getTranslateX()));
 317         maxtxulps = Math.max(maxtxulps,
 318                              ulps(at1.getTranslateY(), at2.getTranslateY()));
 319         return (getModifiedType(at1) == getModifiedType(at2) &&
 320                 (compare(at1.getScaleX(), at2.getScaleX(), MAX_ULPS)) &&
 321                 (compare(at1.getScaleY(), at2.getScaleY(), MAX_ULPS)) &&
 322                 (compare(at1.getShearX(), at2.getShearX(), MAX_ULPS)) &&
 323                 (compare(at1.getShearY(), at2.getShearY(), MAX_ULPS)) &&
 324                 (compare(at1.getTranslateX(),
 325                          at2.getTranslateX(), MAX_TX_ULPS)) &&
 326                 (compare(at1.getTranslateY(),
 327                          at2.getTranslateY(), MAX_TX_ULPS)));
 328     }
 329 
 330     public static int getModifiedType(AffineTransform at) {
 331         int type = at.getType();
 332         // Some of the vector methods can introduce a tiny uniform scale
 333         // at some angles...
 334         if ((type & AffineTransform.TYPE_UNIFORM_SCALE) != 0) {
 335             maxulps = Math.max(maxulps, ulps(at.getDeterminant(), 1.0));
 336             if (ulps(at.getDeterminant(), 1.0) <= MAX_ULPS) {
 337                 // Really tiny - we will ignore it
 338                 type &= (~AffineTransform.TYPE_UNIFORM_SCALE);
 339             }
 340         }
 341         return type;
 342     }
 343 
 344     public static boolean compare(double val1, double val2, double maxulps) {
 345         return (ulps(val1, val2) <= maxulps);
 346     }
 347 
 348     public static double ulps(double val1, double val2) {
 349         double diff = Math.abs(val1 - val2);
 350         double ulpmax = Math.min(Math.ulp(val1), Math.ulp(val2));
 351         return (diff / ulpmax);
 352     }
 353 }