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 }