1 /*
   2  * Copyright (c) 2011, 2013, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.sg.prism;
  27 
  28 import javafx.scene.Group;
  29 import javafx.scene.Node;
  30 import javafx.scene.Parent;
  31 import javafx.scene.Scene;
  32 import javafx.scene.shape.Rectangle;
  33 import com.sun.javafx.geom.BaseBounds;
  34 import com.sun.javafx.geom.RectBounds;
  35 import com.sun.javafx.geom.transform.Affine2D;
  36 import com.sun.javafx.geom.transform.BaseTransform;
  37 import org.junit.Test;
  38 import static org.junit.Assert.assertEquals;
  39 
  40 /**
  41  */
  42 public class ContentBoundsTest {
  43     public static final BaseTransform IDENTITY;
  44     public static final BaseTransform TRANSLATE;
  45     public static final BaseTransform SCALE;
  46     public static final BaseTransform ROTATE;
  47     public static final BaseTransform SCALE_TRANSLATE;
  48     public static final BaseTransform TRANSLATE_SCALE;
  49 
  50     public static BaseTransform translate(BaseTransform transform,
  51                                           double tx, double ty)
  52     {
  53         transform = BaseTransform.getInstance(transform);
  54         return transform.deriveWithConcatenation(1, 0, 0, 1, tx, ty);
  55     }
  56 
  57     public static BaseTransform scale(BaseTransform transform,
  58                                       double sx, double sy)
  59     {
  60         transform = BaseTransform.getInstance(transform);
  61         return transform.deriveWithConcatenation(sx, 0, 0, sy, 0, 0);
  62     }
  63 
  64     public static BaseTransform rotate(BaseTransform transform,
  65                                        double degrees)
  66     {
  67         Affine2D t2d = new Affine2D(transform);
  68         t2d.rotate(Math.toRadians(degrees));
  69         return t2d;
  70     }
  71 
  72     static {
  73         IDENTITY = BaseTransform.IDENTITY_TRANSFORM;
  74         TRANSLATE = translate(IDENTITY, 42.3, 16.5);
  75         SCALE = scale(IDENTITY, 0.7, 0.6);
  76         ROTATE = rotate(IDENTITY, 135);
  77         TRANSLATE_SCALE = scale(TRANSLATE, 0.8, 0.9);
  78         SCALE_TRANSLATE = translate(SCALE, 23.7, 83.5);
  79     }
  80 
  81     public static Node translate(double tx, double ty, Node n) {
  82         n.setTranslateX(tx);
  83         n.setTranslateY(ty);
  84         return n;
  85     }
  86 
  87     public static Node scale(double sx, double sy, Node n) {
  88         n.setScaleX(sx);
  89         n.setScaleY(sy);
  90         return n;
  91     }
  92 
  93     public static Node rotate(double rot, Node n) {
  94         n.setRotate(rot);
  95         return n;
  96     }
  97 
  98     public static Node group(Node... n) {
  99         Group g = new Group(n);
 100         return g;
 101     }
 102 
 103     public static Node makeRectangle(double x, double y, double w, double h) {
 104         return new Rectangle(x, y, w, h);
 105     }
 106 
 107     public static NGNode getValidatedPGNode(Node n) {
 108         if (n instanceof Parent) {
 109             for (Node child : ((Parent) n).getChildrenUnmodifiable()) {
 110                 getValidatedPGNode(child);
 111             }
 112         }
 113         NGNode pgn = n.impl_getPeer();
 114         // Eeek, this is gross! I have to use reflection to invoke this
 115         // method so that bounds are updated...
 116         try {
 117             java.lang.reflect.Method method = Node.class.getDeclaredMethod("updateBounds");
 118             method.setAccessible(true);
 119             method.invoke(n);
 120         } catch (Exception e) {
 121             throw new RuntimeException("Failed to update bounds", e);
 122         }
 123         n.impl_updatePeer();
 124         return pgn;
 125     }
 126 
 127     public static BaseBounds getBounds(Node n, BaseTransform tx) {
 128         Scene.impl_setAllowPGAccess(true);
 129         NGNode pgn = getValidatedPGNode(n);
 130         Scene.impl_setAllowPGAccess(false);
 131         return pgn.getContentBounds(new RectBounds(), tx);
 132     }
 133 
 134     public static class TestPoint {
 135         private float x;
 136         private float y;
 137         private boolean contains;
 138 
 139         public TestPoint(float x, float y, boolean contains) {
 140             this.x = x;
 141             this.y = y;
 142             this.contains = contains;
 143         }
 144 
 145         public boolean isContains() {
 146             return contains;
 147         }
 148 
 149         public float getX() {
 150             return x;
 151         }
 152 
 153         public float getY() {
 154             return y;
 155         }
 156     }
 157 
 158     public static void checkContentPoint(Node n, TestPoint tp,
 159                                          BaseTransform transform)
 160     {
 161         BaseBounds bounds = getBounds(n, transform);
 162         float c[] = new float[] {tp.getX(), tp.getY()};
 163         transform.transform(c, 0, c, 0, 1);
 164         boolean success = false;
 165         try {
 166             assertEquals(bounds.contains(c[0], c[1]), tp.isContains());
 167             success = true;
 168         } finally {
 169             if (!success) {
 170                 System.err.println("Failed on bounds = "+bounds);
 171                 System.err.println("with transform = "+transform);
 172                 System.err.println("with  x,  y = "+tp.getX()+", "+tp.getY());
 173                 System.err.println("and  tx, ty = "+c[0]+", "+c[1]);
 174             }
 175         }
 176     }
 177 
 178     // When a chain of transforms is involved, it can help to back off
 179     // slightly from the edges of a shape using this tiny constant
 180     // to avoid failing a test due to floating point rounding error.
 181     static final float EPSILON = 1e-6f;
 182 
 183     public TestPoint[] rectPoints(float x, float y, float w, float h) {
 184         return new TestPoint[] {
 185             new TestPoint(x  +EPSILON, y  +EPSILON, true),
 186             new TestPoint(x+w-EPSILON, y  +EPSILON, true),
 187             new TestPoint(x  +EPSILON, y+h-EPSILON, true),
 188             new TestPoint(x+w-EPSILON, y+h-EPSILON, true),
 189             new TestPoint(x+w, y+h+h, false)
 190         };
 191     }
 192 
 193     public BaseBounds getBounds(TestPoint... testpts) {
 194         RectBounds rb = new RectBounds();
 195         for (TestPoint tp : testpts) {
 196             if (tp.isContains()) {
 197                 rb.add(tp.getX(), tp.getY());
 198             }
 199         }
 200         return rb;
 201     }
 202 
 203     public TestPoint[] translate(float tx, float ty, TestPoint... testpts) {
 204         TestPoint ret[] = new TestPoint[testpts.length];
 205         for (int i = 0; i < testpts.length; i++) {
 206             TestPoint tp = testpts[i];
 207             ret[i] = new TestPoint(tp.getX() + tx, tp.getY() + ty,
 208                                    tp.isContains());
 209         }
 210         return ret;
 211     }
 212 
 213     public TestPoint[] scale(float sx, float sy, TestPoint... testpts) {
 214         BaseBounds bounds = getBounds(testpts);
 215         float cx = (bounds.getMinX() + bounds.getMaxX()) / 2.0f;
 216         float cy = (bounds.getMinY() + bounds.getMaxY()) / 2.0f;
 217         TestPoint ret[] = new TestPoint[testpts.length];
 218         for (int i = 0; i < testpts.length; i++) {
 219             TestPoint tp = testpts[i];
 220             ret[i] = new TestPoint((tp.getX() - cx) * sx + cx,
 221                                    (tp.getY() - cy) * sy + cy,
 222                                    tp.isContains());
 223         }
 224         return ret;
 225     }
 226 
 227     public TestPoint[] rotate(double degrees, TestPoint... testpts) {
 228         BaseBounds bounds = getBounds(testpts);
 229         float cx = (bounds.getMinX() + bounds.getMaxX()) / 2.0f;
 230         float cy = (bounds.getMinY() + bounds.getMaxY()) / 2.0f;
 231         TestPoint ret[] = new TestPoint[testpts.length];
 232         double radians = Math.toRadians(degrees);
 233         float cos = (float) Math.cos(radians);
 234         float sin = (float) Math.sin(radians);
 235         for (int i = 0; i < testpts.length; i++) {
 236             TestPoint tp = testpts[i];
 237             float relx = tp.getX() - cx;
 238             float rely = tp.getY() - cy;
 239             ret[i] = new TestPoint(relx * cos - rely * sin + cx,
 240                                    relx * sin + rely * cos + cy,
 241                                    tp.isContains());
 242         }
 243         return ret;
 244     }
 245 
 246     public void checkPoints(Node n, TestPoint... testpts) {
 247         for (TestPoint tp : testpts) {
 248             checkContentPoint(n, tp, IDENTITY);
 249             checkContentPoint(n, tp, TRANSLATE);
 250             checkContentPoint(n, tp, SCALE);
 251             checkContentPoint(n, tp, ROTATE);
 252             checkContentPoint(n, tp, TRANSLATE_SCALE);
 253             checkContentPoint(n, tp, SCALE_TRANSLATE);
 254         }
 255     }
 256 
 257     @Test public void testRectangle() {
 258         Node r = makeRectangle(10, 10, 20, 20);
 259         checkPoints(r, rectPoints(10, 10, 20, 20));
 260     }
 261 
 262     @Test public void testTranslatedRectangle() {
 263         Node r = translate(234.7f, 176.3f, makeRectangle(10, 10, 20, 20));
 264         // Content bounds is local to the node, so we ignore the tx, ty
 265         checkPoints(r, rectPoints(10, 10, 20, 20));
 266     }
 267 
 268     @Test public void testScaledRectangle() {
 269         Node r = scale(1.3, 1.1, makeRectangle(10, 10, 20, 20));
 270         // Content bounds is local to the node, so we ignore the sx, sy
 271         checkPoints(r, rectPoints(10, 10, 20, 20));
 272     }
 273 
 274     @Test public void testRotatedRectangle() {
 275         Node r = rotate(15, makeRectangle(10, 10, 20, 20));
 276         // Content bounds is local to the node, so we ignore the rot
 277         checkPoints(r, rectPoints(10, 10, 20, 20));
 278     }
 279 
 280     @Test public void testGroupedRectangle() {
 281         Node r = group(makeRectangle(10, 10, 20, 20));
 282         checkPoints(r, rectPoints(10, 10, 20, 20));
 283     }
 284 
 285     @Test public void testGroupedTranslatedRectangle() {
 286         float tx = 234.7f;
 287         float ty = 165.3f;
 288         Node r = group(translate(tx, ty, makeRectangle(10, 10, 20, 20)));
 289         checkPoints(r, translate(tx, ty, rectPoints(10, 10, 20, 20)));
 290     }
 291 
 292     @Test public void testGroupedScaledRectangle() {
 293         float sx = 1.3f;
 294         float sy = 1.1f;
 295         Node n = group(scale(sx, sy, makeRectangle(10, 10, 20, 20)));
 296         checkPoints(n, scale(sx, sy, rectPoints(10, 10, 20, 20)));
 297     }
 298 
 299     @Test public void testGroupedScaledGroupedTranslatedGroupedRotatedRectangle() {
 300         float sx = 1.3f;
 301         float sy = 1.1f;
 302         float tx = 35.7f;
 303         float ty = 93.1f;
 304         float rot = 25;
 305         Node n = group(scale(sx, sy,
 306                     group(translate(tx, ty,
 307                         group(rotate(rot,
 308                             makeRectangle(10, 10, 20, 20)))))));
 309         checkPoints(n, scale(sx, sy,
 310                            translate(tx, ty,
 311                                rotate(rot,
 312                                    rectPoints(10, 10, 20, 20)))));
 313     }
 314 }