1 /*
   2  * Copyright (c) 2012, 2017, 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 }