1 /*
   2  * Copyright (c) 2012, 2016, 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 com.sun.javafx.geom.Arc2D;
  29 import com.sun.javafx.geom.IllegalPathStateException;
  30 import com.sun.javafx.geom.Path2D;
  31 import com.sun.javafx.geom.PathIterator;
  32 import com.sun.javafx.geom.transform.Affine2D;
  33 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  34 import com.sun.javafx.image.*;
  35 import com.sun.javafx.image.impl.ByteBgraPre;
  36 import com.sun.javafx.sg.prism.GrowableDataBuffer;
  37 import com.sun.javafx.sg.prism.NGCanvas;
  38 import com.sun.javafx.tk.Toolkit;
  39 import com.sun.scenario.effect.EffectHelper;
  40 import javafx.geometry.NodeOrientation;
  41 import javafx.geometry.VPos;
  42 import javafx.scene.effect.Blend;
  43 import javafx.scene.effect.BlendMode;
  44 import javafx.scene.effect.Effect;
  45 import javafx.scene.image.Image;
  46 import javafx.scene.image.PixelFormat;
  47 import javafx.scene.image.PixelReader;
  48 import javafx.scene.image.PixelWriter;
  49 import javafx.scene.paint.Color;
  50 import javafx.scene.paint.Paint;
  51 import javafx.scene.shape.ArcType;
  52 import javafx.scene.shape.FillRule;
  53 import javafx.scene.shape.StrokeLineCap;
  54 import javafx.scene.shape.StrokeLineJoin;
  55 import javafx.scene.text.Font;
  56 import javafx.scene.text.TextAlignment;
  57 import javafx.scene.transform.Affine;
  58 
  59 import java.nio.Buffer;
  60 import java.nio.ByteBuffer;
  61 import java.nio.IntBuffer;
  62 import java.util.Arrays;
  63 import java.util.LinkedList;
  64 import javafx.scene.text.FontSmoothingType;
  65 
  66 /**
  67  * This class is used to issue draw calls to a {@code Canvas} using a buffer.
  68  * <p>
  69  * Each call pushes the necessary parameters onto the buffer
  70  * where they will be later rendered onto the image of the {@code Canvas} node
  71  * by the rendering thread at the end of a pulse.
  72  * <p>
  73  * A {@code Canvas} only contains one {@code GraphicsContext}, and only one buffer.
  74  * If it is not attached to any scene, then it can be modified by any thread,
  75  * as long as it is only used from one thread at a time. Once a {@code Canvas}
  76  * node is attached to a scene, it must be modified on the JavaFX Application
  77  * Thread.
  78  * <p>
  79  * Calling any method on the {@code GraphicsContext} is considered modifying
  80  * its corresponding {@code Canvas} and is subject to the same threading
  81  * rules.
  82  * <p>
  83  * A {@code GraphicsContext} also manages a stack of state objects that can
  84  * be saved or restored at anytime.
  85  * <p>
  86  * The {@code GraphicsContext} maintains the following rendering attributes
  87  * which affect various subsets of the rendering methods:
  88  * <table class="overviewSummary" style="width:80%; margin-left:auto; margin-right:auto">
  89  * <tr>
  90  * <th class="colLast" style="width:15%">Attribute</th>
  91  * <th class="colLast" style="width:10%; text-align:center">Save/Restore?</th>
  92  * <th class="colLast" style="width:10%; text-align:center">Default value</th>
  93  * <th class="colLast">Description</th>
  94  * </tr>
  95  *
  96  * <tr><th colspan="3"><a name="comm-attr"><p align="center">Common Rendering Attributes</p></a></th></tr>
  97  * <tr class="rowColor">
  98  * <td class="colLast" style="width:15%">{@link #clip() Clip}</td>
  99  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 100  * <td class="colLast" style="width:10%; text-align:center">No clipping</td>
 101  * <td class="colLast">
 102  * An anti-aliased intersection of various clip paths to which rendering
 103  * is restricted.
 104  * </td></tr>
 105  * <tr class="altColor">
 106  * <td class="colLast" style="width:15%">{@link #setGlobalAlpha(double) Global Alpha}</td>
 107  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 108  * <td class="colLast" style="width:10%; text-align:center">{@code 1.0}</td>
 109  * <td class="colLast">
 110  * An opacity value that controls the visibility or fading of each rendering
 111  * operation.
 112  * </td></tr>
 113  * <tr class="rowColor">
 114  * <td class="colLast" style="width:15%">{@link #setGlobalBlendMode(javafx.scene.effect.BlendMode) Global Blend Mode}</td>
 115  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 116  * <td class="colLast" style="width:10%; text-align:center">{@link BlendMode#SRC_OVER SRC_OVER}</td>
 117  * <td class="colLast">
 118  * A {@link BlendMode} enum value that controls how pixels from each rendering
 119  * operation are composited into the existing image.
 120  * </td></tr>
 121  * <tr class="altColor">
 122  * <td class="colLast" style="width:15%">{@link #setTransform(javafx.scene.transform.Affine) Transform}</td>
 123  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 124  * <td class="colLast" style="width:10%; text-align:center">{@code Identity}</td>
 125  * <td class="colLast">
 126  * A 3x2 2D affine transformation matrix that controls how coordinates are
 127  * mapped onto the logical pixels of the canvas image.
 128  * </td></tr>
 129  * <tr class="altColor">
 130  * <td class="colLast" style="width:15%">{@link #setEffect(javafx.scene.effect.Effect) Effect}</td>
 131  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 132  * <td class="colLast" style="width:10%; text-align:center">{@code null}</td>
 133  * <td class="colLast">
 134  * An {@link Effect} applied individually to each rendering operation.
 135  * </td></tr>
 136  *
 137  * <tr><th colspan="3"><a name="fill-attr"><p align="center">Fill Attributes</p></a></th></tr>
 138  * <tr class="rowColor">
 139  * <td class="colLast" style="width:15%">{@link #setFill(javafx.scene.paint.Paint) Fill Paint}</td>
 140  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 141  * <td class="colLast" style="width:10%; text-align:center">{@link Color#BLACK BLACK}</td>
 142  * <td class="colLast">
 143  * The {@link Paint} to be applied to the interior of shapes in a
 144  * fill operation.
 145  * </td></tr>
 146  *
 147  * <tr><th colspan="3"><a name="strk-attr"><p align="center">Stroke Attributes</p></a></th></tr>
 148  * <tr class="rowColor">
 149  * <td class="colLast" style="width:15%">{@link #setStroke(javafx.scene.paint.Paint) Stroke Paint}</td>
 150  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 151  * <td class="colLast" style="width:10%; text-align:center">{@link Color#BLACK BLACK}</td>
 152  * <td class="colLast">
 153  * The {@link Paint} to be applied to the boundary of shapes in a
 154  * stroke operation.
 155  * </td></tr>
 156  * <tr class="altColor">
 157  * <td class="colLast" style="width:15%">{@link #setLineWidth(double) Line Width}</td>
 158  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 159  * <td class="colLast" style="width:10%; text-align:center">{@code 1.0}</td>
 160  * <td class="colLast">
 161  * The width of the stroke applied to the boundary of shapes in a
 162  * stroke operation.
 163  * </td></tr>
 164  * <tr class="rowColor">
 165  * <td class="colLast" style="width:15%">{@link #setLineCap(javafx.scene.shape.StrokeLineCap) Line Cap}</td>
 166  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 167  * <td class="colLast" style="width:10%; text-align:center">{@link StrokeLineCap#SQUARE SQUARE}</td>
 168  * <td class="colLast">
 169  * The style of the end caps applied to the beginnings and ends of each
 170  * dash and/or subpath in a stroke operation.
 171  * </td></tr>
 172  * <tr class="altColor">
 173  * <td class="colLast" style="width:15%">{@link #setLineJoin(javafx.scene.shape.StrokeLineJoin) Line Join}</td>
 174  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 175  * <td class="colLast" style="width:10%; text-align:center">{@link StrokeLineJoin#MITER MITER}</td>
 176  * <td class="colLast">
 177  * The style of the joins applied between individual segments in the boundary
 178  * paths of shapes in a stroke operation.
 179  * </td></tr>
 180  * <tr class="rowColor">
 181  * <td class="colLast" style="width:15%">{@link #setMiterLimit(double) Miter Limit}</td>
 182  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 183  * <td class="colLast" style="width:10%; text-align:center">{@code 10.0}</td>
 184  * <td class="colLast">
 185  * The ratio limit of how far a {@link StrokeLineJoin#MITER MITER} line join
 186  * may extend in the direction of a sharp corner between segments in the
 187  * boundary path of a shape, relative to the line width, before it is truncated
 188  * to a {@link StrokeLineJoin#BEVEL BEVEL} join in a stroke operation.
 189  * </td></tr>
 190  * <tr class="altColor">
 191  * <td class="colLast" style="width:15%">{@link #setLineDashes(double...) Dashes}</td>
 192  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 193  * <td class="colLast" style="width:10%; text-align:center">{@code null}</td>
 194  * <td class="colLast">
 195  * The array of dash lengths to be applied to the segments in the boundary
 196  * of shapes in a stroke operation.
 197  * </td></tr>
 198  * <tr class="rowColor">
 199  * <td class="colLast" style="width:15%">{@link #setLineDashOffset(double) Dash Offset}</td>
 200  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 201  * <td class="colLast" style="width:10%; text-align:center">{@code 0.0}</td>
 202  * <td class="colLast">
 203  * The distance offset into the array of dash lengths at which to start the
 204  * dashing of the segments in the boundary of shapes in a stroke operation.
 205  * </td></tr>
 206  *
 207  * <tr><th colspan="3"><a name="text-attr"><p align="center">Text Attributes</p></a></th></tr>
 208  * <tr class="rowColor">
 209  * <td class="colLast" style="width:15%">{@link #setFont(javafx.scene.text.Font) Font}</td>
 210  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 211  * <td class="colLast" style="width:10%; text-align:center">{@link Font#getDefault() Default Font}</td>
 212  * <td class="colLast">
 213  * The font used for all fill and stroke text operations.
 214  * </td></tr>
 215  * <tr class="altColor">
 216  * <td class="colLast" style="width:15%">{@link #setTextAlign(javafx.scene.text.TextAlignment) Text Align}</td>
 217  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 218  * <td class="colLast" style="width:10%; text-align:center">{@link TextAlignment#LEFT LEFT}</td>
 219  * <td class="colLast">
 220  * The horizontal alignment of text with respect to the {@code X} coordinate
 221  * specified in the text operation.
 222  * </td></tr>
 223  * <tr class="rowColor">
 224  * <td class="colLast" style="width:15%">{@link #setTextBaseline(javafx.geometry.VPos) Text Baseline}</td>
 225  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 226  * <td class="colLast" style="width:10%; text-align:center">{@link VPos#BASELINE BASELINE}</td>
 227  * <td class="colLast">
 228  * The vertical position of the text relative to the {@code Y} coordinate
 229  * specified in the text operation.
 230  * </td></tr>
 231  * <tr class="altColor">
 232  * <td class="colLast" style="width:15%">{@link #setFontSmoothingType(javafx.scene.text.FontSmoothingType) Font Smoothing}</td>
 233  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 234  * <td class="colLast" style="width:10%; text-align:center">{@link FontSmoothingType#GRAY GRAY}</td>
 235  * <td class="colLast">
 236  * The type of smoothing (antialiasing) applied to the glyphs in the font
 237  * for all fill text operations.
 238  * </td></tr>
 239  *
 240  * <tr><th colspan="3"><a name="path-attr"><p align="center">Path Attributes</p></a></th></tr>
 241  * <tr class="rowColor">
 242  * <td class="colLast" style="width:15%">{@link #beginPath() Current Path}</td>
 243  * <td class="colLast" style="width:10%; text-align:center; color:#c00">No</td>
 244  * <td class="colLast" style="width:10%; text-align:center">Empty path</td>
 245  * <td class="colLast">
 246  * The path constructed using various path construction methods to be used
 247  * in various path filling, stroking, or clipping operations.
 248  * </td></tr>
 249  * <tr class="altColor">
 250  * <td class="colLast" style="width:15%">{@link #setFillRule(javafx.scene.shape.FillRule) Fill Rule}</td>
 251  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 252  * <td class="colLast" style="width:10%; text-align:center">{@link FillRule#NON_ZERO NON_ZERO}</td>
 253  * <td class="colLast">
 254  * The method used to determine the interior of paths for a path fill or
 255  * clip operation.
 256  * </td></tr>
 257  * </table>
 258  *
 259  * <p>
 260  * <a name="attr-ops-table">
 261  * The various rendering methods on the {@code GraphicsContext} use the
 262  * following sets of rendering attributes:
 263  * </a>
 264  * <table class="overviewSummary" style="width:80%; margin-left:auto; margin-right:auto">
 265  * <tr>
 266  * <th class="colLast" style="width:25%">Method</th>
 267  * <th class="colLast" style="width:15%; text-align:center"><a href="#comm-attr">Common Rendering Attributes</a></th>
 268  * <th class="colLast" style="width:15%; text-align:center"><a href="#fill-attr">Fill Attributes</a></th>
 269  * <th class="colLast" style="width:15%; text-align:center"><a href="#strk-attr">Stroke Attributes</a></th>
 270  * <th class="colLast" style="width:15%; text-align:center"><a href="#text-attr">Text Attributes</a></th>
 271  * <th class="colLast" style="width:15%; text-align:center"><a href="#path-attr">Path Attributes</a></th>
 272  * </tr>
 273  *
 274  * <tr><th colspan="1"><p align="center">Basic Shape Rendering</p></th></tr>
 275  * <tr class="rowColor">
 276  * <td class="colLast" style="width:25%">
 277  * <a name="fill-basic-ops">
 278  * {@link #fillRect(double, double, double, double) fillRect()},
 279  * {@link #fillRoundRect(double, double, double, double, double, double) fillRoundRect()},
 280  * {@link #fillOval(double, double, double, double) fillOval()},
 281  * {@link #fillArc(double, double, double, double, double, double, javafx.scene.shape.ArcType) fillArc()}
 282  * </a>
 283  * </td>
 284  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 285  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 286  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 287  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 288  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 289  * </tr>
 290  * <tr class="altColor">
 291  * <td class="colLast" style="width:25%">
 292  * <a name="strk-basic-ops">
 293  * {@link #strokeLine(double, double, double, double) strokeLine()},
 294  * {@link #strokeRect(double, double, double, double) strokeRect()},
 295  * {@link #strokeRoundRect(double, double, double, double, double, double) strokeRoundRect()},
 296  * {@link #strokeOval(double, double, double, double) strokeOval()},
 297  * {@link #strokeArc(double, double, double, double, double, double, javafx.scene.shape.ArcType) strokeArc()}
 298  * </a>
 299  * </td>
 300  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 301  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 302  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 303  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 304  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 305  * </tr>
 306  * <tr class="rowColor">
 307  * <td class="colLast" style="width:25%">
 308  * <a name="fill-basic-ops">
 309  * {@link #clearRect(double, double, double, double) clearRect()}
 310  * </a>
 311  * </td>
 312  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#base-fn-1">[1]</a></td>
 313  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 314  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 315  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 316  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 317  * </tr>
 318  * <tr class="altColor">
 319  * <td class="colLast" style="width:25%">
 320  * <a name="strk-basic-ops">
 321  * {@link #fillPolygon(double[], double[], int) fillPolygon()}
 322  * </a>
 323  * </td>
 324  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 325  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 326  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 327  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 328  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#base-fn-2">[2]</a></td>
 329  * </tr>
 330  * <tr class="rowColor">
 331  * <td class="colLast" style="width:25%">
 332  * <a name="strk-basic-ops">
 333  * {@link #strokePolygon(double[], double[], int) strokePolygon()},
 334  * {@link #strokePolyline(double[], double[], int) strokePolyline()}
 335  * </a>
 336  * </td>
 337  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 338  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 339  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 340  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 341  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 342  * </tr>
 343  * <tr><td colspan="6"><p align="center">
 344  * <a name="base-fn-1">[1]</a> Only the Transform, Clip, and Effect apply to clearRect()<br>
 345  * <a name="base-fn-2">[2]</a> Only the Fill Rule applies to fillPolygon(), the current path is left unchanged
 346  * </p></td></tr>
 347  *
 348  * <tr><th colspan="1"><p align="center">Text Rendering</p></th></tr>
 349  * <tr class="rowColor">
 350  * <td class="colLast" style="width:25%">
 351  * <a name="fill-text-ops">
 352  * {@link #fillText(java.lang.String, double, double) fillText()},
 353  * {@link #fillText(java.lang.String, double, double, double) fillText(with maxWidth)}
 354  * </a>
 355  * </td>
 356  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 357  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 358  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 359  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#text-fn-3">[3]</a></td>
 360  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 361  * </tr>
 362  * <tr class="altColor">
 363  * <td class="colLast" style="width:25%">
 364  * <a name="strk-text-ops">
 365  * {@link #strokeText(java.lang.String, double, double) strokeText()},
 366  * {@link #strokeText(java.lang.String, double, double, double) strokeText(with maxWidth)}
 367  * </a>
 368  * </td>
 369  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 370  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 371  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 372  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#text-fn-3">[3]</a></td>
 373  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 374  * </tr>
 375  * <tr><td colspan="6"><p align="center">
 376  * <a name="text-fn-3">[3]</a> The Font Smoothing attribute only applies to filled text
 377  * </p></td></tr>
 378  *
 379  * <tr><th colspan="1"><p align="center">Path Rendering</p></th></tr>
 380  * <tr class="rowColor">
 381  * <td class="colLast" style="width:25%">
 382  * {@link #beginPath() beginPath()},
 383  * {@link #moveTo(double, double) moveTo()},
 384  * {@link #lineTo(double, double) lineTo()},
 385  * {@link #quadraticCurveTo(double, double, double, double) quadraticCurveTo()},
 386  * {@link #bezierCurveTo(double, double, double, double, double, double) bezierCurveTo()},
 387  * {@link #arc(double, double, double, double, double, double) arc()},
 388  * {@link #arcTo(double, double, double, double, double) arcTo()},
 389  * {@link #appendSVGPath(java.lang.String) appendSVGPath()},
 390  * {@link #closePath() closePath()},
 391  * {@link #rect(double, double, double, double) rect()}
 392  * </td>
 393  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-4">[4]</a></td>
 394  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 395  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 396  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 397  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 398  * </tr>
 399  * <tr class="altColor">
 400  * <td class="colLast" style="width:25%">
 401  * <a name="fill-path-ops">
 402  * {@link #fill() fill()}
 403  * </a>
 404  * </td>
 405  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-4">[4]</a></td>
 406  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 407  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 408  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 409  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 410  * </tr>
 411  * <tr class="rowColor">
 412  * <td class="colLast" style="width:25%">
 413  * <a name="strk-path-ops">
 414  * {@link #stroke() stroke()}
 415  * </a>
 416  * </td>
 417  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-4">[4]</a></td>
 418  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 419  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 420  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 421  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-5">[5]</a></td>
 422  * </tr>
 423  * <tr class="altColor">
 424  * <td class="colLast" style="width:25%">
 425  * <a name="clip-path-ops">
 426  * {@link #clip() clip()}
 427  * </a>
 428  * </td>
 429  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 430  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 431  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 432  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 433  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 434  * </tr>
 435  * <tr><td colspan="6"><p align="center">
 436  * <a name="path-fn-4">[4]</a> Transform applied only during path construction<br>
 437  * <a name="path-fn-5">[5]</a> Fill Rule only used for fill() and clip()
 438  * </p></td></tr>
 439  *
 440  * <tr><th colspan="1"><p align="center">Image Rendering</p></th></tr>
 441  * <tr class="rowColor">
 442  * <td class="colLast" style="width:25%">
 443  * <a name="draw-img-ops">
 444  * {@link #drawImage(javafx.scene.image.Image, double, double) drawImage(all forms)}
 445  * </a>
 446  * </td>
 447  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 448  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 449  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 450  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 451  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 452  * </tr>
 453  *
 454  * <tr><th colspan="1"><p align="center">Miscellaneous</p></th></tr>
 455  * <tr class="rowColor">
 456  * <td class="colLast" style="width:25%">
 457  * {@link #applyEffect(javafx.scene.effect.Effect) applyEffect()},
 458  * {@link #getPixelWriter() PixelWriter methods}
 459  * </td>
 460  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 461  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 462  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 463  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 464  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 465  * </tr>
 466  * </table>
 467  *
 468  * <p>Example:</p>
 469  *
 470  * <p>
 471  * <pre>
 472 import javafx.scene.*;
 473 import javafx.scene.paint.*;
 474 import javafx.scene.canvas.*;
 475 
 476 Group root = new Group();
 477 Scene s = new Scene(root, 300, 300, Color.BLACK);
 478 
 479 final Canvas canvas = new Canvas(250,250);
 480 GraphicsContext gc = canvas.getGraphicsContext2D();
 481 
 482 gc.setFill(Color.BLUE);
 483 gc.fillRect(75,75,100,100);
 484 
 485 root.getChildren().add(canvas);
 486  * </pre>
 487  * </p>
 488  *
 489  * @since JavaFX 2.2
 490  */
 491 public final class GraphicsContext {
 492     Canvas theCanvas;
 493     Path2D path;
 494     boolean pathDirty;
 495 
 496     State curState;
 497     LinkedList<State> stateStack;
 498     LinkedList<Path2D> clipStack;
 499 
 500     GraphicsContext(Canvas theCanvas) {
 501         this.theCanvas = theCanvas;
 502         this.path = new Path2D();
 503         pathDirty = true;
 504 
 505         this.curState = new State();
 506         this.stateStack = new LinkedList<State>();
 507         this.clipStack = new LinkedList<Path2D>();
 508     }
 509 
 510     static class State {
 511         double globalAlpha;
 512         BlendMode blendop;
 513         Affine2D transform;
 514         Paint fill;
 515         Paint stroke;
 516         double linewidth;
 517         StrokeLineCap linecap;
 518         StrokeLineJoin linejoin;
 519         double miterlimit;
 520         double dashes[];
 521         double dashOffset;
 522         int numClipPaths;
 523         Font font;
 524         FontSmoothingType fontsmoothing;
 525         TextAlignment textalign;
 526         VPos textbaseline;
 527         Effect effect;
 528         FillRule fillRule;
 529 
 530         State() {
 531             init();
 532         }
 533 
 534         final void init() {
 535             set(1.0, BlendMode.SRC_OVER,
 536                 new Affine2D(),
 537                 Color.BLACK, Color.BLACK,
 538                 1.0, StrokeLineCap.SQUARE, StrokeLineJoin.MITER, 10.0,
 539                 null, 0.0,
 540                 0,
 541                 Font.getDefault(), FontSmoothingType.GRAY,
 542                 TextAlignment.LEFT, VPos.BASELINE,
 543                 null, FillRule.NON_ZERO);
 544         }
 545 
 546         State(State copy) {
 547             set(copy.globalAlpha, copy.blendop,
 548                 new Affine2D(copy.transform),
 549                 copy.fill, copy.stroke,
 550                 copy.linewidth, copy.linecap, copy.linejoin, copy.miterlimit,
 551                 copy.dashes, copy.dashOffset,
 552                 copy.numClipPaths,
 553                 copy.font, copy.fontsmoothing, copy.textalign, copy.textbaseline,
 554                 copy.effect, copy.fillRule);
 555         }
 556 
 557         final void set(double globalAlpha, BlendMode blendop,
 558                        Affine2D transform, Paint fill, Paint stroke,
 559                        double linewidth, StrokeLineCap linecap,
 560                        StrokeLineJoin linejoin, double miterlimit,
 561                        double dashes[], double dashOffset,
 562                        int numClipPaths,
 563                        Font font, FontSmoothingType smoothing,
 564                        TextAlignment align, VPos baseline,
 565                        Effect effect, FillRule fillRule)
 566         {
 567             this.globalAlpha = globalAlpha;
 568             this.blendop = blendop;
 569             this.transform = transform;
 570             this.fill = fill;
 571             this.stroke = stroke;
 572             this.linewidth = linewidth;
 573             this.linecap = linecap;
 574             this.linejoin = linejoin;
 575             this.miterlimit = miterlimit;
 576             this.dashes = dashes;
 577             this.dashOffset = dashOffset;
 578             this.numClipPaths = numClipPaths;
 579             this.font = font;
 580             this.fontsmoothing = smoothing;
 581             this.textalign = align;
 582             this.textbaseline = baseline;
 583             this.effect = effect;
 584             this.fillRule = fillRule;
 585         }
 586 
 587         State copy() {
 588             return new State(this);
 589         }
 590 
 591         void restore(GraphicsContext ctx) {
 592             ctx.setGlobalAlpha(globalAlpha);
 593             ctx.setGlobalBlendMode(blendop);
 594             ctx.setTransform(transform.getMxx(), transform.getMyx(),
 595                              transform.getMxy(), transform.getMyy(),
 596                              transform.getMxt(), transform.getMyt());
 597             ctx.setFill(fill);
 598             ctx.setStroke(stroke);
 599             ctx.setLineWidth(linewidth);
 600             ctx.setLineCap(linecap);
 601             ctx.setLineJoin(linejoin);
 602             ctx.setMiterLimit(miterlimit);
 603             ctx.setLineDashes(dashes);
 604             ctx.setLineDashOffset(dashOffset);
 605             GrowableDataBuffer buf = ctx.getBuffer();
 606             while (ctx.curState.numClipPaths > numClipPaths) {
 607                 ctx.curState.numClipPaths--;
 608                 ctx.clipStack.removeLast();
 609                 buf.putByte(NGCanvas.POP_CLIP);
 610             }
 611             ctx.setFillRule(fillRule);
 612             ctx.setFont(font);
 613             ctx.setFontSmoothingType(fontsmoothing);
 614             ctx.setTextAlign(textalign);
 615             ctx.setTextBaseline(textbaseline);
 616             ctx.setEffect(effect);
 617         }
 618     }
 619 
 620     private GrowableDataBuffer getBuffer() {
 621         return theCanvas.getBuffer();
 622     }
 623 
 624     private float coords[] = new float[6];
 625     private static final byte pgtype[] = {
 626         NGCanvas.MOVETO,
 627         NGCanvas.LINETO,
 628         NGCanvas.QUADTO,
 629         NGCanvas.CUBICTO,
 630         NGCanvas.CLOSEPATH,
 631     };
 632     private static final int numsegs[] = { 2, 2, 4, 6, 0, };
 633 
 634     private void markPathDirty() {
 635         pathDirty = true;
 636     }
 637 
 638     private void writePath(byte command) {
 639         updateTransform();
 640         GrowableDataBuffer buf = getBuffer();
 641         if (pathDirty) {
 642             buf.putByte(NGCanvas.PATHSTART);
 643             PathIterator pi = path.getPathIterator(null);
 644             while (!pi.isDone()) {
 645                 int pitype = pi.currentSegment(coords);
 646                 buf.putByte(pgtype[pitype]);
 647                 for (int i = 0; i < numsegs[pitype]; i++) {
 648                     buf.putFloat(coords[i]);
 649                 }
 650                 pi.next();
 651             }
 652             buf.putByte(NGCanvas.PATHEND);
 653             pathDirty = false;
 654         }
 655         buf.putByte(command);
 656     }
 657 
 658     private void writePaint(Paint p, byte command) {
 659         GrowableDataBuffer buf = getBuffer();
 660         buf.putByte(command);
 661         buf.putObject(Toolkit.getPaintAccessor().getPlatformPaint(p));
 662     }
 663 
 664     private void writeArcType(ArcType closure) {
 665         byte type;
 666         switch (closure) {
 667             case OPEN:  type = NGCanvas.ARC_OPEN;  break;
 668             case CHORD: type = NGCanvas.ARC_CHORD; break;
 669             case ROUND: type = NGCanvas.ARC_PIE;   break;
 670             default: return;  // ignored for consistency with other attributes
 671         }
 672         writeParam(type, NGCanvas.ARC_TYPE);
 673     }
 674 
 675     private void writeRectParams(GrowableDataBuffer buf,
 676                                  double x, double y, double w, double h,
 677                                  byte command)
 678     {
 679         buf.putByte(command);
 680         buf.putFloat((float) x);
 681         buf.putFloat((float) y);
 682         buf.putFloat((float) w);
 683         buf.putFloat((float) h);
 684     }
 685 
 686     private void writeOp4(double x, double y, double w, double h, byte command) {
 687         updateTransform();
 688         writeRectParams(getBuffer(), x, y, w, h, command);
 689     }
 690 
 691     private void writeOp6(double x, double y, double w, double h,
 692                           double v1, double v2, byte command)
 693     {
 694         updateTransform();
 695         GrowableDataBuffer buf = getBuffer();
 696         buf.putByte(command);
 697         buf.putFloat((float) x);
 698         buf.putFloat((float) y);
 699         buf.putFloat((float) w);
 700         buf.putFloat((float) h);
 701         buf.putFloat((float) v1);
 702         buf.putFloat((float) v2);
 703     }
 704 
 705     private float polybuf[] = new float[512];
 706     private void flushPolyBuf(GrowableDataBuffer buf,
 707                               float polybuf[], int n, byte command)
 708     {
 709         curState.transform.transform(polybuf, 0, polybuf, 0, n/2);
 710         for (int i = 0; i < n; i += 2) {
 711             buf.putByte(command);
 712             buf.putFloat(polybuf[i]);
 713             buf.putFloat(polybuf[i+1]);
 714             command = NGCanvas.LINETO;
 715         }
 716     }
 717     private void writePoly(double xPoints[], double yPoints[], int nPoints,
 718                            boolean close, byte command)
 719     {
 720         if (xPoints == null || yPoints == null) return;
 721         GrowableDataBuffer buf = getBuffer();
 722         buf.putByte(NGCanvas.PATHSTART);
 723         int pos = 0;
 724         byte polycmd = NGCanvas.MOVETO;
 725         for (int i = 0; i < nPoints; i++) {
 726             if (pos >= polybuf.length) {
 727                 flushPolyBuf(buf, polybuf, pos, polycmd);
 728                 pos = 0;
 729                 polycmd = NGCanvas.LINETO;
 730             }
 731             polybuf[pos++] = (float) xPoints[i];
 732             polybuf[pos++] = (float) yPoints[i];
 733         }
 734         flushPolyBuf(buf, polybuf, pos, polycmd);
 735         if (close) {
 736             buf.putByte(NGCanvas.CLOSEPATH);
 737         }
 738         buf.putByte(NGCanvas.PATHEND);
 739         // Transform needs to be updated for rendering attributes even though
 740         // we have already trasnformed the points as we sent them.
 741         updateTransform();
 742         buf.putByte(command);
 743         // Now that we have changed the PG layer path, we need to mark our path dirty.
 744         markPathDirty();
 745     }
 746 
 747     private void writeImage(Image img,
 748                             double dx, double dy, double dw, double dh)
 749     {
 750         if (img == null || img.getProgress() < 1.0) return;
 751         Object platformImg = img.impl_getPlatformImage();
 752         if (platformImg == null) return;
 753         updateTransform();
 754         GrowableDataBuffer buf = getBuffer();
 755         writeRectParams(buf, dx, dy, dw, dh, NGCanvas.DRAW_IMAGE);
 756         buf.putObject(platformImg);
 757     }
 758 
 759     private void writeImage(Image img,
 760                             double dx, double dy, double dw, double dh,
 761                             double sx, double sy, double sw, double sh)
 762     {
 763         if (img == null || img.getProgress() < 1.0) return;
 764         Object platformImg = img.impl_getPlatformImage();
 765         if (platformImg == null) return;
 766         updateTransform();
 767         GrowableDataBuffer buf = getBuffer();
 768         writeRectParams(buf, dx, dy, dw, dh, NGCanvas.DRAW_SUBIMAGE);
 769         buf.putFloat((float) sx);
 770         buf.putFloat((float) sy);
 771         buf.putFloat((float) sw);
 772         buf.putFloat((float) sh);
 773         buf.putObject(platformImg);
 774     }
 775 
 776     private void writeText(String text, double x, double y, double maxWidth,
 777                            byte command)
 778     {
 779         if (text == null) return;
 780         updateTransform();
 781         GrowableDataBuffer buf = getBuffer();
 782         buf.putByte(command);
 783         buf.putFloat((float) x);
 784         buf.putFloat((float) y);
 785         buf.putFloat((float) maxWidth);
 786         buf.putBoolean(theCanvas.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
 787         buf.putObject(text);
 788     }
 789 
 790     void writeParam(double v, byte command) {
 791         GrowableDataBuffer buf = getBuffer();
 792         buf.putByte(command);
 793         buf.putFloat((float) v);
 794     }
 795 
 796     private void writeParam(byte v, byte command) {
 797         GrowableDataBuffer buf = getBuffer();
 798         buf.putByte(command);
 799         buf.putByte(v);
 800     }
 801 
 802     private boolean txdirty;
 803     private void updateTransform() {
 804         if (txdirty) {
 805             txdirty = false;
 806             GrowableDataBuffer buf = getBuffer();
 807             buf.putByte(NGCanvas.TRANSFORM);
 808             buf.putDouble(curState.transform.getMxx());
 809             buf.putDouble(curState.transform.getMxy());
 810             buf.putDouble(curState.transform.getMxt());
 811             buf.putDouble(curState.transform.getMyx());
 812             buf.putDouble(curState.transform.getMyy());
 813             buf.putDouble(curState.transform.getMyt());
 814         }
 815     }
 816 
 817     void updateDimensions() {
 818         GrowableDataBuffer buf = getBuffer();
 819         buf.putByte(NGCanvas.SET_DIMS);
 820         buf.putFloat((float) theCanvas.getWidth());
 821         buf.putFloat((float) theCanvas.getHeight());
 822     }
 823 
 824     private void reset() {
 825         GrowableDataBuffer buf = getBuffer();
 826         // Only reset if we have a significant amount of data to omit,
 827         // this prevents a common occurence of "setFill(bg); fillRect();"
 828         // at the start of a session from invoking a reset.
 829         // But, do a reset anyway if the rendering layer has been falling
 830         // behind because that lets the synchronization step throw out the
 831         // older buffers that have been backing up.
 832         if (buf.writeValuePosition() > Canvas.DEFAULT_VAL_BUF_SIZE ||
 833             theCanvas.isRendererFallingBehind())
 834         {
 835             buf.reset();
 836             buf.putByte(NGCanvas.RESET);
 837             updateDimensions();
 838             txdirty = true;
 839             pathDirty = true;
 840             State s = this.curState;
 841             int numClipPaths = this.curState.numClipPaths;
 842             this.curState = new State();
 843             for (int i = 0; i < numClipPaths; i++) {
 844                 Path2D clip = clipStack.get(i);
 845                 buf.putByte(NGCanvas.PUSH_CLIP);
 846                 buf.putObject(clip);
 847             }
 848             this.curState.numClipPaths = numClipPaths;
 849             s.restore(this);
 850         }
 851     }
 852 
 853     private void resetIfCovers(Paint p, double x, double y, double w, double h) {
 854         Affine2D tx = this.curState.transform;
 855         if (tx.isTranslateOrIdentity()) {
 856             x += tx.getMxt();
 857             y += tx.getMyt();
 858             if (x > 0 || y > 0 ||
 859                 (x+w) < theCanvas.getWidth() ||
 860                 (y+h) < theCanvas.getHeight())
 861             {
 862                 return;
 863             }
 864         } else {
 865 //          quad test for coverage...?
 866             return;
 867         }
 868         if (p != null) {
 869             if (this.curState.blendop != BlendMode.SRC_OVER) return;
 870             if (!p.isOpaque() || this.curState.globalAlpha < 1.0) return;
 871         }
 872         if (this.curState.numClipPaths > 0) return;
 873         if (this.curState.effect != null) return;
 874         reset();
 875     }
 876 
 877     /**
 878     * Gets the {@code Canvas} that the {@code GraphicsContext} is issuing draw
 879     * commands to. There is only ever one {@code Canvas} for a
 880     * {@code GraphicsContext}.
 881     *
 882     * @return Canvas the canvas that this {@code GraphicsContext} is issuing draw
 883     * commands to.
 884     */
 885     public Canvas getCanvas() {
 886         return theCanvas;
 887     }
 888 
 889     /**
 890      * Saves the following attributes onto a stack.
 891      * <ul>
 892      *     <li>Global Alpha</li>
 893      *     <li>Global Blend Operation</li>
 894      *     <li>Transform</li>
 895      *     <li>Fill Paint</li>
 896      *     <li>Stroke Paint</li>
 897      *     <li>Line Width</li>
 898      *     <li>Line Cap</li>
 899      *     <li>Line Join</li>
 900      *     <li>Miter Limit</li>
 901      *     <li>Clip</li>
 902      *     <li>Font</li>
 903      *     <li>Text Align</li>
 904      *     <li>Text Baseline</li>
 905      *     <li>Effect</li>
 906      *     <li>Fill Rule</li>
 907      * </ul>
 908      * This method does NOT alter the current state in any way. Also, note that
 909      * the current path is not saved.
 910      */
 911     public void save() {
 912         stateStack.push(curState.copy());
 913     }
 914 
 915     /**
 916      * Pops the state off of the stack, setting the following attributes to their
 917      * value at the time when that state was pushed onto the stack. If the stack
 918      * is empty then nothing is changed.
 919      *
 920      * <ul>
 921      *     <li>Global Alpha</li>
 922      *     <li>Global Blend Operation</li>
 923      *     <li>Transform</li>
 924      *     <li>Fill Paint</li>
 925      *     <li>Stroke Paint</li>
 926      *     <li>Line Width</li>
 927      *     <li>Line Cap</li>
 928      *     <li>Line Join</li>
 929      *     <li>Miter Limit</li>
 930      *     <li>Clip</li>
 931      *     <li>Font</li>
 932      *     <li>Text Align</li>
 933      *     <li>Text Baseline</li>
 934      *     <li>Effect</li>
 935      *     <li>Fill Rule</li>
 936      * </ul>
 937      * Note that the current path is not restored.
 938      */
 939     public void restore() {
 940         if (!stateStack.isEmpty()) {
 941             State savedState = stateStack.pop();
 942             savedState.restore(this);
 943             txdirty = true;
 944         }
 945     }
 946 
 947     /**
 948      * Translates the current transform by x, y.
 949      * @param x value to translate along the x axis.
 950      * @param y value to translate along the y axis.
 951      */
 952     public void translate(double x, double y) {
 953         curState.transform.translate(x, y);
 954         txdirty = true;
 955     }
 956 
 957     /**
 958      * Scales the current transform by x, y.
 959      * @param x value to scale in the x axis.
 960      * @param y value to scale in the y axis.
 961      */
 962     public void scale(double x, double y) {
 963         curState.transform.scale(x, y);
 964         txdirty = true;
 965     }
 966 
 967     /**
 968      * Rotates the current transform in degrees.
 969      * @param degrees value in degrees to rotate the current transform.
 970      */
 971     public void rotate(double degrees) {
 972         curState.transform.rotate(Math.toRadians(degrees));
 973         txdirty = true;
 974     }
 975 
 976     /**
 977      * Concatenates the input with the current transform.
 978      *
 979      * @param mxx - the X coordinate scaling element of the 3x4 matrix
 980      * @param myx - the Y coordinate shearing element of the 3x4 matrix
 981      * @param mxy - the X coordinate shearing element of the 3x4 matrix
 982      * @param myy - the Y coordinate scaling element of the 3x4 matrix
 983      * @param mxt - the X coordinate translation element of the 3x4 matrix
 984      * @param myt - the Y coordinate translation element of the 3x4 matrix
 985      */
 986     public void transform(double mxx, double myx,
 987                           double mxy, double myy,
 988                           double mxt, double myt)
 989     {
 990         curState.transform.concatenate(mxx, mxy, mxt,
 991                                        myx, myy, myt);
 992         txdirty = true;
 993     }
 994 
 995     /**
 996      * Concatenates the input with the current transform. Only 2D transforms are
 997      * supported. The only values used are the X and Y scaling, translation, and
 998      * shearing components of a transform. A {@code null} value is treated as identity.
 999      *
1000      * @param xform The affine to be concatenated with the current transform or null.
1001      */
1002     public void transform(Affine xform) {
1003         if (xform == null) return;
1004         curState.transform.concatenate(xform.getMxx(), xform.getMxy(), xform.getTx(),
1005                                        xform.getMyx(), xform.getMyy(), xform.getTy());
1006         txdirty = true;
1007     }
1008 
1009     /**
1010      * Sets the current transform.
1011      * @param mxx - the X coordinate scaling element of the 3x4 matrix
1012      * @param myx - the Y coordinate shearing element of the 3x4 matrix
1013      * @param mxy - the X coordinate shearing element of the 3x4 matrix
1014      * @param myy - the Y coordinate scaling element of the 3x4 matrix
1015      * @param mxt - the X coordinate translation element of the 3x4 matrix
1016      * @param myt - the Y coordinate translation element of the 3x4 matrix
1017      */
1018     public void setTransform(double mxx, double myx,
1019                              double mxy, double myy,
1020                              double mxt, double myt)
1021     {
1022         curState.transform.setTransform(mxx, myx,
1023                                         mxy, myy,
1024                                         mxt, myt);
1025         txdirty = true;
1026     }
1027 
1028     /**
1029      * Sets the current transform. Only 2D transforms are supported. The only
1030      * values used are the X and Y scaling, translation, and shearing components
1031      * of a transform.
1032      *
1033      * @param xform The affine to be copied and used as the current transform.
1034      */
1035     public void setTransform(Affine xform) {
1036         curState.transform.setTransform(xform.getMxx(), xform.getMyx(),
1037                                         xform.getMxy(), xform.getMyy(),
1038                                         xform.getTx(), xform.getTy());
1039         txdirty = true;
1040     }
1041 
1042     /**
1043      * Copies the current transform into the supplied object, creating
1044      * a new {@link Affine} object if it is null, and returns the object
1045      * containing the copy.
1046      *
1047      * @param xform A transform object that will be used to hold the result.
1048      * If xform is non null, then this method will copy the current transform
1049      * into that object. If xform is null a new transform object will be
1050      * constructed. In either case, the return value is a copy of the current
1051      * transform.
1052      *
1053      * @return A copy of the current transform.
1054      */
1055     public Affine getTransform(Affine xform) {
1056         if (xform == null) {
1057             xform = new Affine();
1058         }
1059 
1060         xform.setMxx(curState.transform.getMxx());
1061         xform.setMxy(curState.transform.getMxy());
1062         xform.setMxz(0);
1063         xform.setTx(curState.transform.getMxt());
1064         xform.setMyx(curState.transform.getMyx());
1065         xform.setMyy(curState.transform.getMyy());
1066         xform.setMyz(0);
1067         xform.setTy(curState.transform.getMyt());
1068         xform.setMzx(0);
1069         xform.setMzy(0);
1070         xform.setMzz(1);
1071         xform.setTz(0);
1072 
1073         return xform;
1074     }
1075 
1076     /**
1077      * Returns a copy of the current transform.
1078      *
1079      * @return a copy of the transform of the current state.
1080      */
1081     public Affine getTransform() {
1082         return getTransform(null);
1083     }
1084 
1085     /**
1086      * Sets the global alpha of the current state.
1087      * The default value is {@code 1.0}.
1088      * Any valid double can be set, but only values in the range
1089      * {@code [0.0, 1.0]} are valid and the nearest value in that
1090      * range will be used for rendering.
1091      * The global alpha is a <a href="#comm-attr">common attribute</a>
1092      * used for nearly all rendering methods as specified in the
1093      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1094      *
1095      * @param alpha the new alpha value, clamped to {@code [0.0, 1.0]}
1096      *              during actual use.
1097      */
1098     public void setGlobalAlpha(double alpha) {
1099         if (curState.globalAlpha != alpha) {
1100             curState.globalAlpha = alpha;
1101             alpha = (alpha > 1.0) ? 1.0 : (alpha < 0.0) ? 0.0 : alpha;
1102             writeParam(alpha, NGCanvas.GLOBAL_ALPHA);
1103         }
1104     }
1105 
1106     /**
1107      * Gets the current global alpha.
1108      * The default value is {@code 1.0}.
1109      * The global alpha is a <a href="#comm-attr">common attribute</a>
1110      * used for nearly all rendering methods as specified in the
1111      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1112      *
1113      * @return the current global alpha.
1114      */
1115     public double getGlobalAlpha() {
1116         return curState.globalAlpha;
1117     }
1118 
1119     /**
1120      * Sets the global blend mode.
1121      * The default value is {@link BlendMode#SRC_OVER SRC_OVER}.
1122      * A {@code null} value will be ignored and the current value will remain unchanged.
1123      * The blend mode is a <a href="#comm-attr">common attribute</a>
1124      * used for nearly all rendering methods as specified in the
1125      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1126      *
1127      * @param op the {@code BlendMode} that will be set or null.
1128      */
1129     public void setGlobalBlendMode(BlendMode op) {
1130         if (op != null && op != curState.blendop) {
1131             GrowableDataBuffer buf = getBuffer();
1132             curState.blendop = op;
1133             buf.putByte(NGCanvas.COMP_MODE);
1134             buf.putObject(Blend.impl_getToolkitMode(op));
1135         }
1136     }
1137 
1138     /**
1139      * Gets the global blend mode.
1140      * The default value is {@link BlendMode#SRC_OVER SRC_OVER}.
1141      * The blend mode is a <a href="#comm-attr">common attribute</a>
1142      * used for nearly all rendering methods as specified in the
1143      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1144      *
1145      * @return the global {@code BlendMode} of the current state.
1146      */
1147     public BlendMode getGlobalBlendMode() {
1148         return curState.blendop;
1149     }
1150 
1151     /**
1152      * Sets the current fill paint attribute.
1153      * The default value is {@link Color#BLACK BLACK}.
1154      * The fill paint is a <a href="#fill-attr">fill attribute</a>
1155      * used for any of the fill methods as specified in the
1156      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1157      * A {@code null} value will be ignored and the current value will remain unchanged.
1158      *
1159      * @param p The {@code Paint} to be used as the fill {@code Paint} or null.
1160      */
1161     public void setFill(Paint p) {
1162         if (p != null && curState.fill != p) {
1163             curState.fill = p;
1164             writePaint(p, NGCanvas.FILL_PAINT);
1165         }
1166     }
1167 
1168     /**
1169      * Gets the current fill paint attribute.
1170      * The default value is {@link Color#BLACK BLACK}.
1171      * The fill paint is a <a href="#fill-attr">fill attribute</a>
1172      * used for any of the fill methods as specified in the
1173      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1174      *
1175      * @return p The {@code Paint} to be used as the fill {@code Paint}.
1176      */
1177     public Paint getFill() {
1178         return curState.fill;
1179     }
1180 
1181     /**
1182      * Sets the current stroke paint attribute.
1183      * The default value is {@link Color#BLACK BLACK}.
1184      * The stroke paint is a <a href="#strk-attr">stroke attribute</a>
1185      * used for any of the stroke methods as specified in the
1186      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1187      * A {@code null} value will be ignored and the current value will remain unchanged.
1188      *
1189      * @param p The Paint to be used as the stroke Paint or null.
1190      */
1191     public void setStroke(Paint p) {
1192         if (p != null && curState.stroke != p) {
1193             curState.stroke = p;
1194             writePaint(p, NGCanvas.STROKE_PAINT);
1195         }
1196     }
1197 
1198     /**
1199      * Gets the current stroke.
1200      * The default value is {@link Color#BLACK BLACK}.
1201      * The stroke paint is a <a href="#strk-attr">stroke attribute</a>
1202      * used for any of the stroke methods as specified in the
1203      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1204      *
1205      * @return the {@code Paint} to be used as the stroke {@code Paint}.
1206      */
1207     public Paint getStroke() {
1208         return curState.stroke;
1209     }
1210 
1211     /**
1212      * Sets the current line width.
1213      * The default value is {@code 1.0}.
1214      * The line width is a <a href="#strk-attr">stroke attribute</a>
1215      * used for any of the stroke methods as specified in the
1216      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1217      * An infinite or non-positive value outside of the range {@code (0, +inf)}
1218      * will be ignored and the current value will remain unchanged.
1219      *
1220      * @param lw value in the range {0-positive infinity}, with any other value
1221      * being ignored and leaving the value unchanged.
1222      */
1223     public void setLineWidth(double lw) {
1224         // Per W3C spec: On setting, zero, negative, infinite, and NaN
1225         // values must be ignored, leaving the value unchanged
1226         if (lw > 0 && lw < Double.POSITIVE_INFINITY) {
1227             if (curState.linewidth != lw) {
1228                 curState.linewidth = lw;
1229                 writeParam(lw, NGCanvas.LINE_WIDTH);
1230             }
1231         }
1232     }
1233 
1234     /**
1235      * Gets the current line width.
1236      * The default value is {@code 1.0}.
1237      * The line width is a <a href="#strk-attr">stroke attribute</a>
1238      * used for any of the stroke methods as specified in the
1239      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1240      *
1241      * @return value between 0 and infinity.
1242      */
1243     public double getLineWidth() {
1244         return curState.linewidth;
1245     }
1246 
1247     /**
1248      * Sets the current stroke line cap.
1249      * The default value is {@link StrokeLineCap#SQUARE SQUARE}.
1250      * The line cap is a <a href="#strk-attr">stroke attribute</a>
1251      * used for any of the stroke methods as specified in the
1252      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1253      * A {@code null} value will be ignored and the current value will remain unchanged.
1254      *
1255      * @param cap {@code StrokeLineCap} with a value of Butt, Round, or Square or null.
1256      */
1257     public void setLineCap(StrokeLineCap cap) {
1258         if (cap != null && curState.linecap != cap) {
1259             byte v;
1260             switch (cap) {
1261                 case BUTT: v = NGCanvas.CAP_BUTT; break;
1262                 case ROUND: v = NGCanvas.CAP_ROUND; break;
1263                 case SQUARE: v = NGCanvas.CAP_SQUARE; break;
1264                 default: return;
1265             }
1266             curState.linecap = cap;
1267             writeParam(v, NGCanvas.LINE_CAP);
1268         }
1269     }
1270 
1271     /**
1272      * Gets the current stroke line cap.
1273      * The default value is {@link StrokeLineCap#SQUARE SQUARE}.
1274      * The line cap is a <a href="#strk-attr">stroke attribute</a>
1275      * used for any of the stroke methods as specified in the
1276      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1277      *
1278      * @return {@code StrokeLineCap} with a value of Butt, Round, or Square.
1279      */
1280     public StrokeLineCap getLineCap() {
1281         return curState.linecap;
1282     }
1283 
1284     /**
1285      * Sets the current stroke line join.
1286      * The default value is {@link StrokeLineJoin#MITER}.
1287      * The line join is a <a href="#strk-attr">stroke attribute</a>
1288      * used for any of the stroke methods as specified in the
1289      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1290      * A {@code null} value will be ignored and the current value will remain unchanged.
1291      *
1292      * @param join {@code StrokeLineJoin} with a value of Miter, Bevel, or Round or null.
1293      */
1294     public void setLineJoin(StrokeLineJoin join) {
1295         if (join != null && curState.linejoin != join) {
1296             byte v;
1297             switch (join) {
1298                 case MITER: v = NGCanvas.JOIN_MITER; break;
1299                 case BEVEL: v = NGCanvas.JOIN_BEVEL; break;
1300                 case ROUND: v = NGCanvas.JOIN_ROUND; break;
1301                 default: return;
1302             }
1303             curState.linejoin = join;
1304             writeParam(v, NGCanvas.LINE_JOIN);
1305         }
1306     }
1307 
1308     /**
1309      * Gets the current stroke line join.
1310      * The default value is {@link StrokeLineJoin#MITER}.
1311      * The line join is a <a href="#strk-attr">stroke attribute</a>
1312      * used for any of the stroke methods as specified in the
1313      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1314      *
1315      * @return {@code StrokeLineJoin} with a value of Miter, Bevel, or Round.
1316      */
1317     public StrokeLineJoin getLineJoin() {
1318         return curState.linejoin;
1319     }
1320 
1321     /**
1322      * Sets the current miter limit.
1323      * The default value is {@code 10.0}.
1324      * The miter limit is a <a href="#strk-attr">stroke attribute</a>
1325      * used for any of the stroke methods as specified in the
1326      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1327      * An infinite or non-positive value outside of the range {@code (0, +inf)}
1328      * will be ignored and the current value will remain unchanged.
1329      *
1330      * @param ml miter limit value between 0 and positive infinity with
1331      * any other value being ignored and leaving the value unchanged.
1332      */
1333     public void setMiterLimit(double ml) {
1334         // Per W3C spec: On setting, zero, negative, infinite, and NaN
1335         // values must be ignored, leaving the value unchanged
1336         if (ml > 0.0 && ml < Double.POSITIVE_INFINITY) {
1337             if (curState.miterlimit != ml) {
1338                 curState.miterlimit = ml;
1339                 writeParam(ml, NGCanvas.MITER_LIMIT);
1340             }
1341         }
1342     }
1343 
1344     /**
1345      * Gets the current miter limit.
1346      * The default value is {@code 10.0}.
1347      * The miter limit is a <a href="#strk-attr">stroke attribute</a>
1348      * used for any of the stroke methods as specified in the
1349      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1350      *
1351      * @return the miter limit value in the range {@code 0.0-positive infinity}
1352      */
1353     public double getMiterLimit() {
1354         return curState.miterlimit;
1355     }
1356 
1357     /**
1358      * Sets the current stroke line dash pattern to a normalized copy of
1359      * the argument.
1360      * The default value is {@code null}.
1361      * The line dash array is a <a href="#strk-attr">stroke attribute</a>
1362      * used for any of the stroke methods as specified in the
1363      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1364      * If the array is {@code null} or empty or contains all {@code 0} elements
1365      * then dashing will be disabled and the current dash array will be set
1366      * to {@code null}.
1367      * If any of the elements of the array are a negative, infinite, or NaN
1368      * value outside the range {@code [0, +inf)} then the entire array will
1369      * be ignored and the current dash array will remain unchanged.
1370      * If the array is an odd length then it will be treated as if it
1371      * were two copies of the array appended to each other.
1372      *
1373      * @param dashes the array of finite non-negative dash lengths
1374      * @since JavaFX 8u40
1375      */
1376     public void setLineDashes(double... dashes) {
1377         if (dashes == null || dashes.length == 0) {
1378             if (curState.dashes == null) {
1379                 return;
1380             }
1381             curState.dashes = null;
1382         } else {
1383             boolean allZeros = true;
1384             for (int i = 0; i < dashes.length; i++) {
1385                 double d = dashes[i];
1386                 if (d >= 0.0 && d < Double.POSITIVE_INFINITY) {
1387                     // Non-NaN, finite, non-negative
1388                     // Test cannot be inverted or it will not implicitly test for NaN
1389                     if (d > 0) {
1390                         allZeros = false;
1391                     }
1392                 } else {
1393                     return;
1394                 }
1395             }
1396             if (allZeros) {
1397                 if (curState.dashes == null) {
1398                     return;
1399                 }
1400                 curState.dashes = null;
1401             } else {
1402                 int dashlen = dashes.length;
1403                 if ((dashlen & 1) == 0) {
1404                     curState.dashes = Arrays.copyOf(dashes, dashlen);
1405                 } else {
1406                     curState.dashes = Arrays.copyOf(dashes, dashlen * 2);
1407                     System.arraycopy(dashes, 0, curState.dashes, dashlen, dashlen);
1408                 }
1409             }
1410         }
1411         GrowableDataBuffer buf = getBuffer();
1412         buf.putByte(NGCanvas.DASH_ARRAY);
1413         buf.putObject(curState.dashes);
1414     }
1415 
1416     /**
1417      * Gets a copy of the current line dash array.
1418      * The default value is {@code null}.
1419      * The array may be normalized by the validation tests in the
1420      * {@link #setLineDashes(double...)} method.
1421      * The line dash array is a <a href="#strk-attr">stroke attribute</a>
1422      * used for any of the stroke methods as specified in the
1423      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1424      *
1425      * @return a copy of the current line dash array.
1426      * @since JavaFX 8u40
1427      */
1428     public double[] getLineDashes() {
1429         if (curState.dashes == null) {
1430             return null;
1431         }
1432         return Arrays.copyOf(curState.dashes, curState.dashes.length);
1433     }
1434 
1435     /**
1436      * Sets the line dash offset.
1437      * The default value is {@code 0.0}.
1438      * The line dash offset is a <a href="#strk-attr">stroke attribute</a>
1439      * used for any of the stroke methods as specified in the
1440      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1441      * An infinite or NaN value outside of the range {@code (-inf, +inf)}
1442      * will be ignored and the current value will remain unchanged.
1443      *
1444      * @param dashOffset the line dash offset in the range {@code (-inf, +inf)}
1445      * @since JavaFX 8u40
1446      */
1447     public void setLineDashOffset(double dashOffset) {
1448         // Per W3C spec: On setting, infinite, and NaN
1449         // values must be ignored, leaving the value unchanged
1450         if (dashOffset > Double.NEGATIVE_INFINITY && dashOffset < Double.POSITIVE_INFINITY) {
1451             curState.dashOffset = dashOffset;
1452             writeParam(dashOffset, NGCanvas.DASH_OFFSET);
1453         }
1454     }
1455 
1456     /**
1457      * Gets the current line dash offset.
1458      * The default value is {@code 0.0}.
1459      * The line dash offset is a <a href="#strk-attr">stroke attribute</a>
1460      * used for any of the stroke methods as specified in the
1461      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1462      *
1463      * @return the line dash offset in the range {@code (-inf, +inf)}
1464      * @since JavaFX 8u40
1465      */
1466     public double getLineDashOffset() {
1467         return curState.dashOffset;
1468     }
1469 
1470     /**
1471      * Sets the current Font.
1472      * The default value is specified by {@link Font#getDefault()}.
1473      * The font is a <a href="#text-attr">text attribute</a>
1474      * used for any of the text methods as specified in the
1475      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1476      * A {@code null} value will be ignored and the current value will remain unchanged.
1477      *
1478      * @param f the Font or null.
1479      */
1480     public void setFont(Font f) {
1481         if (f != null && curState.font != f) {
1482             curState.font = f;
1483             GrowableDataBuffer buf = getBuffer();
1484             buf.putByte(NGCanvas.FONT);
1485             buf.putObject(f.impl_getNativeFont());
1486         }
1487     }
1488 
1489     /**
1490      * Gets the current Font.
1491      * The default value is specified by {@link Font#getDefault()}.
1492      * The font is a <a href="#text-attr">text attribute</a>
1493      * used for any of the text methods as specified in the
1494      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1495      *
1496      * @return the Font
1497      */
1498     public Font getFont() {
1499         return curState.font;
1500     }
1501 
1502     /**
1503      * Sets the current Font Smoothing Type.
1504      * The default value is {@link FontSmoothingType#GRAY GRAY}.
1505      * The font smoothing type is a <a href="#text-attr">text attribute</a>
1506      * used for any of the text methods as specified in the
1507      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1508      * A {@code null} value will be ignored and the current value will remain unchanged.
1509      * <p>
1510      * <b>Note</b> that the {@code FontSmoothingType} value of
1511      * {@link FontSmoothingType#LCD LCD} is only supported over an opaque
1512      * background.  {@code LCD} text will generally appear as {@code GRAY}
1513      * text over transparent or partially transparent pixels, and in some
1514      * implementations it may not be supported at all on a {@link Canvas}
1515      * because the required support does not exist for surfaces which contain
1516      * an alpha channel as all {@code Canvas} objects do.
1517      *
1518      * @param fontsmoothing the {@link FontSmoothingType} or null
1519      * @since JavaFX 8u40
1520      */
1521     public void setFontSmoothingType(FontSmoothingType fontsmoothing) {
1522         if (fontsmoothing != null && fontsmoothing != curState.fontsmoothing) {
1523             curState.fontsmoothing = fontsmoothing;
1524             writeParam((byte) fontsmoothing.ordinal(), NGCanvas.FONT_SMOOTH);
1525         }
1526     }
1527 
1528     /**
1529      * Gets the current Font Smoothing Type.
1530      * The default value is {@link FontSmoothingType#GRAY GRAY}.
1531      * The font smoothing type is a <a href="#text-attr">text attribute</a>
1532      * used for any of the text methods as specified in the
1533      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1534      *
1535      * @return the {@link FontSmoothingType}
1536      * @since JavaFX 8u40
1537      */
1538     public FontSmoothingType getFontSmoothingType() {
1539         return curState.fontsmoothing;
1540     }
1541 
1542     /**
1543      * Defines horizontal text alignment, relative to the text {@code x} origin.
1544      * The default value is {@link TextAlignment#LEFT LEFT}.
1545      * The text alignment is a <a href="#text-attr">text attribute</a>
1546      * used for any of the text methods as specified in the
1547      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1548      * <p>
1549      * Let horizontal bounds represent the logical width of a single line of
1550      * text. Where each line of text has a separate horizontal bounds.
1551      * <p>
1552      * Then TextAlignment is specified as:
1553      * <ul>
1554      * <li>Left: the left edge of the horizontal bounds will be at {@code x}.
1555      * <li>Center: the center, halfway between left and right edge, of the
1556      * horizontal bounds will be at {@code x}.
1557      * <li>Right: the right edge of the horizontal bounds will be at {@code x}.
1558      * </ul>
1559      * <p>
1560      *
1561      * Note: Canvas does not support line wrapping, therefore the text
1562      * alignment Justify is identical to left aligned text.
1563      * <p>
1564      * A {@code null} value will be ignored and the current value will remain unchanged.
1565      *
1566      * @param align {@code TextAlignment} with values of Left, Center, Right or null.
1567      */
1568     public void setTextAlign(TextAlignment align) {
1569         if (align != null && curState.textalign != align) {
1570             byte a;
1571             switch (align) {
1572                 case LEFT: a = NGCanvas.ALIGN_LEFT; break;
1573                 case CENTER: a = NGCanvas.ALIGN_CENTER; break;
1574                 case RIGHT: a = NGCanvas.ALIGN_RIGHT; break;
1575                 case JUSTIFY: a = NGCanvas.ALIGN_JUSTIFY; break;
1576                 default: return;
1577             }
1578             curState.textalign = align;
1579             writeParam(a, NGCanvas.TEXT_ALIGN);
1580         }
1581     }
1582 
1583     /**
1584      * Gets the current {@code TextAlignment}.
1585      * The default value is {@link TextAlignment#LEFT LEFT}.
1586      * The text alignment is a <a href="#text-attr">text attribute</a>
1587      * used for any of the text methods as specified in the
1588      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1589      *
1590      * @return {@code TextAlignment} with values of Left, Center, Right, or
1591      * Justify.
1592      */
1593     public TextAlignment getTextAlign() {
1594         return curState.textalign;
1595     }
1596 
1597     /**
1598      * Sets the current Text Baseline.
1599      * The default value is {@link VPos#BASELINE BASELINE}.
1600      * The text baseline is a <a href="#text-attr">text attribute</a>
1601      * used for any of the text methods as specified in the
1602      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1603      * A {@code null} value will be ignored and the current value will remain unchanged.
1604      *
1605      * @param baseline {@code VPos} with values of Top, Center, Baseline, or Bottom or null.
1606      */
1607     public void setTextBaseline(VPos baseline) {
1608         if (baseline != null && curState.textbaseline != baseline) {
1609             byte b;
1610             switch (baseline) {
1611                 case TOP: b = NGCanvas.BASE_TOP; break;
1612                 case CENTER: b = NGCanvas.BASE_MIDDLE; break;
1613                 case BASELINE: b = NGCanvas.BASE_ALPHABETIC; break;
1614                 case BOTTOM: b = NGCanvas.BASE_BOTTOM; break;
1615                 default: return;
1616             }
1617             curState.textbaseline = baseline;
1618             writeParam(b, NGCanvas.TEXT_BASELINE);
1619         }
1620     }
1621 
1622     /**
1623      * Gets the current Text Baseline.
1624      * The default value is {@link VPos#BASELINE BASELINE}.
1625      * The text baseline is a <a href="#text-attr">text attribute</a>
1626      * used for any of the text methods as specified in the
1627      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1628      *
1629      * @return {@code VPos} with values of Top, Center, Baseline, or Bottom
1630      */
1631     public VPos getTextBaseline() {
1632         return curState.textbaseline;
1633     }
1634 
1635     /**
1636      * Fills the given string of text at position x, y
1637      * with the current fill paint attribute.
1638      * A {@code null} text value will be ignored.
1639      * <p>
1640      * This method will be affected by any of the
1641      * <a href="#comm-attr">global common</a>,
1642      * <a href="#fill-attr">fill</a>,
1643      * or <a href="#text-attr">text</a>
1644      * attributes as specified in the
1645      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1646      * </p>
1647      *
1648      * @param text the string of text or null.
1649      * @param x position on the x axis.
1650      * @param y position on the y axis.
1651      */
1652     public void fillText(String text, double x, double y) {
1653         writeText(text, x, y, 0, NGCanvas.FILL_TEXT);
1654     }
1655 
1656     /**
1657      * Draws the given string of text at position x, y
1658      * with the current stroke paint attribute.
1659      * A {@code null} text value will be ignored.
1660      * <p>
1661      * This method will be affected by any of the
1662      * <a href="#comm-attr">global common</a>,
1663      * <a href="#strk-attr">stroke</a>,
1664      * or <a href="#text-attr">text</a>
1665      * attributes as specified in the
1666      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1667      * </p>
1668      *
1669      * @param text the string of text or null.
1670      * @param x position on the x axis.
1671      * @param y position on the y axis.
1672      */
1673     public void strokeText(String text, double x, double y) {
1674         writeText(text, x, y, 0, NGCanvas.STROKE_TEXT);
1675     }
1676 
1677     /**
1678      * Fills text and includes a maximum width of the string.
1679      * If the width of the text extends past max width, then it will be sized
1680      * to fit.
1681      * A {@code null} text value will be ignored.
1682      * <p>
1683      * This method will be affected by any of the
1684      * <a href="#comm-attr">global common</a>,
1685      * <a href="#fill-attr">fill</a>,
1686      * or <a href="#text-attr">text</a>
1687      * attributes as specified in the
1688      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1689      * </p>
1690      *
1691      * @param text the string of text or null.
1692      * @param x position on the x axis.
1693      * @param y position on the y axis.
1694      * @param maxWidth  maximum width the text string can have.
1695      */
1696     public void fillText(String text, double x, double y, double maxWidth) {
1697         if (maxWidth <= 0) return;
1698         writeText(text, x, y, maxWidth, NGCanvas.FILL_TEXT);
1699     }
1700 
1701     /**
1702      * Draws text with stroke paint and includes a maximum width of the string.
1703      * If the width of the text extends past max width, then it will be sized
1704      * to fit.
1705      * A {@code null} text value will be ignored.
1706      * <p>
1707      * This method will be affected by any of the
1708      * <a href="#comm-attr">global common</a>,
1709      * <a href="#strk-attr">stroke</a>,
1710      * or <a href="#text-attr">text</a>
1711      * attributes as specified in the
1712      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1713      * </p>
1714      *
1715      * @param text the string of text or null.
1716      * @param x position on the x axis.
1717      * @param y position on the y axis.
1718      * @param maxWidth  maximum width the text string can have.
1719      */
1720     public void strokeText(String text, double x, double y, double maxWidth) {
1721         if (maxWidth <= 0) return;
1722         writeText(text, x, y, maxWidth, NGCanvas.STROKE_TEXT);
1723     }
1724 
1725 
1726     /**
1727      * Set the filling rule attribute for determining the interior of paths
1728      * in fill or clip operations.
1729      * The default value is {@code FillRule.NON_ZERO}.
1730      * A {@code null} value will be ignored and the current value will remain unchanged.
1731      * The fill rule is a <a href="#path-attr">path attribute</a>
1732      * used for any of the fill or clip path methods as specified in the
1733      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1734      *
1735      * @param fillRule {@code FillRule} with a value of  Even_odd or Non_zero or null.
1736      */
1737      public void setFillRule(FillRule fillRule) {
1738          if (fillRule != null && curState.fillRule != fillRule) {
1739             byte b;
1740             if (fillRule == FillRule.EVEN_ODD) {
1741                 b = NGCanvas.FILL_RULE_EVEN_ODD;
1742             } else {
1743                 b = NGCanvas.FILL_RULE_NON_ZERO;
1744             }
1745             curState.fillRule = fillRule;
1746             writeParam(b, NGCanvas.FILL_RULE);
1747         }
1748      }
1749 
1750     /**
1751      * Get the filling rule attribute for determining the interior of paths
1752      * in fill and clip operations.
1753      * The default value is {@code FillRule.NON_ZERO}.
1754      * The fill rule is a <a href="#path-attr">path attribute</a>
1755      * used for any of the fill or clip path methods as specified in the
1756      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1757      *
1758      * @return current fill rule.
1759      */
1760      public FillRule getFillRule() {
1761          return curState.fillRule;
1762      }
1763 
1764     /**
1765      * Resets the current path to empty.
1766      * The default path is empty.
1767      * The current path is a <a href="#path-attr">path attribute</a>
1768      * used for any of the path methods as specified in the
1769      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1770      * and <b>is not affected</b> by the {@link #save()} and
1771      * {@link #restore()} operations.
1772      */
1773     public void beginPath() {
1774         path.reset();
1775         markPathDirty();
1776     }
1777 
1778     /**
1779      * Issues a move command for the current path to the given x,y coordinate.
1780      * The coordinates are transformed by the current transform as they are
1781      * added to the path and unaffected by subsequent changes to the transform.
1782      * The current path is a <a href="#path-attr">path attribute</a>
1783      * used for any of the path methods as specified in the
1784      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1785      * and <b>is not affected</b> by the {@link #save()} and
1786      * {@link #restore()} operations.
1787      *
1788      * @param x0 the X position for the move to command.
1789      * @param y0 the Y position for the move to command.
1790      */
1791     public void moveTo(double x0, double y0) {
1792         coords[0] = (float) x0;
1793         coords[1] = (float) y0;
1794         curState.transform.transform(coords, 0, coords, 0, 1);
1795         path.moveTo(coords[0], coords[1]);
1796         markPathDirty();
1797     }
1798 
1799     /**
1800      * Adds segments to the current path to make a line to the given x,y
1801      * coordinate.
1802      * The coordinates are transformed by the current transform as they are
1803      * added to the path and unaffected by subsequent changes to the transform.
1804      * The current path is a <a href="#path-attr">path attribute</a>
1805      * used for any of the path methods as specified in the
1806      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1807      * and <b>is not affected</b> by the {@link #save()} and
1808      * {@link #restore()} operations.
1809      *
1810      * @param x1 the X coordinate of the ending point of the line.
1811      * @param y1 the Y coordinate of the ending point of the line.
1812      */
1813     public void lineTo(double x1, double y1) {
1814         coords[0] = (float) x1;
1815         coords[1] = (float) y1;
1816         curState.transform.transform(coords, 0, coords, 0, 1);
1817         if (path.getNumCommands() == 0) {
1818             path.moveTo(coords[0], coords[1]);
1819         }
1820         path.lineTo(coords[0], coords[1]);
1821         markPathDirty();
1822     }
1823 
1824     /**
1825      * Adds segments to the current path to make a quadratic Bezier curve.
1826      * The coordinates are transformed by the current transform as they are
1827      * added to the path and unaffected by subsequent changes to the transform.
1828      * The current path is a <a href="#path-attr">path attribute</a>
1829      * used for any of the path methods as specified in the
1830      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1831      * and <b>is not affected</b> by the {@link #save()} and
1832      * {@link #restore()} operations.
1833      *
1834      * @param xc the X coordinate of the control point
1835      * @param yc the Y coordinate of the control point
1836      * @param x1 the X coordinate of the end point
1837      * @param y1 the Y coordinate of the end point
1838      */
1839     public void quadraticCurveTo(double xc, double yc, double x1, double y1) {
1840         coords[0] = (float) xc;
1841         coords[1] = (float) yc;
1842         coords[2] = (float) x1;
1843         coords[3] = (float) y1;
1844         curState.transform.transform(coords, 0, coords, 0, 2);
1845         if (path.getNumCommands() == 0) {
1846             path.moveTo(coords[0], coords[1]);
1847         }
1848         path.quadTo(coords[0], coords[1], coords[2], coords[3]);
1849         markPathDirty();
1850     }
1851 
1852     /**
1853      * Adds segments to the current path to make a cubic Bezier curve.
1854      * The coordinates are transformed by the current transform as they are
1855      * added to the path and unaffected by subsequent changes to the transform.
1856      * The current path is a <a href="#path-attr">path attribute</a>
1857      * used for any of the path methods as specified in the
1858      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1859      * and <b>is not affected</b> by the {@link #save()} and
1860      * {@link #restore()} operations.
1861      *
1862      * @param xc1 the X coordinate of first Bezier control point.
1863      * @param yc1 the Y coordinate of the first Bezier control point.
1864      * @param xc2 the X coordinate of the second Bezier control point.
1865      * @param yc2 the Y coordinate of the second Bezier control point.
1866      * @param x1  the X coordinate of the end point.
1867      * @param y1  the Y coordinate of the end point.
1868      */
1869     public void bezierCurveTo(double xc1, double yc1, double xc2, double yc2, double x1, double y1) {
1870         coords[0] = (float) xc1;
1871         coords[1] = (float) yc1;
1872         coords[2] = (float) xc2;
1873         coords[3] = (float) yc2;
1874         coords[4] = (float) x1;
1875         coords[5] = (float) y1;
1876         curState.transform.transform(coords, 0, coords, 0, 3);
1877         if (path.getNumCommands() == 0) {
1878             path.moveTo(coords[0], coords[1]);
1879         }
1880         path.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
1881         markPathDirty();
1882     }
1883 
1884     /**
1885      * Adds segments to the current path to make an arc.
1886      * The coordinates are transformed by the current transform as they are
1887      * added to the path and unaffected by subsequent changes to the transform.
1888      * The current path is a <a href="#path-attr">path attribute</a>
1889      * used for any of the path methods as specified in the
1890      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1891      * and <b>is not affected</b> by the {@link #save()} and
1892      * {@link #restore()} operations.
1893      * <p>
1894      * If {@code p0} is the current point in the path and {@code p1} is the
1895      * point specified by {@code (x1, y1)} and {@code p2} is the point
1896      * specified by {@code (x2, y2)}, then the arc segments appended will
1897      * be segments along the circumference of a circle of the specified
1898      * radius touching and inscribed into the convex (interior) side of
1899      * {@code p0->p1->p2}.  The path will contain a line segment (if
1900      * needed) to the tangent point between that circle and {@code p0->p1}
1901      * followed by circular arc segments to reach the tangent point between
1902      * the circle and {@code p1->p2} and will end with the current point at
1903      * that tangent point (not at {@code p2}).
1904      * Note that the radius and circularity of the arc segments will be
1905      * measured or considered relative to the current transform, but the
1906      * resulting segments that are computed from those untransformed
1907      * points will then be transformed when they are added to the path.
1908      * Since all computation is done in untransformed space, but the
1909      * pre-existing path segments are all transformed, the ability to
1910      * correctly perform the computation may implicitly depend on being
1911      * able to inverse transform the current end of the current path back
1912      * into untransformed coordinates.
1913      * </p>
1914      * <p>
1915      * If there is no way to compute and inscribe the indicated circle
1916      * for any reason then the entire operation will simply append segments
1917      * to force a line to point {@code p1}.  Possible reasons that the
1918      * computation may fail include:
1919      * <ul>
1920      * <li>The current path is empty.</li>
1921      * <li>The segments {@code p0->p1->p2} are colinear.</li>
1922      * <li>the current transform is non-invertible so that the current end
1923      * point of the current path cannot be untransformed for computation.</li>
1924      * </ul>
1925      * </p>
1926      *
1927      * @param x1 the X coordinate of the first point of the arc.
1928      * @param y1 the Y coordinate of the first point of the arc.
1929      * @param x2 the X coordinate of the second point of the arc.
1930      * @param y2 the Y coordinate of the second point of the arc.
1931      * @param radius the radius of the arc in the range {0.0-positive infinity}.
1932      */
1933     public void arcTo(double x1, double y1, double x2, double y2, double radius) {
1934         if (path.getNumCommands() == 0) {
1935             moveTo(x1, y1);
1936             lineTo(x1, y1);
1937         } else if (!tryArcTo((float) x1, (float) y1, (float) x2, (float) y2,
1938                              (float) radius))
1939         {
1940             lineTo(x1, y1);
1941         }
1942     }
1943 
1944     private static double lenSq(double x0, double y0, double x1, double y1) {
1945         x1 -= x0;
1946         y1 -= y0;
1947         return x1 * x1 + y1 * y1;
1948     }
1949 
1950     private boolean tryArcTo(float x1, float y1, float x2, float y2, float radius) {
1951         float x0, y0;
1952         if (curState.transform.isTranslateOrIdentity()) {
1953             x0 = (float) (path.getCurrentX() - curState.transform.getMxt());
1954             y0 = (float) (path.getCurrentY() - curState.transform.getMyt());
1955         } else {
1956             coords[0] = path.getCurrentX();
1957             coords[1] = path.getCurrentY();
1958             try {
1959                 curState.transform.inverseTransform(coords, 0, coords, 0, 1);
1960             } catch (NoninvertibleTransformException e) {
1961                 return false;
1962             }
1963             x0 = coords[0];
1964             y0 = coords[1];
1965         }
1966         // call x1,y1 the corner point
1967         // If 2*theta is the angle described by p0->p1->p2
1968         // then theta is the angle described by p0->p1->centerpt and
1969         // centerpt->p1->p2
1970         // We know that the distance from the arc center to the tangent points
1971         // is r, and if A is the distance from the corner to the tangent point
1972         // then we know:
1973         // tan(theta) = r/A
1974         // A = r / sin(theta)
1975         // B = A * cos(theta) = r * (sin/cos) = r * tan
1976         // We use the cosine rule on the triangle to get the 2*theta angle:
1977         // cosB = (a^2 + c^2 - b^2) / (2ac)
1978         // where a and c are the adjacent sides and b is the opposite side
1979         // i.e. a = p0->p1, c=p1->p2, b=p0->p2
1980         // Then we can use the tan^2 identity to compute B:
1981         // tan^2 = (1 - cos(2theta)) / (1 + cos(2theta))
1982         double lsq01 = lenSq(x0, y0, x1, y1);
1983         double lsq12 = lenSq(x1, y1, x2, y2);
1984         double lsq02 = lenSq(x0, y0, x2, y2);
1985         double len01 = Math.sqrt(lsq01);
1986         double len12 = Math.sqrt(lsq12);
1987         double cosnum = lsq01 + lsq12 - lsq02;
1988         double cosden = 2.0 * len01 * len12;
1989         if (cosden == 0.0 || radius <= 0f) {
1990             return false;
1991         }
1992         double cos_2theta = cosnum / cosden;
1993         double tansq_den = (1.0 + cos_2theta);
1994         if (tansq_den == 0.0) {
1995             return false;
1996         }
1997         double tansq_theta = (1.0 - cos_2theta) / tansq_den;
1998         double A = radius / Math.sqrt(tansq_theta);
1999         double tx0 = x1 + (A / len01) * (x0 - x1);
2000         double ty0 = y1 + (A / len01) * (y0 - y1);
2001         double tx1 = x1 + (A / len12) * (x2 - x1);
2002         double ty1 = y1 + (A / len12) * (y2 - y1);
2003         // The midpoint between the two tangent points
2004         double mx = (tx0 + tx1) / 2.0;
2005         double my = (ty0 + ty1) / 2.0;
2006         // similar triangles tell us that:
2007         // len(m,center)/len(m,tangent) = len(m,tangent)/len(corner,m)
2008         // len(m,center) = lensq(m,tangent)/len(corner,m)
2009         // center = m + (m - p1) * len(m,center) / len(corner,m)
2010         //   = m + (m - p1) * (lensq(m,tangent) / lensq(corner,m))
2011         double lenratioden = lenSq(mx, my, x1, y1);
2012         if (lenratioden == 0.0) {
2013             return false;
2014         }
2015         double lenratio = lenSq(mx, my, tx0, ty0) / lenratioden;
2016         double cx = mx + (mx - x1) * lenratio;
2017         double cy = my + (my - y1) * lenratio;
2018         if (!(cx == cx && cy == cy)) {
2019             return false;
2020         }
2021         // Looks like we are good to draw, first we have to get to the
2022         // initial tangent point with a line segment.
2023         if (tx0 != x0 || ty0 != y0) {
2024             lineTo(tx0, ty0);
2025         }
2026         // We need sin(arc/2), cos(arc/2)
2027         // and possibly sin(arc/4), cos(arc/4) if we need 2 cubic beziers
2028         // We have tan(theta) = tan(tri/2)
2029         // arc = 180-tri
2030         // arc/2 = (180-tri)/2 = 90-(tri/2)
2031         // sin(arc/2) = sin(90-(tri/2)) = cos(tri/2)
2032         // cos(arc/2) = cos(90-(tri/2)) = sin(tri/2)
2033         // 2theta = tri, therefore theta = tri/2
2034         // cos(tri/2)^2 = (1+cos(tri)) / 2.0 = (1+cos_2theta)/2.0
2035         // sin(tri/2)^2 = (1-cos(tri)) / 2.0 = (1-cos_2theta)/2.0
2036         // sin(arc/2) = cos(tri/2) = sqrt((1+cos_2theta)/2.0)
2037         // cos(arc/2) = sin(tri/2) = sqrt((1-cos_2theta)/2.0)
2038         // We compute cos(arc/2) here as we need it in either case below
2039         double coshalfarc = Math.sqrt((1.0 - cos_2theta) / 2.0);
2040         boolean ccw = (ty0 - cy) * (tx1 - cx) > (ty1 - cy) * (tx0 - cx);
2041         // If the arc covers more than 90 degrees then we must use 2
2042         // cubic beziers to get a decent approximation.
2043         // arc = 180-tri
2044         // arc = 180-2*theta
2045         // arc > 90 implies 2*theta < 90
2046         // 2*theta < 90 implies cos_2theta > 0
2047         // So, we need 2 cubics if cos_2theta > 0
2048         if (cos_2theta <= 0.0) {
2049             // 1 cubic bezier
2050             double sinhalfarc = Math.sqrt((1.0 + cos_2theta) / 2.0);
2051             double cv = 4.0 / 3.0 * sinhalfarc / (1.0 + coshalfarc);
2052             if (ccw) cv = -cv;
2053             double cpx0 = tx0 - cv * (ty0 - cy);
2054             double cpy0 = ty0 + cv * (tx0 - cx);
2055             double cpx1 = tx1 + cv * (ty1 - cy);
2056             double cpy1 = ty1 - cv * (tx1 - cx);
2057             bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1);
2058         } else {
2059             // 2 cubic beziers
2060             // We need sin(arc/4) and cos(arc/4)
2061             // We computed cos(arc/2), so we can compute them as follows:
2062             // sin(arc/4) = sqrt((1 - cos(arc/2)) / 2)
2063             // cos(arc/4) = sart((1 + cos(arc/2)) / 2)
2064             double sinqtrarc = Math.sqrt((1.0 - coshalfarc) / 2.0);
2065             double cosqtrarc = Math.sqrt((1.0 + coshalfarc) / 2.0);
2066             double cv = 4.0 / 3.0 * sinqtrarc / (1.0 + cosqtrarc);
2067             if (ccw) cv = -cv;
2068             double midratio = radius / Math.sqrt(lenratioden);
2069             double midarcx = cx + (x1 - mx) * midratio;
2070             double midarcy = cy + (y1 - my) * midratio;
2071             double cpx0 = tx0 - cv * (ty0 - cy);
2072             double cpy0 = ty0 + cv * (tx0 - cx);
2073             double cpx1 = midarcx + cv * (midarcy - cy);
2074             double cpy1 = midarcy - cv * (midarcx - cx);
2075             bezierCurveTo(cpx0, cpy0, cpx1, cpy1, midarcx, midarcy);
2076             cpx0 = midarcx - cv * (midarcy - cy);
2077             cpy0 = midarcy + cv * (midarcx - cx);
2078             cpx1 = tx1 + cv * (ty1 - cy);
2079             cpy1 = ty1 - cv * (tx1 - cx);
2080             bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1);
2081         }
2082         return true;
2083     }
2084 
2085     /**
2086      * Adds path elements to the current path to make an arc that uses Euclidean
2087      * degrees. This Euclidean orientation sweeps from East to North, then West,
2088      * then South, then back to East.
2089      * The coordinates are transformed by the current transform as they are
2090      * added to the path and unaffected by subsequent changes to the transform.
2091      * The current path is a <a href="#path-attr">path attribute</a>
2092      * used for any of the path methods as specified in the
2093      * <a href="#attr-ops-table">Rendering Attributes Table</a>
2094      * and <b>is not affected</b> by the {@link #save()} and
2095      * {@link #restore()} operations.
2096      *
2097      * @param centerX the center x position of the arc.
2098      * @param centerY the center y position of the arc.
2099      * @param radiusX the x radius of the arc.
2100      * @param radiusY the y radius of the arc.
2101      * @param startAngle the starting angle of the arc in the range {@code 0-360.0}
2102      * @param length  the length of the baseline of the arc.
2103      */
2104     public void arc(double centerX, double centerY,
2105                     double radiusX, double radiusY,
2106                     double startAngle, double length)
2107     {
2108         Arc2D arc = new Arc2D((float) (centerX - radiusX), // x
2109                               (float) (centerY - radiusY), // y
2110                               (float) (radiusX * 2.0), // w
2111                               (float) (radiusY * 2.0), // h
2112                               (float) startAngle,
2113                               (float) length,
2114                               Arc2D.OPEN);
2115         path.append(arc.getPathIterator(curState.transform), true);
2116         markPathDirty();
2117     }
2118 
2119     /**
2120      * Adds path elements to the current path to make a rectangle.
2121      * The coordinates are transformed by the current transform as they are
2122      * added to the path and unaffected by subsequent changes to the transform.
2123      * The current path is a <a href="#path-attr">path attribute</a>
2124      * used for any of the path methods as specified in the
2125      * <a href="#attr-ops-table">Rendering Attributes Table</a>
2126      * and <b>is not affected</b> by the {@link #save()} and
2127      * {@link #restore()} operations.
2128      *
2129      * @param x x position of the upper left corner of the rectangle.
2130      * @param y y position of the upper left corner of the rectangle.
2131      * @param w width of the rectangle.
2132      * @param h height of the rectangle.
2133      */
2134     public void rect(double x, double y, double w, double h) {
2135         coords[0] = (float) x;
2136         coords[1] = (float) y;
2137         coords[2] = (float) w;
2138         coords[3] = (float) 0;
2139         coords[4] = (float) 0;
2140         coords[5] = (float) h;
2141         curState.transform.deltaTransform(coords, 0, coords, 0, 3);
2142         float x0 = coords[0] + (float) curState.transform.getMxt();
2143         float y0 = coords[1] + (float) curState.transform.getMyt();
2144         float dx1 = coords[2];
2145         float dy1 = coords[3];
2146         float dx2 = coords[4];
2147         float dy2 = coords[5];
2148         path.moveTo(x0, y0);
2149         path.lineTo(x0+dx1, y0+dy1);
2150         path.lineTo(x0+dx1+dx2, y0+dy1+dy2);
2151         path.lineTo(x0+dx2, y0+dy2);
2152         path.closePath();
2153         markPathDirty();
2154 //        path.moveTo(x0, y0); // not needed, closepath leaves pen at moveto
2155     }
2156 
2157     /**
2158      * Appends an SVG Path string to the current path. If there is no current
2159      * path the string must then start with either type of move command.
2160      * A {@code null} value or incorrect SVG path will be ignored.
2161      * The coordinates are transformed by the current transform as they are
2162      * added to the path and unaffected by subsequent changes to the transform.
2163      * The current path is a <a href="#path-attr">path attribute</a>
2164      * used for any of the path methods as specified in the
2165      * <a href="#attr-ops-table">Rendering Attributes Table</a>
2166      * and <b>is not affected</b> by the {@link #save()} and
2167      * {@link #restore()} operations.
2168      *
2169      * @param svgpath the SVG Path string.
2170      */
2171     public void appendSVGPath(String svgpath) {
2172         if (svgpath == null) return;
2173         boolean prependMoveto = true;
2174         boolean skipMoveto = true;
2175         for (int i = 0; i < svgpath.length(); i++) {
2176             switch (svgpath.charAt(i)) {
2177                 case ' ':
2178                 case '\t':
2179                 case '\r':
2180                 case '\n':
2181                     continue;
2182                 case 'M':
2183                     prependMoveto = skipMoveto = false;
2184                     break;
2185                 case 'm':
2186                     if (path.getNumCommands() == 0) {
2187                         // An initial relative moveTo becomes absolute
2188                         prependMoveto = false;
2189                     }
2190                     // Even if we prepend an initial moveTo in the temp
2191                     // path, we do not want to delete the resulting initial
2192                     // moveTo because the relative moveto will be folded
2193                     // into it by an optimization in the Path2D object.
2194                     skipMoveto = false;
2195                     break;
2196             }
2197             break;
2198         }
2199         Path2D p2d = new Path2D();
2200         if (prependMoveto && path.getNumCommands() > 0) {
2201             float x0, y0;
2202             if (curState.transform.isTranslateOrIdentity()) {
2203                 x0 = (float) (path.getCurrentX() - curState.transform.getMxt());
2204                 y0 = (float) (path.getCurrentY() - curState.transform.getMyt());
2205             } else {
2206                 coords[0] = path.getCurrentX();
2207                 coords[1] = path.getCurrentY();
2208                 try {
2209                     curState.transform.inverseTransform(coords, 0, coords, 0, 1);
2210                 } catch (NoninvertibleTransformException e) {
2211                 }
2212                 x0 = coords[0];
2213                 y0 = coords[1];
2214             }
2215             p2d.moveTo(x0, y0);
2216         } else {
2217             skipMoveto = false;
2218         }
2219         try {
2220             p2d.appendSVGPath(svgpath);
2221             PathIterator pi = p2d.getPathIterator(curState.transform);
2222             if (skipMoveto) {
2223                 // We need to delete the initial moveto and let the path
2224                 // extend from the actual existing geometry.
2225                 pi.next();
2226             }
2227             path.append(pi, false);
2228         } catch (IllegalArgumentException | IllegalPathStateException ex) {
2229             //Ignore incorrect path
2230         }
2231     }
2232 
2233     /**
2234      * Closes the path.
2235      * The current path is a <a href="#path-attr">path attribute</a>
2236      * used for any of the path methods as specified in the
2237      * <a href="#attr-ops-table">Rendering Attributes Table</a>
2238      * and <b>is not affected</b> by the {@link #save()} and
2239      * {@link #restore()} operations.
2240      */
2241     public void closePath() {
2242         if (path.getNumCommands() > 0) {
2243             path.closePath();
2244             markPathDirty();
2245         }
2246     }
2247 
2248     /**
2249      * Fills the path with the current fill paint.
2250      * <p>
2251      * This method will be affected by any of the
2252      * <a href="#comm-attr">global common</a>,
2253      * <a href="#fill-attr">fill</a>,
2254      * or <a href="#path-attr">path</a>
2255      * attributes as specified in the
2256      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2257      * Note that the path segments were transformed as they were originally
2258      * added to the current path so the current transform will not affect
2259      * those path segments again, but it may affect other attributes in
2260      * affect at the time of the {@code fill()} operation.
2261      * </p>
2262      */
2263     public void fill() {
2264         writePath(NGCanvas.FILL_PATH);
2265     }
2266 
2267     /**
2268      * Strokes the path with the current stroke paint.
2269      * <p>
2270      * This method will be affected by any of the
2271      * <a href="#comm-attr">global common</a>,
2272      * <a href="#strk-attr">stroke</a>,
2273      * or <a href="#path-attr">path</a>
2274      * attributes as specified in the
2275      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2276      * Note that the path segments were transformed as they were originally
2277      * added to the current path so the current transform will not affect
2278      * those path segments again, but it may affect other attributes in
2279      * affect at the time of the {@code stroke()} operation.
2280      * </p>
2281      */
2282     public void stroke() {
2283         writePath(NGCanvas.STROKE_PATH);
2284     }
2285 
2286     /**
2287      * Intersects the current clip with the current path and applies it to
2288      * subsequent rendering operation as an anti-aliased mask.
2289      * The current clip is a <a href="#comm-attr">common attribute</a>
2290      * used for nearly all rendering operations as specified in the
2291      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2292      * <p>
2293      * This method will itself be affected only by the
2294      * <a href="#path-attr">path</a>
2295      * attributes as specified in the
2296      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2297      * Note that the path segments were transformed as they were originally
2298      * added to the current path so the current transform will not affect
2299      * those path segments again, but it may affect other attributes in
2300      * affect at the time of the {@code stroke()} operation.
2301      * </p>
2302      */
2303     public void clip() {
2304         Path2D clip = new Path2D(path);
2305         clipStack.addLast(clip);
2306         curState.numClipPaths++;
2307         GrowableDataBuffer buf = getBuffer();
2308         buf.putByte(NGCanvas.PUSH_CLIP);
2309         buf.putObject(clip);
2310     }
2311 
2312     /**
2313      * Returns true if the the given x,y point is inside the path.
2314      *
2315      * @param x the X coordinate to use for the check.
2316      * @param y the Y coordinate to use for the check.
2317      * @return true if the point given is inside the path, false
2318      * otherwise.
2319      */
2320     public boolean isPointInPath(double x, double y) {
2321         // TODO: HTML5 considers points on the path to be inside, but we
2322         // implement a halfin-halfout approach...
2323         return path.contains((float) x, (float) y);
2324     }
2325 
2326     /**
2327      * Clears a portion of the canvas with a transparent color value.
2328      * <p>
2329      * This method will be affected only by the current transform, clip,
2330      * and effect.
2331      * </p>
2332      *
2333      * @param x X position of the upper left corner of the rectangle.
2334      * @param y Y position of the upper left corner of the rectangle.
2335      * @param w width of the rectangle.
2336      * @param h height of the rectangle.
2337      */
2338     public void clearRect(double x, double y, double w, double h) {
2339         if (w != 0 && h != 0) {
2340             resetIfCovers(null, x, y, w, h);
2341             writeOp4(x, y, w, h, NGCanvas.CLEAR_RECT);
2342         }
2343     }
2344 
2345     /**
2346      * Fills a rectangle using the current fill paint.
2347      * <p>
2348      * This method will be affected by any of the
2349      * <a href="#comm-attr">global common</a>
2350      * or <a href="#fill-attr">fill</a>
2351      * attributes as specified in the
2352      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2353      * </p>
2354      *
2355      * @param x the X position of the upper left corner of the rectangle.
2356      * @param y the Y position of the upper left corner of the rectangle.
2357      * @param w the width of the rectangle.
2358      * @param h the height of the rectangle.
2359      */
2360     public void fillRect(double x, double y, double w, double h) {
2361         if (w != 0 && h != 0) {
2362             resetIfCovers(this.curState.fill, x, y, w, h);
2363             writeOp4(x, y, w, h, NGCanvas.FILL_RECT);
2364         }
2365     }
2366 
2367     /**
2368      * Strokes a rectangle using the current stroke paint.
2369      * <p>
2370      * This method will be affected by any of the
2371      * <a href="#comm-attr">global common</a>
2372      * or <a href="#strk-attr">stroke</a>
2373      * attributes as specified in the
2374      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2375      * </p>
2376      *
2377      * @param x the X position of the upper left corner of the rectangle.
2378      * @param y the Y position of the upper left corner of the rectangle.
2379      * @param w the width of the rectangle.
2380      * @param h the height of the rectangle.
2381      */
2382     public void strokeRect(double x, double y, double w, double h) {
2383         if (w != 0 || h != 0) {
2384             writeOp4(x, y, w, h, NGCanvas.STROKE_RECT);
2385         }
2386     }
2387 
2388     /**
2389      * Fills an oval using the current fill paint.
2390      * <p>
2391      * This method will be affected by any of the
2392      * <a href="#comm-attr">global common</a>
2393      * or <a href="#fill-attr">fill</a>
2394      * attributes as specified in the
2395      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2396      * </p>
2397      *
2398      * @param x the X coordinate of the upper left bound of the oval.
2399      * @param y the Y coordinate of the upper left bound of the oval.
2400      * @param w the width at the center of the oval.
2401      * @param h the height at the center of the oval.
2402      */
2403     public void fillOval(double x, double y, double w, double h) {
2404         if (w != 0 && h != 0) {
2405             writeOp4(x, y, w, h, NGCanvas.FILL_OVAL);
2406         }
2407     }
2408 
2409     /**
2410      * Strokes an oval using the current stroke paint.
2411      * <p>
2412      * This method will be affected by any of the
2413      * <a href="#comm-attr">global common</a>
2414      * or <a href="#strk-attr">stroke</a>
2415      * attributes as specified in the
2416      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2417      * </p>
2418      *
2419      * @param x the X coordinate of the upper left bound of the oval.
2420      * @param y the Y coordinate of the upper left bound of the oval.
2421      * @param w the width at the center of the oval.
2422      * @param h the height at the center of the oval.
2423      */
2424     public void strokeOval(double x, double y, double w, double h) {
2425         if (w != 0 || h != 0) {
2426             writeOp4(x, y, w, h, NGCanvas.STROKE_OVAL);
2427         }
2428     }
2429 
2430     /**
2431      * Fills an arc using the current fill paint. A {@code null} ArcType or
2432      * non positive width or height will cause the render command to be ignored.
2433      * <p>
2434      * This method will be affected by any of the
2435      * <a href="#comm-attr">global common</a>
2436      * or <a href="#fill-attr">fill</a>
2437      * attributes as specified in the
2438      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2439      * </p>
2440      *
2441      * @param x the X coordinate of the arc.
2442      * @param y the Y coordinate of the arc.
2443      * @param w the width of the arc.
2444      * @param h the height of the arc.
2445      * @param startAngle the starting angle of the arc in degrees.
2446      * @param arcExtent the angular extent of the arc in degrees.
2447      * @param closure closure type (Round, Chord, Open) or null.
2448      */
2449     public void fillArc(double x, double y, double w, double h,
2450                         double startAngle, double arcExtent, ArcType closure)
2451     {
2452         if (w != 0 && h != 0 && closure != null) {
2453             writeArcType(closure);
2454             writeOp6(x, y, w, h, startAngle, arcExtent, NGCanvas.FILL_ARC);
2455         }
2456     }
2457 
2458     /**
2459      * Strokes an Arc using the current stroke paint. A {@code null} ArcType or
2460      * non positive width or height will cause the render command to be ignored.
2461      * <p>
2462      * This method will be affected by any of the
2463      * <a href="#comm-attr">global common</a>
2464      * or <a href="#strk-attr">stroke</a>
2465      * attributes as specified in the
2466      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2467      * </p>
2468      *
2469      * @param x the X coordinate of the arc.
2470      * @param y the Y coordinate of the arc.
2471      * @param w the width of the arc.
2472      * @param h the height of the arc.
2473      * @param startAngle the starting angle of the arc in degrees.
2474      * @param arcExtent arcExtent the angular extent of the arc in degrees.
2475      * @param closure closure type (Round, Chord, Open) or null
2476      */
2477     public void strokeArc(double x, double y, double w, double h,
2478                         double startAngle, double arcExtent, ArcType closure)
2479     {
2480         if (w != 0 && h != 0 && closure != null) {
2481             writeArcType(closure);
2482             writeOp6(x, y, w, h, startAngle, arcExtent, NGCanvas.STROKE_ARC);
2483         }
2484     }
2485 
2486     /**
2487      * Fills a rounded rectangle using the current fill paint.
2488      * <p>
2489      * This method will be affected by any of the
2490      * <a href="#comm-attr">global common</a>
2491      * or <a href="#fill-attr">fill</a>
2492      * attributes as specified in the
2493      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2494      * </p>
2495      *
2496      * @param x the X coordinate of the upper left bound of the oval.
2497      * @param y the Y coordinate of the upper left bound of the oval.
2498      * @param w the width at the center of the oval.
2499      * @param h the height at the center of the oval.
2500      * @param arcWidth the arc width of the rectangle corners.
2501      * @param arcHeight the arc height of the rectangle corners.
2502      */
2503     public void fillRoundRect(double x, double y, double w, double h,
2504                               double arcWidth, double arcHeight)
2505     {
2506         if (w != 0 && h != 0) {
2507             writeOp6(x, y, w, h, arcWidth, arcHeight, NGCanvas.FILL_ROUND_RECT);
2508         }
2509     }
2510 
2511     /**
2512      * Strokes a rounded rectangle using the current stroke paint.
2513      * <p>
2514      * This method will be affected by any of the
2515      * <a href="#comm-attr">global common</a>
2516      * or <a href="#strk-attr">stroke</a>
2517      * attributes as specified in the
2518      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2519      * </p>
2520      *
2521      * @param x the X coordinate of the upper left bound of the oval.
2522      * @param y the Y coordinate of the upper left bound of the oval.
2523      * @param w the width at the center of the oval.
2524      * @param h the height at the center of the oval.
2525      * @param arcWidth the arc width of the rectangle corners.
2526      * @param arcHeight the arc height of the rectangle corners.
2527      */
2528     public void strokeRoundRect(double x, double y, double w, double h,
2529                               double arcWidth, double arcHeight)
2530     {
2531         if (w != 0 && h != 0) {
2532             writeOp6(x, y, w, h, arcWidth, arcHeight, NGCanvas.STROKE_ROUND_RECT);
2533         }
2534     }
2535 
2536     /**
2537      * Strokes a line using the current stroke paint.
2538      * <p>
2539      * This method will be affected by any of the
2540      * <a href="#comm-attr">global common</a>
2541      * or <a href="#strk-attr">stroke</a>
2542      * attributes as specified in the
2543      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2544      * </p>
2545      *
2546      * @param x1 the X coordinate of the starting point of the line.
2547      * @param y1 the Y coordinate of the starting point of the line.
2548      * @param x2 the X coordinate of the ending point of the line.
2549      * @param y2 the Y coordinate of the ending point of the line.
2550      */
2551     public void strokeLine(double x1, double y1, double x2, double y2) {
2552         writeOp4(x1, y1, x2, y2, NGCanvas.STROKE_LINE);
2553     }
2554 
2555     /**
2556      * Fills a polygon with the given points using the currently set fill paint.
2557      * A {@code null} value for any of the arrays will be ignored and nothing will be drawn.
2558      * <p>
2559      * This method will be affected by any of the
2560      * <a href="#comm-attr">global common</a>,
2561      * <a href="#fill-attr">fill</a>,
2562      * or <a href="#path-attr">Fill Rule</a>
2563      * attributes as specified in the
2564      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2565      * </p>
2566      *
2567      * @param xPoints array containing the x coordinates of the polygon's points or null.
2568      * @param yPoints array containing the y coordinates of the polygon's points or null.
2569      * @param nPoints the number of points that make the polygon.
2570      */
2571     public void fillPolygon(double xPoints[], double yPoints[], int nPoints) {
2572         if (nPoints >= 3) {
2573             writePoly(xPoints, yPoints, nPoints, true, NGCanvas.FILL_PATH);
2574         }
2575     }
2576 
2577     /**
2578      * Strokes a polygon with the given points using the currently set stroke paint.
2579      * A {@code null} value for any of the arrays will be ignored and nothing will be drawn.
2580      * <p>
2581      * This method will be affected by any of the
2582      * <a href="#comm-attr">global common</a>
2583      * or <a href="#strk-attr">stroke</a>
2584      * attributes as specified in the
2585      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2586      * </p>
2587      *
2588      * @param xPoints array containing the x coordinates of the polygon's points or null.
2589      * @param yPoints array containing the y coordinates of the polygon's points or null.
2590      * @param nPoints the number of points that make the polygon.
2591      */
2592     public void strokePolygon(double xPoints[], double yPoints[], int nPoints) {
2593         if (nPoints >= 2) {
2594             writePoly(xPoints, yPoints, nPoints, true, NGCanvas.STROKE_PATH);
2595         }
2596     }
2597 
2598     /**
2599      * Strokes a polyline with the given points using the currently set stroke
2600      * paint attribute.
2601      * A {@code null} value for any of the arrays will be ignored and nothing will be drawn.
2602      * <p>
2603      * This method will be affected by any of the
2604      * <a href="#comm-attr">global common</a>
2605      * or <a href="#strk-attr">stroke</a>
2606      * attributes as specified in the
2607      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2608      * </p>
2609      *
2610      * @param xPoints array containing the x coordinates of the polyline's points or null.
2611      * @param yPoints array containing the y coordinates of the polyline's points or null.
2612      * @param nPoints the number of points that make the polyline.
2613      */
2614     public void strokePolyline(double xPoints[], double yPoints[], int nPoints) {
2615         if (nPoints >= 2) {
2616             writePoly(xPoints, yPoints, nPoints, false, NGCanvas.STROKE_PATH);
2617         }
2618     }
2619 
2620     /**
2621      * Draws an image at the given x, y position using the width
2622      * and height of the given image.
2623      * A {@code null} image value or an image still in progress will be ignored.
2624      * <p>
2625      * This method will be affected by any of the
2626      * <a href="#comm-attr">global common</a>
2627      * attributes as specified in the
2628      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2629      * </p>
2630      *
2631      * @param img the image to be drawn or null.
2632      * @param x the X coordinate on the destination for the upper left of the image.
2633      * @param y the Y coordinate on the destination for the upper left of the image.
2634      */
2635     public void drawImage(Image img, double x, double y) {
2636         if (img == null) return;
2637         double sw = img.getWidth();
2638         double sh = img.getHeight();
2639         writeImage(img, x, y, sw, sh);
2640     }
2641 
2642     /**
2643      * Draws an image into the given destination rectangle of the canvas. The
2644      * Image is scaled to fit into the destination rectagnle.
2645      * A {@code null} image value or an image still in progress will be ignored.
2646      * <p>
2647      * This method will be affected by any of the
2648      * <a href="#comm-attr">global common</a>
2649      * attributes as specified in the
2650      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2651      * </p>
2652      *
2653      * @param img the image to be drawn or null.
2654      * @param x the X coordinate on the destination for the upper left of the image.
2655      * @param y the Y coordinate on the destination for the upper left of the image.
2656      * @param w the width of the destination rectangle.
2657      * @param h the height of the destination rectangle.
2658      */
2659     public void drawImage(Image img, double x, double y, double w, double h) {
2660         writeImage(img, x, y, w, h);
2661     }
2662 
2663     /**
2664      * Draws the specified source rectangle of the given image to the given
2665      * destination rectangle of the Canvas.
2666      * A {@code null} image value or an image still in progress will be ignored.
2667      * <p>
2668      * This method will be affected by any of the
2669      * <a href="#comm-attr">global common</a>
2670      * attributes as specified in the
2671      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2672      * </p>
2673      *
2674      * @param img the image to be drawn or null.
2675      * @param sx the source rectangle's X coordinate position.
2676      * @param sy the source rectangle's Y coordinate position.
2677      * @param sw the source rectangle's width.
2678      * @param sh the source rectangle's height.
2679      * @param dx the destination rectangle's X coordinate position.
2680      * @param dy the destination rectangle's Y coordinate position.
2681      * @param dw the destination rectangle's width.
2682      * @param dh the destination rectangle's height.
2683      */
2684     public void drawImage(Image img,
2685                           double sx, double sy, double sw, double sh,
2686                           double dx, double dy, double dw, double dh)
2687     {
2688         writeImage(img, dx, dy, dw, dh, sx, sy, sw, sh);
2689     }
2690 
2691     private PixelWriter writer;
2692     /**
2693      * Returns a {@link PixelWriter} object that can be used to modify
2694      * the pixels of the {@link Canvas} associated with this
2695      * {@code GraphicsContext}.
2696      * All coordinates in the {@code PixelWriter} methods on the returned
2697      * object will be in device space since they refer directly to pixels
2698      * and no other rendering attributes will be applied when modifying
2699      * pixels using this object.
2700      *
2701      * @return the {@code PixelWriter} for modifying the pixels of this
2702      *         {@code Canvas}
2703      */
2704     public PixelWriter getPixelWriter() {
2705         if (writer == null) {
2706             writer = new PixelWriter() {
2707                 @Override
2708                 public PixelFormat<ByteBuffer> getPixelFormat() {
2709                     return PixelFormat.getByteBgraPreInstance();
2710                 }
2711 
2712                 private BytePixelSetter getSetter() {
2713                     return ByteBgraPre.setter;
2714                 }
2715 
2716                 @Override
2717                 public void setArgb(int x, int y, int argb) {
2718                     GrowableDataBuffer buf = getBuffer();
2719                     buf.putByte(NGCanvas.PUT_ARGB);
2720                     buf.putInt(x);
2721                     buf.putInt(y);
2722                     buf.putInt(argb);
2723                 }
2724 
2725                 @Override
2726                 public void setColor(int x, int y, Color c) {
2727                     if (c == null) throw new NullPointerException("Color cannot be null");
2728                     int a = (int) Math.round(c.getOpacity() * 255.0);
2729                     int r = (int) Math.round(c.getRed() * 255.0);
2730                     int g = (int) Math.round(c.getGreen() * 255.0);
2731                     int b = (int) Math.round(c.getBlue() * 255.0);
2732                     setArgb(x, y, (a << 24) | (r << 16) | (g << 8) | b);
2733                 }
2734 
2735                 private void writePixelBuffer(int x, int y, int w, int h,
2736                                               byte[] pixels)
2737                 {
2738                     GrowableDataBuffer buf = getBuffer();
2739                     buf.putByte(NGCanvas.PUT_ARGBPRE_BUF);
2740                     buf.putInt(x);
2741                     buf.putInt(y);
2742                     buf.putInt(w);
2743                     buf.putInt(h);
2744                     buf.putObject(pixels);
2745                 }
2746 
2747                 private int[] checkBounds(int x, int y, int w, int h,
2748                                           PixelFormat<? extends Buffer> pf,
2749                                           int scan)
2750                 {
2751                     // assert (w >= 0 && h >= 0) - checked by caller
2752                     int cw = (int) Math.ceil(theCanvas.getWidth());
2753                     int ch = (int) Math.ceil(theCanvas.getHeight());
2754                     if (x >= 0 && y >= 0 && x+w <= cw && y+h <= ch) {
2755                         return null;
2756                     }
2757                     int offset = 0;
2758                     if (x < 0) {
2759                         w += x;
2760                         if (w < 0) return null;
2761                         if (pf != null) {
2762                             switch (pf.getType()) {
2763                                 case BYTE_BGRA:
2764                                 case BYTE_BGRA_PRE:
2765                                     offset -= x * 4;
2766                                     break;
2767                                 case BYTE_RGB:
2768                                     offset -= x * 3;
2769                                     break;
2770                                 case BYTE_INDEXED:
2771                                 case INT_ARGB:
2772                                 case INT_ARGB_PRE:
2773                                     offset -= x;
2774                                     break;
2775                                 default:
2776                                     throw new InternalError("unknown Pixel Format");
2777                             }
2778                         }
2779                         x = 0;
2780                     }
2781                     if (y < 0) {
2782                         h += y;
2783                         if (h < 0) return null;
2784                         offset -= y * scan;
2785                         y = 0;
2786                     }
2787                     if (x + w > cw) {
2788                         w = cw - x;
2789                         if (w < 0) return null;
2790                     }
2791                     if (y + h > ch) {
2792                         h = ch - y;
2793                         if (h < 0) return null;
2794                     }
2795                     return new int[] {
2796                         x, y, w, h, offset
2797                     };
2798                 }
2799 
2800                 @Override
2801                 public <T extends Buffer> void
2802                     setPixels(int x, int y, int w, int h,
2803                               PixelFormat<T> pixelformat,
2804                               T buffer, int scan)
2805                 {
2806                     if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null");
2807                     if (buffer == null) throw new NullPointerException("Buffer cannot be null");
2808                     if (w <= 0 || h <= 0) return;
2809                     int offset = buffer.position();
2810                     int adjustments[] = checkBounds(x, y, w, h,
2811                                                     pixelformat, scan);
2812                     if (adjustments != null) {
2813                         x = adjustments[0];
2814                         y = adjustments[1];
2815                         w = adjustments[2];
2816                         h = adjustments[3];
2817                         offset += adjustments[4];
2818                     }
2819 
2820                     byte pixels[] = new byte[w * h * 4];
2821                     ByteBuffer dst = ByteBuffer.wrap(pixels);
2822 
2823                     PixelGetter<T> getter = PixelUtils.getGetter(pixelformat);
2824                     PixelConverter<T, ByteBuffer> converter =
2825                         PixelUtils.getConverter(getter, getSetter());
2826                     converter.convert(buffer, offset, scan,
2827                                       dst, 0, w * 4,
2828                                       w, h);
2829                     writePixelBuffer(x, y, w, h, pixels);
2830                 }
2831 
2832                 @Override
2833                 public void setPixels(int x, int y, int w, int h,
2834                                       PixelFormat<ByteBuffer> pixelformat,
2835                                       byte[] buffer, int offset, int scanlineStride)
2836                 {
2837                     if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null");
2838                     if (buffer == null) throw new NullPointerException("Buffer cannot be null");
2839                     if (w <= 0 || h <= 0) return;
2840                     int adjustments[] = checkBounds(x, y, w, h,
2841                                                     pixelformat, scanlineStride);
2842                     if (adjustments != null) {
2843                         x = adjustments[0];
2844                         y = adjustments[1];
2845                         w = adjustments[2];
2846                         h = adjustments[3];
2847                         offset += adjustments[4];
2848                     }
2849 
2850                     byte pixels[] = new byte[w * h * 4];
2851 
2852                     BytePixelGetter getter = PixelUtils.getByteGetter(pixelformat);
2853                     ByteToBytePixelConverter converter =
2854                         PixelUtils.getB2BConverter(getter, getSetter());
2855                     converter.convert(buffer, offset, scanlineStride,
2856                                       pixels, 0, w * 4,
2857                                       w, h);
2858                     writePixelBuffer(x, y, w, h, pixels);
2859                 }
2860 
2861                 @Override
2862                 public void setPixels(int x, int y, int w, int h,
2863                                       PixelFormat<IntBuffer> pixelformat,
2864                                       int[] buffer, int offset, int scanlineStride)
2865                 {
2866                     if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null");
2867                     if (buffer == null) throw new NullPointerException("Buffer cannot be null");
2868                     if (w <= 0 || h <= 0) return;
2869                     int adjustments[] = checkBounds(x, y, w, h,
2870                                                     pixelformat, scanlineStride);
2871                     if (adjustments != null) {
2872                         x = adjustments[0];
2873                         y = adjustments[1];
2874                         w = adjustments[2];
2875                         h = adjustments[3];
2876                         offset += adjustments[4];
2877                     }
2878 
2879                     byte pixels[] = new byte[w * h * 4];
2880 
2881                     IntPixelGetter getter = PixelUtils.getIntGetter(pixelformat);
2882                     IntToBytePixelConverter converter =
2883                         PixelUtils.getI2BConverter(getter, getSetter());
2884                     converter.convert(buffer, offset, scanlineStride,
2885                                       pixels, 0, w * 4,
2886                                       w, h);
2887                     writePixelBuffer(x, y, w, h, pixels);
2888                 }
2889 
2890                 @Override
2891                 public void setPixels(int dstx, int dsty, int w, int h,
2892                                       PixelReader reader, int srcx, int srcy)
2893                 {
2894                     if (reader == null) throw new NullPointerException("Reader cannot be null");
2895                     if (w <= 0 || h <= 0) return;
2896                     int adjustments[] = checkBounds(dstx, dsty, w, h, null, 0);
2897                     if (adjustments != null) {
2898                         int newx = adjustments[0];
2899                         int newy = adjustments[1];
2900                         srcx += newx - dstx;
2901                         srcy += newy - dsty;
2902                         dstx = newx;
2903                         dsty = newy;
2904                         w = adjustments[2];
2905                         h = adjustments[3];
2906                     }
2907 
2908                     byte pixels[] = new byte[w * h * 4];
2909                     reader.getPixels(srcx, srcy, w, h,
2910                                      PixelFormat.getByteBgraPreInstance(),
2911                                      pixels, 0, w * 4);
2912                     writePixelBuffer(dstx, dsty, w, h, pixels);
2913                 }
2914             };
2915         }
2916         return writer;
2917     }
2918 
2919     /**
2920      * Sets the effect to be applied after the next draw call, or null to
2921      * disable effects.
2922      * The current effect is a <a href="#comm-attr">common attribute</a>
2923      * used for nearly all rendering operations as specified in the
2924      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2925      *
2926      * @param e the effect to use, or null to disable effects
2927      */
2928     public void setEffect(Effect e) {
2929         GrowableDataBuffer buf = getBuffer();
2930         buf.putByte(NGCanvas.EFFECT);
2931         if (e == null) {
2932             curState.effect = null;
2933             buf.putObject(null);
2934         } else {
2935             curState.effect = EffectHelper.copy(e);
2936             EffectHelper.sync(curState.effect);
2937             buf.putObject(EffectHelper.getPeer(curState.effect));
2938         }
2939     }
2940 
2941     /**
2942      * Gets a copy of the effect to be applied after the next draw call.
2943      * A null return value means that no effect will be applied after subsequent
2944      * rendering calls.
2945      * The current effect is a <a href="#comm-attr">common attribute</a>
2946      * used for nearly all rendering operations as specified in the
2947      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2948      *
2949      * @param e an {@code Effect} object that may be used to store the
2950      *        copy of the current effect, if it is of a compatible type
2951      * @return the current effect used for all rendering calls,
2952      *         or null if there is no current effect
2953      */
2954     public Effect getEffect(Effect e) {
2955         return curState.effect == null ? null : EffectHelper.copy(curState.effect);
2956     }
2957 
2958     /**
2959      * Applies the given effect to the entire bounds of the canvas and stores
2960      * the result back into the same canvas.
2961      * A {@code null} value will be ignored.
2962      * The effect will be applied without any other rendering attributes and
2963      * under an Identity coordinate transform.
2964      * Since the effect is applied to the entire bounds of the canvas, some
2965      * effects may have a confusing result, such as a Reflection effect
2966      * that will apply its reflection off of the bottom of the canvas even if
2967      * only a portion of the canvas has been rendered to and will not be
2968      * visible unless a negative offset is used to bring the reflection back
2969      * into view.
2970      *
2971      * @param e the effect to apply onto the entire destination or null.
2972      */
2973     public void applyEffect(Effect e) {
2974         if (e == null) return;
2975         GrowableDataBuffer buf = getBuffer();
2976         buf.putByte(NGCanvas.FX_APPLY_EFFECT);
2977         Effect effect = EffectHelper.copy(e);
2978         EffectHelper.sync(effect);
2979         buf.putObject(EffectHelper.getPeer(effect));
2980     }
2981 }