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