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