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 }