1 /* 2 * Copyright (c) 2012, 2014, 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 javafx.scene.canvas; 27 28 import javafx.beans.property.DoubleProperty; 29 import javafx.beans.property.DoublePropertyBase; 30 import javafx.geometry.NodeOrientation; 31 import javafx.scene.Node; 32 import com.sun.javafx.geom.BaseBounds; 33 import com.sun.javafx.geom.RectBounds; 34 import com.sun.javafx.geom.transform.BaseTransform; 35 import com.sun.javafx.jmx.MXNodeAlgorithm; 36 import com.sun.javafx.jmx.MXNodeAlgorithmContext; 37 import com.sun.javafx.scene.DirtyBits; 38 import com.sun.javafx.scene.NodeHelper; 39 import com.sun.javafx.scene.canvas.CanvasHelper; 40 import com.sun.javafx.sg.prism.GrowableDataBuffer; 41 import com.sun.javafx.sg.prism.NGCanvas; 42 import com.sun.javafx.sg.prism.NGNode; 43 44 /** 45 * {@code Canvas} is an image that can be drawn on using a set of graphics 46 * commands provided by a {@code GraphicsContext}. 47 * 48 * <p> 49 * A {@code Canvas} node is constructed with a width and height that specifies the size 50 * of the image into which the canvas drawing commands are rendered. All drawing 51 * operations are clipped to the bounds of that image. 52 * 53 * <p>Example:</p> 54 * 55 * <p> 56 * <pre> 57 import javafx.scene.*; 58 import javafx.scene.paint.*; 59 import javafx.scene.canvas.*; 60 61 Group root = new Group(); 62 Scene s = new Scene(root, 300, 300, Color.BLACK); 63 64 final Canvas canvas = new Canvas(250,250); 65 GraphicsContext gc = canvas.getGraphicsContext2D(); 66 67 gc.setFill(Color.BLUE); 68 gc.fillRect(75,75,100,100); 69 70 root.getChildren().add(canvas); 71 * </pre> 72 * </p> 73 * 74 * @since JavaFX 2.2 75 */ 76 public class Canvas extends Node { 77 static { 78 CanvasHelper.setCanvasAccessor(new CanvasHelper.CanvasAccessor() { 79 @Override 80 public NGNode doCreatePeer(Node node) { 81 return ((Canvas) node).doCreatePeer(); 82 } 83 84 @Override 85 public void doUpdatePeer(Node node) { 86 ((Canvas) node).doUpdatePeer(); 87 } 88 89 @Override 90 public BaseBounds doComputeGeomBounds(Node node, 91 BaseBounds bounds, BaseTransform tx) { 92 return ((Canvas) node).doComputeGeomBounds(bounds, tx); 93 } 94 95 @Override 96 public boolean doComputeContains(Node node, double localX, double localY) { 97 return ((Canvas) node).doComputeContains(localX, localY); 98 } 99 100 @Override 101 public Object doProcessMXNode(Node node, MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) { 102 return ((Canvas) node).doProcessMXNode(alg, ctx); 103 } 104 }); 105 } 106 static final int DEFAULT_VAL_BUF_SIZE = 1024; 107 static final int DEFAULT_OBJ_BUF_SIZE = 32; 108 private static final int SIZE_HISTORY = 5; 109 110 private GrowableDataBuffer current; 111 private boolean rendererBehind; 112 private int recentvalsizes[]; 113 private int recentobjsizes[]; 114 private int lastsizeindex; 115 116 private GraphicsContext theContext; 117 118 { 119 // To initialize the class helper at the begining each constructor of this class 120 CanvasHelper.initHelper(this); 121 } 122 123 /** 124 * Creates an empty instance of Canvas. 125 */ 126 public Canvas() { 127 this(0, 0); 128 } 129 130 /** 131 * Creates a new instance of Canvas with the given size. 132 * 133 * @param width width of the canvas 134 * @param height height of the canvas 135 */ 136 public Canvas(double width, double height) { 137 this.recentvalsizes = new int[SIZE_HISTORY]; 138 this.recentobjsizes = new int[SIZE_HISTORY]; 139 setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); 140 setWidth(width); 141 setHeight(height); 142 } 143 144 private static int max(int sizes[], int defsize) { 145 for (int s : sizes) { 146 if (defsize < s) defsize = s; 147 } 148 return defsize; 149 } 150 151 GrowableDataBuffer getBuffer() { 152 NodeHelper.markDirty(this, DirtyBits.NODE_CONTENTS); 153 NodeHelper.markDirty(this, DirtyBits.NODE_FORCE_SYNC); 154 if (current == null) { 155 int vsize = max(recentvalsizes, DEFAULT_VAL_BUF_SIZE); 156 int osize = max(recentobjsizes, DEFAULT_OBJ_BUF_SIZE); 157 current = GrowableDataBuffer.getBuffer(vsize, osize); 158 theContext.updateDimensions(); 159 } 160 return current; 161 } 162 163 boolean isRendererFallingBehind() { 164 return rendererBehind; 165 } 166 167 /** 168 * returns the {@code GraphicsContext} associated with this {@code Canvas}. 169 */ 170 public GraphicsContext getGraphicsContext2D() { 171 if (theContext == null) { 172 theContext = new GraphicsContext(this); 173 } 174 return theContext; 175 } 176 177 /** 178 * Defines the width of the canvas. 179 * 180 * @profile common 181 * @defaultvalue 0.0 182 */ 183 private DoubleProperty width; 184 185 public final void setWidth(double value) { 186 widthProperty().set(value); 187 } 188 189 public final double getWidth() { 190 return width == null ? 0.0 : width.get(); 191 } 192 193 public final DoubleProperty widthProperty() { 194 if (width == null) { 195 width = new DoublePropertyBase() { 196 197 @Override 198 public void invalidated() { 199 NodeHelper.markDirty(Canvas.this, DirtyBits.NODE_GEOMETRY); 200 NodeHelper.geomChanged(Canvas.this); 201 if (theContext != null) { 202 theContext.updateDimensions(); 203 } 204 } 205 206 @Override 207 public Object getBean() { 208 return Canvas.this; 209 } 210 211 @Override 212 public String getName() { 213 return "width"; 214 } 215 }; 216 } 217 return width; 218 } 219 220 /** 221 * Defines the height of the canvas. 222 * 223 * @profile common 224 * @defaultvalue 0.0 225 */ 226 private DoubleProperty height; 227 228 public final void setHeight(double value) { 229 heightProperty().set(value); 230 } 231 232 public final double getHeight() { 233 return height == null ? 0.0 : height.get(); 234 } 235 236 public final DoubleProperty heightProperty() { 237 if (height == null) { 238 height = new DoublePropertyBase() { 239 240 @Override 241 public void invalidated() { 242 NodeHelper.markDirty(Canvas.this, DirtyBits.NODE_GEOMETRY); 243 NodeHelper.geomChanged(Canvas.this); 244 if (theContext != null) { 245 theContext.updateDimensions(); 246 } 247 } 248 249 @Override 250 public Object getBean() { 251 return Canvas.this; 252 } 253 254 @Override 255 public String getName() { 256 return "height"; 257 } 258 }; 259 } 260 return height; 261 } 262 263 private NGNode doCreatePeer() { 264 return new NGCanvas(); 265 } 266 267 /* 268 * Note: This method MUST only be called via its accessor method. 269 */ 270 private void doUpdatePeer() { 271 if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) { 272 NGCanvas peer = NodeHelper.getPeer(this); 273 peer.updateBounds((float)getWidth(), 274 (float)getHeight()); 275 } 276 if (NodeHelper.isDirty(this, DirtyBits.NODE_CONTENTS)) { 277 NGCanvas peer = NodeHelper.getPeer(this); 278 if (current != null && !current.isEmpty()) { 279 if (--lastsizeindex < 0) { 280 lastsizeindex = SIZE_HISTORY - 1; 281 } 282 recentvalsizes[lastsizeindex] = current.writeValuePosition(); 283 recentobjsizes[lastsizeindex] = current.writeObjectPosition(); 284 rendererBehind = peer.updateRendering(current); 285 current = null; 286 } 287 } 288 } 289 290 /* 291 * Note: This method MUST only be called via its accessor method. 292 */ 293 private boolean doComputeContains(double localX, double localY) { 294 double w = getWidth(); 295 double h = getHeight(); 296 return (w > 0 && h > 0 && 297 localX >= 0 && localY >= 0 && 298 localX < w && localY < h); 299 } 300 301 /* 302 * Note: This method MUST only be called via its accessor method. 303 */ 304 private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { 305 bounds = new RectBounds(0f, 0f, (float) getWidth(), (float) getHeight()); 306 bounds = tx.transform(bounds, bounds); 307 return bounds; 308 } 309 310 /* 311 * Note: This method MUST only be called via its accessor method. 312 */ 313 private Object doProcessMXNode(MXNodeAlgorithm alg, 314 MXNodeAlgorithmContext ctx) { 315 return alg.processLeafNode(this, ctx); 316 } 317 }