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