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 }