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