1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.canvas; 27 28 import com.sun.javafx.geom.Arc2D; 29 import com.sun.javafx.geom.IllegalPathStateException; 30 import com.sun.javafx.geom.Path2D; 31 import com.sun.javafx.geom.PathIterator; 32 import com.sun.javafx.geom.transform.Affine2D; 33 import com.sun.javafx.geom.transform.NoninvertibleTransformException; 34 import com.sun.javafx.image.*; 35 import com.sun.javafx.image.impl.ByteBgraPre; 36 import com.sun.javafx.sg.prism.GrowableDataBuffer; 37 import com.sun.javafx.sg.prism.NGCanvas; 38 import com.sun.javafx.tk.Toolkit; 39 import javafx.geometry.NodeOrientation; 40 import javafx.geometry.VPos; 41 import javafx.scene.effect.Blend; 42 import javafx.scene.effect.BlendMode; 43 import javafx.scene.effect.Effect; 44 import javafx.scene.image.Image; 45 import javafx.scene.image.PixelFormat; 46 import javafx.scene.image.PixelReader; 47 import javafx.scene.image.PixelWriter; 48 import javafx.scene.paint.Color; 49 import javafx.scene.paint.Paint; 50 import javafx.scene.shape.ArcType; 51 import javafx.scene.shape.FillRule; 52 import javafx.scene.shape.StrokeLineCap; 53 import javafx.scene.shape.StrokeLineJoin; 54 import javafx.scene.text.Font; 55 import javafx.scene.text.TextAlignment; 56 import javafx.scene.transform.Affine; 57 58 import java.nio.Buffer; 59 import java.nio.ByteBuffer; 60 import java.nio.IntBuffer; 61 import java.util.LinkedList; 62 63 /** 64 * This class is used to issue draw calls to a {@code Canvas} using a buffer. 65 * <p> 66 * Each call pushes the necessary parameters onto the buffer 67 * where they will be later rendered onto the image of the {@code Canvas} node 68 * by the rendering thread at the end of a pulse. 69 * <p> 70 * A {@code Canvas} only contains one {@code GraphicsContext}, and only one buffer. 71 * If it is not attached to any scene, then it can be modified by any thread, 72 * as long as it is only used from one thread at a time. Once a {@code Canvas} 73 * node is attached to a scene, it must be modified on the JavaFX Application 74 * Thread. 75 * <p> 76 * Calling any method on the {@code GraphicsContext} is considered modifying 77 * its corresponding {@code Canvas} and is subject to the same threading 78 * rules. 79 * <p> 80 * A {@code GraphicsContext} also manages a stack of state objects that can 81 * be saved or restored at anytime. 82 * <p> 83 * The {@code GraphicsContext} maintains the following rendering attributes 84 * which affect various subsets of the rendering methods: 85 * <table class="overviewSummary" style="width:80%; margin-left:auto; margin-right:auto"> 86 * <tr> 87 * <th class="colLast" style="width:15%">Attribute</th> 88 * <th class="colLast" style="width:10%; text-align:center">Save/Restore?</th> 89 * <th class="colLast" style="width:10%; text-align:center">Default value</th> 90 * <th class="colLast">Description</th> 91 * </tr> 92 * 93 * <tr><th colspan="3"><a name="comm-attr"><p align="center">Common Rendering Attributes</p></a></th></tr> 94 * <tr class="rowColor"> 95 * <td class="colLast" style="width:15%">{@link #clip() Clip}</td> 96 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 97 * <td class="colLast" style="width:10%; text-align:center">No clipping</td> 98 * <td class="colLast"> 99 * An anti-aliased intersection of various clip paths to which rendering 100 * is restricted. 101 * </td></tr> 102 * <tr class="altColor"> 103 * <td class="colLast" style="width:15%">{@link #setGlobalAlpha(double) Global Alpha}</td> 104 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 105 * <td class="colLast" style="width:10%; text-align:center">{@code 1.0}</td> 106 * <td class="colLast"> 107 * An opacity value that controls the visibility or fading of each rendering 108 * operation. 109 * </td></tr> 110 * <tr class="rowColor"> 111 * <td class="colLast" style="width:15%">{@link #setGlobalBlendMode(javafx.scene.effect.BlendMode) Global Blend Mode}</td> 112 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 113 * <td class="colLast" style="width:10%; text-align:center">{@link BlendMode#SRC_OVER SRC_OVER}</td> 114 * <td class="colLast"> 115 * A {@link BlendMode} enum value that controls how pixels from each rendering 116 * operation are composited into the existing image. 117 * </td></tr> 118 * <tr class="altColor"> 119 * <td class="colLast" style="width:15%">{@link #setTransform(javafx.scene.transform.Affine) Transform}</td> 120 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 121 * <td class="colLast" style="width:10%; text-align:center">{@code Identity}</td> 122 * <td class="colLast"> 123 * A 3x2 2D affine transformation matrix that controls how coordinates are 124 * mapped onto the logical pixels of the canvas image. 125 * </td></tr> 126 * <tr class="altColor"> 127 * <td class="colLast" style="width:15%">{@link #setEffect(javafx.scene.effect.Effect) Effect}</td> 128 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 129 * <td class="colLast" style="width:10%; text-align:center">{@code null}</td> 130 * <td class="colLast"> 131 * An {@link Effect} applied individually to each rendering operation. 132 * </td></tr> 133 * 134 * <tr><th colspan="3"><a name="fill-attr"><p align="center">Fill Attributes</p></a></th></tr> 135 * <tr class="rowColor"> 136 * <td class="colLast" style="width:15%">{@link #setFill(javafx.scene.paint.Paint) Fill Paint}</td> 137 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 138 * <td class="colLast" style="width:10%; text-align:center">{@link Color#BLACK BLACK}</td> 139 * <td class="colLast"> 140 * The {@link Paint} to be applied to the interior of shapes in a 141 * fill operation. 142 * </td></tr> 143 * 144 * <tr><th colspan="3"><a name="strk-attr"><p align="center">Stroke Attributes</p></a></th></tr> 145 * <tr class="rowColor"> 146 * <td class="colLast" style="width:15%">{@link #setStroke(javafx.scene.paint.Paint) Stroke Paint}</td> 147 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 148 * <td class="colLast" style="width:10%; text-align:center">{@link Color#BLACK BLACK}</td> 149 * <td class="colLast"> 150 * The {@link Paint} to be applied to the boundary of shapes in a 151 * stroke operation. 152 * </td></tr> 153 * <tr class="altColor"> 154 * <td class="colLast" style="width:15%">{@link #setLineWidth(double) Line Width}</td> 155 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 156 * <td class="colLast" style="width:10%; text-align:center">{@code 1.0}</td> 157 * <td class="colLast"> 158 * The width of the stroke applied to the boundary of shapes in a 159 * stroke operation. 160 * </td></tr> 161 * <tr class="rowColor"> 162 * <td class="colLast" style="width:15%">{@link #setLineCap(javafx.scene.shape.StrokeLineCap) Line Cap}</td> 163 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 164 * <td class="colLast" style="width:10%; text-align:center">{@link StrokeLineCap#SQUARE SQUARE}</td> 165 * <td class="colLast"> 166 * The style of the end caps applied to the beginnings and ends of each 167 * dash and/or subpath in a stroke operation. 168 * </td></tr> 169 * <tr class="altColor"> 170 * <td class="colLast" style="width:15%">{@link #setLineJoin(javafx.scene.shape.StrokeLineJoin) Line Join}</td> 171 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 172 * <td class="colLast" style="width:10%; text-align:center">{@link StrokeLineJoin#MITER MITER}</td> 173 * <td class="colLast"> 174 * The style of the joins applied between individual segments in the boundary 175 * paths of shapes in a stroke operation. 176 * </td></tr> 177 * <tr class="rowColor"> 178 * <td class="colLast" style="width:15%">{@link #setMiterLimit(double) Miter Limit}</td> 179 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 180 * <td class="colLast" style="width:10%; text-align:center">{@code 10.0}</td> 181 * <td class="colLast"> 182 * The ratio limit of how far a {@link StrokeLineJoin#MITER MITER} line join 183 * may extend in the direction of a sharp corner between segments in the 184 * boundary path of a shape, relative to the line width, before it is truncated 185 * to a {@link StrokeLineJoin#BEVEL BEVEL} join in a stroke operation. 186 * </td></tr> 187 * 188 * <tr><th colspan="3"><a name="text-attr"><p align="center">Text Attributes</p></a></th></tr> 189 * <tr class="rowColor"> 190 * <td class="colLast" style="width:15%">{@link #setFont(javafx.scene.text.Font) Font}</td> 191 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 192 * <td class="colLast" style="width:10%; text-align:center">{@link Font#getDefault() Default Font}</td> 193 * <td class="colLast"> 194 * The font used for all fill and stroke text operations. 195 * </td></tr> 196 * <tr class="altColor"> 197 * <td class="colLast" style="width:15%">{@link #setTextAlign(javafx.scene.text.TextAlignment) Text Align}</td> 198 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 199 * <td class="colLast" style="width:10%; text-align:center">{@link TextAlignment#LEFT LEFT}</td> 200 * <td class="colLast"> 201 * The horizontal alignment of text with respect to the {@code X} coordinate 202 * specified in the text operation. 203 * </td></tr> 204 * <tr class="rowColor"> 205 * <td class="colLast" style="width:15%">{@link #setTextBaseline(javafx.geometry.VPos) Text Baseline}</td> 206 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 207 * <td class="colLast" style="width:10%; text-align:center">{@link VPos#BASELINE BASELINE}</td> 208 * <td class="colLast"> 209 * The vertical position of the text relative to the {@code Y} coordinate 210 * specified in the text operation. 211 * </td></tr> 212 * 213 * <tr><th colspan="3"><a name="path-attr"><p align="center">Path Attributes</p></a></th></tr> 214 * <tr class="rowColor"> 215 * <td class="colLast" style="width:15%">{@link #beginPath() Current Path}</td> 216 * <td class="colLast" style="width:10%; text-align:center; color:#c00">No</td> 217 * <td class="colLast" style="width:10%; text-align:center">Empty path</td> 218 * <td class="colLast"> 219 * The path constructed using various path construction methods to be used 220 * in various path filling, stroking, or clipping operations. 221 * </td></tr> 222 * <tr class="altColor"> 223 * <td class="colLast" style="width:15%">{@link #setFillRule(javafx.scene.shape.FillRule) Fill Rule}</td> 224 * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td> 225 * <td class="colLast" style="width:10%; text-align:center">{@link FillRule#NON_ZERO NON_ZERO}</td> 226 * <td class="colLast"> 227 * The method used to determine the interior of paths for a path fill or 228 * clip operation. 229 * </td></tr> 230 * </table> 231 * 232 * <p> 233 * <a name="attr-ops-table"> 234 * The various rendering methods on the {@code GraphicsContext} use the 235 * following sets of rendering attributes: 236 * </a> 237 * <table class="overviewSummary" style="width:80%; margin-left:auto; margin-right:auto"> 238 * <tr> 239 * <th class="colLast" style="width:25%">Method</th> 240 * <th class="colLast" style="width:15%; text-align:center"><a href="#comm-attr">Common Rendering Attributes</a></th> 241 * <th class="colLast" style="width:15%; text-align:center"><a href="#fill-attr">Fill Attributes</a></th> 242 * <th class="colLast" style="width:15%; text-align:center"><a href="#strk-attr">Stroke Attributes</a></th> 243 * <th class="colLast" style="width:15%; text-align:center"><a href="#text-attr">Text Attributes</a></th> 244 * <th class="colLast" style="width:15%; text-align:center"><a href="#path-attr">Path Attributes</a></th> 245 * </tr> 246 * 247 * <tr><th colspan="1"><p align="center">Basic Shape Rendering</p></th></tr> 248 * <tr class="rowColor"> 249 * <td class="colLast" style="width:25%"> 250 * <a name="fill-basic-ops"> 251 * {@link #fillRect(double, double, double, double) fillRect()}, 252 * {@link #fillRoundRect(double, double, double, double, double, double) fillRoundRect()}, 253 * {@link #fillOval(double, double, double, double) fillOval()}, 254 * {@link #fillArc(double, double, double, double, double, double, javafx.scene.shape.ArcType) fillArc()} 255 * </a> 256 * </td> 257 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 258 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 259 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 260 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 261 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 262 * </tr> 263 * <tr class="altColor"> 264 * <td class="colLast" style="width:25%"> 265 * <a name="strk-basic-ops"> 266 * {@link #strokeLine(double, double, double, double) strokeLine()}, 267 * {@link #strokeRect(double, double, double, double) strokeRect()}, 268 * {@link #strokeRoundRect(double, double, double, double, double, double) strokeRoundRect()}, 269 * {@link #strokeOval(double, double, double, double) strokeOval()}, 270 * {@link #strokeArc(double, double, double, double, double, double, javafx.scene.shape.ArcType) strokeArc()} 271 * </a> 272 * </td> 273 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 274 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 275 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 276 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 277 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 278 * </tr> 279 * <tr class="rowColor"> 280 * <td class="colLast" style="width:25%"> 281 * <a name="fill-basic-ops"> 282 * {@link #clearRect(double, double, double, double) clearRect()} 283 * </a> 284 * </td> 285 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#base-fn-1">[1]</a></td> 286 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 287 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 288 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 289 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 290 * </tr> 291 * <tr class="altColor"> 292 * <td class="colLast" style="width:25%"> 293 * <a name="strk-basic-ops"> 294 * {@link #fillPolygon(double[], double[], int) fillPolygon()} 295 * </a> 296 * </td> 297 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 298 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 299 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 300 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 301 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#base-fn-2">[2]</a></td> 302 * </tr> 303 * <tr class="rowColor"> 304 * <td class="colLast" style="width:25%"> 305 * <a name="strk-basic-ops"> 306 * {@link #strokePolygon(double[], double[], int) strokePolygon()}, 307 * {@link #strokePolyline(double[], double[], int) strokePolyline()} 308 * </a> 309 * </td> 310 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 311 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 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:#c00">No</td> 314 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 315 * </tr> 316 * <tr><td colspan="6"><p align="center"> 317 * <a name="base-fn-1">[1]</a>Only the Transform, Clip, and Effect apply to clearRect()<br> 318 * <a name="base-fn-2">[2]</a>Only the Fill Rule applies to fillPolygon(), the current path is left unchanged 319 * </p></td></tr> 320 * 321 * <tr><th colspan="1"><p align="center">Text Rendering</p></th></tr> 322 * <tr class="rowColor"> 323 * <td class="colLast" style="width:25%"> 324 * <a name="fill-text-ops"> 325 * {@link #fillText(java.lang.String, double, double) fillText()}, 326 * {@link #fillText(java.lang.String, double, double, double) fillText(with maxWidth)} 327 * </a> 328 * </td> 329 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 330 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 331 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 332 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 333 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 334 * </tr> 335 * <tr class="altColor"> 336 * <td class="colLast" style="width:25%"> 337 * <a name="strk-text-ops"> 338 * {@link #strokeText(java.lang.String, double, double) strokeText()}, 339 * {@link #strokeText(java.lang.String, double, double, double) strokeText(with maxWidth)} 340 * </a> 341 * </td> 342 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 343 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 344 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 345 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 346 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 347 * </tr> 348 * 349 * <tr><th colspan="1"><p align="center">Path Rendering</p></th></tr> 350 * <tr class="rowColor"> 351 * <td class="colLast" style="width:25%"> 352 * {@link #beginPath() beginPath()}, 353 * {@link #moveTo(double, double) moveTo()}, 354 * {@link #lineTo(double, double) lineTo()}, 355 * {@link #quadraticCurveTo(double, double, double, double) quadraticCurveTo()}, 356 * {@link #bezierCurveTo(double, double, double, double, double, double) bezierCurveTo()}, 357 * {@link #arc(double, double, double, double, double, double) arc()}, 358 * {@link #arcTo(double, double, double, double, double) arcTo()}, 359 * {@link #appendSVGPath(java.lang.String) appendSVGPath()}, 360 * {@link #closePath() closePath()}, 361 * {@link #rect(double, double, double, double) rect()} 362 * </td> 363 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-3">[3]</a></td> 364 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 365 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 366 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 367 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 368 * </tr> 369 * <tr class="altColor"> 370 * <td class="colLast" style="width:25%"> 371 * <a name="fill-path-ops"> 372 * {@link #fill() fill()} 373 * </a> 374 * </td> 375 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-3">[3]</a></td> 376 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 377 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 378 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 379 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 380 * </tr> 381 * <tr class="rowColor"> 382 * <td class="colLast" style="width:25%"> 383 * <a name="strk-path-ops"> 384 * {@link #stroke() stroke()} 385 * </a> 386 * </td> 387 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-3">[3]</a></td> 388 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 389 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 390 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 391 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-4">[4]</a></td> 392 * </tr> 393 * <tr class="altColor"> 394 * <td class="colLast" style="width:25%"> 395 * <a name="clip-path-ops"> 396 * {@link #clip() clip()} 397 * </a> 398 * </td> 399 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 400 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 401 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 402 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 403 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 404 * </tr> 405 * <tr><td colspan="6"><p align="center"> 406 * <a name="path-fn-3">[3]</a>Transform applied only during path construction<br> 407 * <a name="path-fn-4">[4]</a>Fill Rule only used for fill() and clip() 408 * </p></td></tr> 409 * 410 * <tr><th colspan="1"><p align="center">Image Rendering</p></th></tr> 411 * <tr class="rowColor"> 412 * <td class="colLast" style="width:25%"> 413 * <a name="draw-img-ops"> 414 * {@link #drawImage(javafx.scene.image.Image, double, double) drawImage(all forms)} 415 * </a> 416 * </td> 417 * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td> 418 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 419 * <td class="colLast" style="width:15%; text-align:center; color:#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 * </tr> 423 * 424 * <tr><th colspan="1"><p align="center">Miscellaneous</p></th></tr> 425 * <tr class="rowColor"> 426 * <td class="colLast" style="width:25%"> 427 * {@link #applyEffect(javafx.scene.effect.Effect) applyEffect()}, 428 * {@link #getPixelWriter() PixelWriter methods} 429 * </td> 430 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 431 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 432 * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td> 433 * <td class="colLast" style="width:15%; text-align:center; color:#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 * <p> 441 * <pre> 442 import javafx.scene.*; 443 import javafx.scene.paint.*; 444 import javafx.scene.canvas.*; 445 446 Group root = new Group(); 447 Scene s = new Scene(root, 300, 300, Color.BLACK); 448 449 final Canvas canvas = new Canvas(250,250); 450 GraphicsContext gc = canvas.getGraphicsContext2D(); 451 452 gc.setFill(Color.BLUE); 453 gc.fillRect(75,75,100,100); 454 455 root.getChildren().add(canvas); 456 * </pre> 457 * </p> 458 * 459 * @since JavaFX 2.2 460 */ 461 public final class GraphicsContext { 462 Canvas theCanvas; 463 Path2D path; 464 boolean pathDirty; 465 466 State curState; 467 LinkedList<State> stateStack; 468 LinkedList<Path2D> clipStack; 469 470 GraphicsContext(Canvas theCanvas) { 471 this.theCanvas = theCanvas; 472 this.path = new Path2D(); 473 pathDirty = true; 474 475 this.curState = new State(); 476 this.stateStack = new LinkedList<State>(); 477 this.clipStack = new LinkedList<Path2D>(); 478 } 479 480 static class State { 481 double globalAlpha; 482 BlendMode blendop; 483 Affine2D transform; 484 Paint fill; 485 Paint stroke; 486 double linewidth; 487 StrokeLineCap linecap; 488 StrokeLineJoin linejoin; 489 double miterlimit; 490 int numClipPaths; 491 Font font; 492 TextAlignment textalign; 493 VPos textbaseline; 494 Effect effect; 495 FillRule fillRule; 496 497 State() { 498 init(); 499 } 500 501 final void init() { 502 set(1.0, BlendMode.SRC_OVER, 503 new Affine2D(), 504 Color.BLACK, Color.BLACK, 505 1.0, StrokeLineCap.SQUARE, StrokeLineJoin.MITER, 10.0, 506 0, Font.getDefault(), TextAlignment.LEFT, VPos.BASELINE, 507 null, FillRule.NON_ZERO); 508 } 509 510 State(State copy) { 511 set(copy.globalAlpha, copy.blendop, 512 new Affine2D(copy.transform), 513 copy.fill, copy.stroke, 514 copy.linewidth, copy.linecap, copy.linejoin, copy.miterlimit, 515 copy.numClipPaths, 516 copy.font, copy.textalign, copy.textbaseline, 517 copy.effect, copy.fillRule); 518 } 519 520 State(double globalAlpha, BlendMode blendop, 521 Affine2D transform, Paint fill, Paint stroke, 522 double linewidth, StrokeLineCap linecap, 523 StrokeLineJoin linejoin, double miterlimit, 524 int numClipPaths, 525 Font font, TextAlignment align, VPos baseline, 526 Effect effect, FillRule fillRule) 527 { 528 set(globalAlpha, blendop, 529 new Affine2D(transform), 530 fill, stroke, 531 linewidth, linecap, linejoin, miterlimit, 532 numClipPaths, 533 font, textalign, textbaseline, 534 effect, fillRule); 535 } 536 537 final void set(double globalAlpha, BlendMode blendop, 538 Affine2D transform, Paint fill, Paint stroke, 539 double linewidth, StrokeLineCap linecap, 540 StrokeLineJoin linejoin, double miterlimit, 541 int numClipPaths, 542 Font font, TextAlignment align, VPos baseline, 543 Effect effect, FillRule fillRule) 544 { 545 this.globalAlpha = globalAlpha; 546 this.blendop = blendop; 547 this.transform = transform; 548 this.fill = fill; 549 this.stroke = stroke; 550 this.linewidth = linewidth; 551 this.linecap = linecap; 552 this.linejoin = linejoin; 553 this.miterlimit = miterlimit; 554 this.numClipPaths = numClipPaths; 555 this.font = font; 556 this.textalign = align; 557 this.textbaseline = baseline; 558 this.effect = effect; 559 this.fillRule = fillRule; 560 } 561 562 State copy() { 563 return new State(this); 564 } 565 566 void restore(GraphicsContext ctx) { 567 ctx.setGlobalAlpha(globalAlpha); 568 ctx.setGlobalBlendMode(blendop); 569 ctx.setTransform(transform.getMxx(), transform.getMyx(), 570 transform.getMxy(), transform.getMyy(), 571 transform.getMxt(), transform.getMyt()); 572 ctx.setFill(fill); 573 ctx.setStroke(stroke); 574 ctx.setLineWidth(linewidth); 575 ctx.setLineCap(linecap); 576 ctx.setLineJoin(linejoin); 577 ctx.setMiterLimit(miterlimit); 578 GrowableDataBuffer buf = ctx.getBuffer(); 579 while (ctx.curState.numClipPaths > numClipPaths) { 580 ctx.curState.numClipPaths--; 581 ctx.clipStack.removeLast(); 582 buf.putByte(NGCanvas.POP_CLIP); 583 } 584 ctx.setFillRule(fillRule); 585 ctx.setFont(font); 586 ctx.setTextAlign(textalign); 587 ctx.setTextBaseline(textbaseline); 588 ctx.setEffect(effect); 589 } 590 } 591 592 private GrowableDataBuffer getBuffer() { 593 return theCanvas.getBuffer(); 594 } 595 596 private float coords[] = new float[6]; 597 private static final byte pgtype[] = { 598 NGCanvas.MOVETO, 599 NGCanvas.LINETO, 600 NGCanvas.QUADTO, 601 NGCanvas.CUBICTO, 602 NGCanvas.CLOSEPATH, 603 }; 604 private static final int numsegs[] = { 2, 2, 4, 6, 0, }; 605 606 private void markPathDirty() { 607 pathDirty = true; 608 } 609 610 private void writePath(byte command) { 611 updateTransform(); 612 GrowableDataBuffer buf = getBuffer(); 613 if (pathDirty) { 614 buf.putByte(NGCanvas.PATHSTART); 615 PathIterator pi = path.getPathIterator(null); 616 while (!pi.isDone()) { 617 int pitype = pi.currentSegment(coords); 618 buf.putByte(pgtype[pitype]); 619 for (int i = 0; i < numsegs[pitype]; i++) { 620 buf.putFloat(coords[i]); 621 } 622 pi.next(); 623 } 624 buf.putByte(NGCanvas.PATHEND); 625 pathDirty = false; 626 } 627 buf.putByte(command); 628 } 629 630 private void writePaint(Paint p, byte command) { 631 GrowableDataBuffer buf = getBuffer(); 632 buf.putByte(command); 633 buf.putObject(Toolkit.getPaintAccessor().getPlatformPaint(p)); 634 } 635 636 private void writeArcType(ArcType closure) { 637 byte type; 638 switch (closure) { 639 case OPEN: type = NGCanvas.ARC_OPEN; break; 640 case CHORD: type = NGCanvas.ARC_CHORD; break; 641 case ROUND: type = NGCanvas.ARC_PIE; break; 642 default: return; // ignored for consistency with other attributes 643 } 644 writeParam(type, NGCanvas.ARC_TYPE); 645 } 646 647 private void writeRectParams(GrowableDataBuffer buf, 648 double x, double y, double w, double h, 649 byte command) 650 { 651 buf.putByte(command); 652 buf.putFloat((float) x); 653 buf.putFloat((float) y); 654 buf.putFloat((float) w); 655 buf.putFloat((float) h); 656 } 657 658 private void writeOp4(double x, double y, double w, double h, byte command) { 659 updateTransform(); 660 writeRectParams(getBuffer(), x, y, w, h, command); 661 } 662 663 private void writeOp6(double x, double y, double w, double h, 664 double v1, double v2, byte command) 665 { 666 updateTransform(); 667 GrowableDataBuffer buf = getBuffer(); 668 buf.putByte(command); 669 buf.putFloat((float) x); 670 buf.putFloat((float) y); 671 buf.putFloat((float) w); 672 buf.putFloat((float) h); 673 buf.putFloat((float) v1); 674 buf.putFloat((float) v2); 675 } 676 677 private float polybuf[] = new float[512]; 678 private void flushPolyBuf(GrowableDataBuffer buf, 679 float polybuf[], int n, byte command) 680 { 681 curState.transform.transform(polybuf, 0, polybuf, 0, n/2); 682 for (int i = 0; i < n; i += 2) { 683 buf.putByte(command); 684 buf.putFloat(polybuf[i]); 685 buf.putFloat(polybuf[i+1]); 686 command = NGCanvas.LINETO; 687 } 688 } 689 private void writePoly(double xPoints[], double yPoints[], int nPoints, 690 boolean close, byte command) 691 { 692 if (xPoints == null || yPoints == null) return; 693 GrowableDataBuffer buf = getBuffer(); 694 buf.putByte(NGCanvas.PATHSTART); 695 int pos = 0; 696 byte polycmd = NGCanvas.MOVETO; 697 for (int i = 0; i < nPoints; i++) { 698 if (pos >= polybuf.length) { 699 flushPolyBuf(buf, polybuf, pos, polycmd); 700 pos = 0; 701 polycmd = NGCanvas.LINETO; 702 } 703 polybuf[pos++] = (float) xPoints[i]; 704 polybuf[pos++] = (float) yPoints[i]; 705 } 706 flushPolyBuf(buf, polybuf, pos, polycmd); 707 if (close) { 708 buf.putByte(NGCanvas.CLOSEPATH); 709 } 710 buf.putByte(NGCanvas.PATHEND); 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 = img.impl_getPlatformImage(); 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 = img.impl_getPlatformImage(); 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 occurence 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(Blend.impl_getToolkitMode(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 Font. 1328 * The default value is specified by {@link Font#getDefault()}. 1329 * The font is a <a href="#text-attr">text attribute</a> 1330 * used for any of the text methods as specified in the 1331 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1332 * A {@code null} value will be ignored and the current value will remain unchanged. 1333 * 1334 * @param f the Font or null. 1335 */ 1336 public void setFont(Font f) { 1337 if (f != null && curState.font != f) { 1338 curState.font = f; 1339 GrowableDataBuffer buf = getBuffer(); 1340 buf.putByte(NGCanvas.FONT); 1341 buf.putObject(f.impl_getNativeFont()); 1342 } 1343 } 1344 1345 /** 1346 * Gets the current Font. 1347 * The default value is specified by {@link Font#getDefault()}. 1348 * The font is a <a href="#text-attr">text attribute</a> 1349 * used for any of the text methods as specified in the 1350 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1351 * 1352 * @return the Font 1353 */ 1354 public Font getFont() { 1355 return curState.font; 1356 } 1357 1358 /** 1359 * Defines horizontal text alignment, relative to the text {@code x} origin. 1360 * The default value is {@link TextAlignment#LEFT LEFT}. 1361 * The text alignment is a <a href="#text-attr">text attribute</a> 1362 * used for any of the text methods as specified in the 1363 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1364 * <p> 1365 * Let horizontal bounds represent the logical width of a single line of 1366 * text. Where each line of text has a separate horizontal bounds. 1367 * <p> 1368 * Then TextAlignment is specified as: 1369 * <ul> 1370 * <li>Left: the left edge of the horizontal bounds will be at {@code x}. 1371 * <li>Center: the center, halfway between left and right edge, of the 1372 * horizontal bounds will be at {@code x}. 1373 * <li>Right: the right edge of the horizontal bounds will be at {@code x}. 1374 * </ul> 1375 * <p> 1376 * 1377 * Note: Canvas does not support line wrapping, therefore the text 1378 * alignment Justify is identical to left aligned text. 1379 * <p> 1380 * A {@code null} value will be ignored and the current value will remain unchanged. 1381 * 1382 * @param align {@code TextAlignment} with values of Left, Center, Right or null. 1383 */ 1384 public void setTextAlign(TextAlignment align) { 1385 if (align != null && curState.textalign != align) { 1386 byte a; 1387 switch (align) { 1388 case LEFT: a = NGCanvas.ALIGN_LEFT; break; 1389 case CENTER: a = NGCanvas.ALIGN_CENTER; break; 1390 case RIGHT: a = NGCanvas.ALIGN_RIGHT; break; 1391 case JUSTIFY: a = NGCanvas.ALIGN_JUSTIFY; break; 1392 default: return; 1393 } 1394 curState.textalign = align; 1395 writeParam(a, NGCanvas.TEXT_ALIGN); 1396 } 1397 } 1398 1399 /** 1400 * Gets the current {@code TextAlignment}. 1401 * The default value is {@link TextAlignment#LEFT LEFT}. 1402 * The text alignment is a <a href="#text-attr">text attribute</a> 1403 * used for any of the text methods as specified in the 1404 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1405 * 1406 * @return {@code TextAlignment} with values of Left, Center, Right, or 1407 * Justify. 1408 */ 1409 public TextAlignment getTextAlign() { 1410 return curState.textalign; 1411 } 1412 1413 /** 1414 * Sets the current Text Baseline. 1415 * The default value is {@link VPos#BASELINE BASELINE}. 1416 * The text baseline is a <a href="#text-attr">text attribute</a> 1417 * used for any of the text methods as specified in the 1418 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1419 * A {@code null} value will be ignored and the current value will remain unchanged. 1420 * 1421 * @param baseline {@code VPos} with values of Top, Center, Baseline, or Bottom or null. 1422 */ 1423 public void setTextBaseline(VPos baseline) { 1424 if (baseline != null && curState.textbaseline != baseline) { 1425 byte b; 1426 switch (baseline) { 1427 case TOP: b = NGCanvas.BASE_TOP; break; 1428 case CENTER: b = NGCanvas.BASE_MIDDLE; break; 1429 case BASELINE: b = NGCanvas.BASE_ALPHABETIC; break; 1430 case BOTTOM: b = NGCanvas.BASE_BOTTOM; break; 1431 default: return; 1432 } 1433 curState.textbaseline = baseline; 1434 writeParam(b, NGCanvas.TEXT_BASELINE); 1435 } 1436 } 1437 1438 /** 1439 * Gets the current Text Baseline. 1440 * The default value is {@link VPos#BASELINE BASELINE}. 1441 * The text baseline is a <a href="#text-attr">text attribute</a> 1442 * used for any of the text methods as specified in the 1443 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1444 * 1445 * @return {@code VPos} with values of Top, Center, Baseline, or Bottom 1446 */ 1447 public VPos getTextBaseline() { 1448 return curState.textbaseline; 1449 } 1450 1451 /** 1452 * Fills the given string of text at position x, y 1453 * with the current fill paint attribute. 1454 * A {@code null} text value will be ignored. 1455 * <p> 1456 * This method will be affected by any of the 1457 * <a href="#comm-attr">global common</a>, 1458 * <a href="#fill-attr">fill</a>, 1459 * or <a href="#text-attr">text</a> 1460 * attributes as specified in the 1461 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1462 * </p> 1463 * 1464 * @param text the string of text or null. 1465 * @param x position on the x axis. 1466 * @param y position on the y axis. 1467 */ 1468 public void fillText(String text, double x, double y) { 1469 writeText(text, x, y, 0, NGCanvas.FILL_TEXT); 1470 } 1471 1472 /** 1473 * Draws the given string of text at position x, y 1474 * with the current stroke paint attribute. 1475 * A {@code null} text value will be ignored. 1476 * <p> 1477 * This method will be affected by any of the 1478 * <a href="#comm-attr">global common</a>, 1479 * <a href="#strk-attr">stroke</a>, 1480 * or <a href="#text-attr">text</a> 1481 * attributes as specified in the 1482 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1483 * </p> 1484 * 1485 * @param text the string of text or null. 1486 * @param x position on the x axis. 1487 * @param y position on the y axis. 1488 */ 1489 public void strokeText(String text, double x, double y) { 1490 writeText(text, x, y, 0, NGCanvas.STROKE_TEXT); 1491 } 1492 1493 /** 1494 * Fills text and includes a maximum width of the string. 1495 * If the width of the text extends past max width, then it will be sized 1496 * to fit. 1497 * A {@code null} text value will be ignored. 1498 * <p> 1499 * This method will be affected by any of the 1500 * <a href="#comm-attr">global common</a>, 1501 * <a href="#fill-attr">fill</a>, 1502 * or <a href="#text-attr">text</a> 1503 * attributes as specified in the 1504 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1505 * </p> 1506 * 1507 * @param text the string of text or null. 1508 * @param x position on the x axis. 1509 * @param y position on the y axis. 1510 * @param maxWidth maximum width the text string can have. 1511 */ 1512 public void fillText(String text, double x, double y, double maxWidth) { 1513 if (maxWidth <= 0) return; 1514 writeText(text, x, y, maxWidth, NGCanvas.FILL_TEXT); 1515 } 1516 1517 /** 1518 * Draws text with stroke paint and includes a maximum width of the string. 1519 * If the width of the text extends past max width, then it will be sized 1520 * to fit. 1521 * A {@code null} text value will be ignored. 1522 * <p> 1523 * This method will be affected by any of the 1524 * <a href="#comm-attr">global common</a>, 1525 * <a href="#strk-attr">stroke</a>, 1526 * or <a href="#text-attr">text</a> 1527 * attributes as specified in the 1528 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1529 * </p> 1530 * 1531 * @param text the string of text or null. 1532 * @param x position on the x axis. 1533 * @param y position on the y axis. 1534 * @param maxWidth maximum width the text string can have. 1535 */ 1536 public void strokeText(String text, double x, double y, double maxWidth) { 1537 if (maxWidth <= 0) return; 1538 writeText(text, x, y, maxWidth, NGCanvas.STROKE_TEXT); 1539 } 1540 1541 1542 /** 1543 * Set the filling rule attribute for determining the interior of paths 1544 * in fill or clip operations. 1545 * The default value is {@code FillRule.NON_ZERO}. 1546 * A {@code null} value will be ignored and the current value will remain unchanged. 1547 * The fill rule is a <a href="#path-attr">path attribute</a> 1548 * used for any of the fill or clip path methods as specified in the 1549 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1550 * 1551 * @param fillRule {@code FillRule} with a value of Even_odd or Non_zero or null. 1552 */ 1553 public void setFillRule(FillRule fillRule) { 1554 if (fillRule != null && curState.fillRule != fillRule) { 1555 byte b; 1556 if (fillRule == FillRule.EVEN_ODD) { 1557 b = NGCanvas.FILL_RULE_EVEN_ODD; 1558 } else { 1559 b = NGCanvas.FILL_RULE_NON_ZERO; 1560 } 1561 curState.fillRule = fillRule; 1562 writeParam(b, NGCanvas.FILL_RULE); 1563 } 1564 } 1565 1566 /** 1567 * Get the filling rule attribute for determining the interior of paths 1568 * in fill and clip operations. 1569 * The default value is {@code FillRule.NON_ZERO}. 1570 * The fill rule is a <a href="#path-attr">path attribute</a> 1571 * used for any of the fill or clip path methods as specified in the 1572 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 1573 * 1574 * @return current fill rule. 1575 */ 1576 public FillRule getFillRule() { 1577 return curState.fillRule; 1578 } 1579 1580 /** 1581 * Resets the current path to empty. 1582 * The default path is empty. 1583 * The current path is a <a href="#path-attr">path attribute</a> 1584 * used for any of the path methods as specified in the 1585 * <a href="#attr-ops-table">Rendering Attributes Table</a> 1586 * and <b>is not affected</b> by the {@link #save()} and 1587 * {@link #restore()} operations. 1588 */ 1589 public void beginPath() { 1590 path.reset(); 1591 markPathDirty(); 1592 } 1593 1594 /** 1595 * Issues a move command for the current path to the given x,y coordinate. 1596 * The coordinates are transformed by the current transform as they are 1597 * added to the path and unaffected by subsequent changes to the transform. 1598 * The current path is a <a href="#path-attr">path attribute</a> 1599 * used for any of the path methods as specified in the 1600 * <a href="#attr-ops-table">Rendering Attributes Table</a> 1601 * and <b>is not affected</b> by the {@link #save()} and 1602 * {@link #restore()} operations. 1603 * 1604 * @param x0 the X position for the move to command. 1605 * @param y0 the Y position for the move to command. 1606 */ 1607 public void moveTo(double x0, double y0) { 1608 coords[0] = (float) x0; 1609 coords[1] = (float) y0; 1610 curState.transform.transform(coords, 0, coords, 0, 1); 1611 path.moveTo(coords[0], coords[1]); 1612 markPathDirty(); 1613 } 1614 1615 /** 1616 * Adds segments to the current path to make a line to the given x,y 1617 * coordinate. 1618 * The coordinates are transformed by the current transform as they are 1619 * added to the path and unaffected by subsequent changes to the transform. 1620 * The current path is a <a href="#path-attr">path attribute</a> 1621 * used for any of the path methods as specified in the 1622 * <a href="#attr-ops-table">Rendering Attributes Table</a> 1623 * and <b>is not affected</b> by the {@link #save()} and 1624 * {@link #restore()} operations. 1625 * 1626 * @param x1 the X coordinate of the ending point of the line. 1627 * @param y1 the Y coordinate of the ending point of the line. 1628 */ 1629 public void lineTo(double x1, double y1) { 1630 coords[0] = (float) x1; 1631 coords[1] = (float) y1; 1632 curState.transform.transform(coords, 0, coords, 0, 1); 1633 if (path.getNumCommands() == 0) { 1634 path.moveTo(coords[0], coords[1]); 1635 } 1636 path.lineTo(coords[0], coords[1]); 1637 markPathDirty(); 1638 } 1639 1640 /** 1641 * Adds segments to the current path to make a quadratic Bezier curve. 1642 * The coordinates are transformed by the current transform as they are 1643 * added to the path and unaffected by subsequent changes to the transform. 1644 * The current path is a <a href="#path-attr">path attribute</a> 1645 * used for any of the path methods as specified in the 1646 * <a href="#attr-ops-table">Rendering Attributes Table</a> 1647 * and <b>is not affected</b> by the {@link #save()} and 1648 * {@link #restore()} operations. 1649 * 1650 * @param xc the X coordinate of the control point 1651 * @param yc the Y coordinate of the control point 1652 * @param x1 the X coordinate of the end point 1653 * @param y1 the Y coordinate of the end point 1654 */ 1655 public void quadraticCurveTo(double xc, double yc, double x1, double y1) { 1656 coords[0] = (float) xc; 1657 coords[1] = (float) yc; 1658 coords[2] = (float) x1; 1659 coords[3] = (float) y1; 1660 curState.transform.transform(coords, 0, coords, 0, 2); 1661 if (path.getNumCommands() == 0) { 1662 path.moveTo(coords[0], coords[1]); 1663 } 1664 path.quadTo(coords[0], coords[1], coords[2], coords[3]); 1665 markPathDirty(); 1666 } 1667 1668 /** 1669 * Adds segments to the current path to make a cubic Bezier curve. 1670 * The coordinates are transformed by the current transform as they are 1671 * added to the path and unaffected by subsequent changes to the transform. 1672 * The current path is a <a href="#path-attr">path attribute</a> 1673 * used for any of the path methods as specified in the 1674 * <a href="#attr-ops-table">Rendering Attributes Table</a> 1675 * and <b>is not affected</b> by the {@link #save()} and 1676 * {@link #restore()} operations. 1677 * 1678 * @param xc1 the X coordinate of first Bezier control point. 1679 * @param yc1 the Y coordinate of the first Bezier control point. 1680 * @param xc2 the X coordinate of the second Bezier control point. 1681 * @param yc2 the Y coordinate of the second Bezier control point. 1682 * @param x1 the X coordinate of the end point. 1683 * @param y1 the Y coordinate of the end point. 1684 */ 1685 public void bezierCurveTo(double xc1, double yc1, double xc2, double yc2, double x1, double y1) { 1686 coords[0] = (float) xc1; 1687 coords[1] = (float) yc1; 1688 coords[2] = (float) xc2; 1689 coords[3] = (float) yc2; 1690 coords[4] = (float) x1; 1691 coords[5] = (float) y1; 1692 curState.transform.transform(coords, 0, coords, 0, 3); 1693 if (path.getNumCommands() == 0) { 1694 path.moveTo(coords[0], coords[1]); 1695 } 1696 path.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); 1697 markPathDirty(); 1698 } 1699 1700 /** 1701 * Adds segments to the current path to make an arc. 1702 * The coordinates are transformed by the current transform as they are 1703 * added to the path and unaffected by subsequent changes to the transform. 1704 * The current path is a <a href="#path-attr">path attribute</a> 1705 * used for any of the path methods as specified in the 1706 * <a href="#attr-ops-table">Rendering Attributes Table</a> 1707 * and <b>is not affected</b> by the {@link #save()} and 1708 * {@link #restore()} operations. 1709 * <p> 1710 * If {@code p0} is the current point in the path and {@code p1} is the 1711 * point specified by {@code (x1, y1)} and {@code p2} is the point 1712 * specified by {@code (x2, y2)}, then the arc segments appended will 1713 * be segments along the circumference of a circle of the specified 1714 * radius touching and inscribed into the convex (interior) side of 1715 * {@code p0->p1->p2}. The path will contain a line segment (if 1716 * needed) to the tangent point between that circle and {@code p0->p1} 1717 * followed by circular arc segments to reach the tangent point between 1718 * the circle and {@code p1->p2} and will end with the current point at 1719 * that tangent point (not at {@code p2}). 1720 * Note that the radius and circularity of the arc segments will be 1721 * measured or considered relative to the current transform, but the 1722 * resulting segments that are computed from those untransformed 1723 * points will then be transformed when they are added to the path. 1724 * Since all computation is done in untransformed space, but the 1725 * pre-existing path segments are all transformed, the ability to 1726 * correctly perform the computation may implicitly depend on being 1727 * able to inverse transform the current end of the current path back 1728 * into untransformed coordinates. 1729 * </p> 1730 * <p> 1731 * If there is no way to compute and inscribe the indicated circle 1732 * for any reason then the entire operation will simply append segments 1733 * to force a line to point {@code p1}. Possible reasons that the 1734 * computation may fail include: 1735 * <ul> 1736 * <li>The current path is empty.</li> 1737 * <li>The segments {@code p0->p1->p2} are colinear.</li> 1738 * <li>the current transform is non-invertible so that the current end 1739 * point of the current path cannot be untransformed for computation.</li> 1740 * </ul> 1741 * </p> 1742 * 1743 * @param x1 the X coordinate of the first point of the arc. 1744 * @param y1 the Y coordinate of the first point of the arc. 1745 * @param x2 the X coordinate of the second point of the arc. 1746 * @param y2 the Y coordinate of the second point of the arc. 1747 * @param radius the radius of the arc in the range {0.0-positive infinity}. 1748 */ 1749 public void arcTo(double x1, double y1, double x2, double y2, double radius) { 1750 if (path.getNumCommands() == 0) { 1751 moveTo(x1, y1); 1752 lineTo(x1, y1); 1753 } else if (!tryArcTo((float) x1, (float) y1, (float) x2, (float) y2, 1754 (float) radius)) 1755 { 1756 lineTo(x1, y1); 1757 } 1758 } 1759 1760 private static double lenSq(double x0, double y0, double x1, double y1) { 1761 x1 -= x0; 1762 y1 -= y0; 1763 return x1 * x1 + y1 * y1; 1764 } 1765 1766 private boolean tryArcTo(float x1, float y1, float x2, float y2, float radius) { 1767 float x0, y0; 1768 if (curState.transform.isTranslateOrIdentity()) { 1769 x0 = (float) (path.getCurrentX() - curState.transform.getMxt()); 1770 y0 = (float) (path.getCurrentY() - curState.transform.getMyt()); 1771 } else { 1772 coords[0] = path.getCurrentX(); 1773 coords[1] = path.getCurrentY(); 1774 try { 1775 curState.transform.inverseTransform(coords, 0, coords, 0, 1); 1776 } catch (NoninvertibleTransformException e) { 1777 return false; 1778 } 1779 x0 = coords[0]; 1780 y0 = coords[1]; 1781 } 1782 // call x1,y1 the corner point 1783 // If 2*theta is the angle described by p0->p1->p2 1784 // then theta is the angle described by p0->p1->centerpt and 1785 // centerpt->p1->p2 1786 // We know that the distance from the arc center to the tangent points 1787 // is r, and if A is the distance from the corner to the tangent point 1788 // then we know: 1789 // tan(theta) = r/A 1790 // A = r / sin(theta) 1791 // B = A * cos(theta) = r * (sin/cos) = r * tan 1792 // We use the cosine rule on the triangle to get the 2*theta angle: 1793 // cosB = (a^2 + c^2 - b^2) / (2ac) 1794 // where a and c are the adjacent sides and b is the opposite side 1795 // i.e. a = p0->p1, c=p1->p2, b=p0->p2 1796 // Then we can use the tan^2 identity to compute B: 1797 // tan^2 = (1 - cos(2theta)) / (1 + cos(2theta)) 1798 double lsq01 = lenSq(x0, y0, x1, y1); 1799 double lsq12 = lenSq(x1, y1, x2, y2); 1800 double lsq02 = lenSq(x0, y0, x2, y2); 1801 double len01 = Math.sqrt(lsq01); 1802 double len12 = Math.sqrt(lsq12); 1803 double cosnum = lsq01 + lsq12 - lsq02; 1804 double cosden = 2.0 * len01 * len12; 1805 if (cosden == 0.0 || radius <= 0f) { 1806 return false; 1807 } 1808 double cos_2theta = cosnum / cosden; 1809 double tansq_den = (1.0 + cos_2theta); 1810 if (tansq_den == 0.0) { 1811 return false; 1812 } 1813 double tansq_theta = (1.0 - cos_2theta) / tansq_den; 1814 double A = radius / Math.sqrt(tansq_theta); 1815 double tx0 = x1 + (A / len01) * (x0 - x1); 1816 double ty0 = y1 + (A / len01) * (y0 - y1); 1817 double tx1 = x1 + (A / len12) * (x2 - x1); 1818 double ty1 = y1 + (A / len12) * (y2 - y1); 1819 // The midpoint between the two tangent points 1820 double mx = (tx0 + tx1) / 2.0; 1821 double my = (ty0 + ty1) / 2.0; 1822 // similar triangles tell us that: 1823 // len(m,center)/len(m,tangent) = len(m,tangent)/len(corner,m) 1824 // len(m,center) = lensq(m,tangent)/len(corner,m) 1825 // center = m + (m - p1) * len(m,center) / len(corner,m) 1826 // = m + (m - p1) * (lensq(m,tangent) / lensq(corner,m)) 1827 double lenratioden = lenSq(mx, my, x1, y1); 1828 if (lenratioden == 0.0) { 1829 return false; 1830 } 1831 double lenratio = lenSq(mx, my, tx0, ty0) / lenratioden; 1832 double cx = mx + (mx - x1) * lenratio; 1833 double cy = my + (my - y1) * lenratio; 1834 if (!(cx == cx && cy == cy)) { 1835 return false; 1836 } 1837 // Looks like we are good to draw, first we have to get to the 1838 // initial tangent point with a line segment. 1839 if (tx0 != x0 || ty0 != y0) { 1840 lineTo(tx0, ty0); 1841 } 1842 // We need sin(arc/2), cos(arc/2) 1843 // and possibly sin(arc/4), cos(arc/4) if we need 2 cubic beziers 1844 // We have tan(theta) = tan(tri/2) 1845 // arc = 180-tri 1846 // arc/2 = (180-tri)/2 = 90-(tri/2) 1847 // sin(arc/2) = sin(90-(tri/2)) = cos(tri/2) 1848 // cos(arc/2) = cos(90-(tri/2)) = sin(tri/2) 1849 // 2theta = tri, therefore theta = tri/2 1850 // cos(tri/2)^2 = (1+cos(tri)) / 2.0 = (1+cos_2theta)/2.0 1851 // sin(tri/2)^2 = (1-cos(tri)) / 2.0 = (1-cos_2theta)/2.0 1852 // sin(arc/2) = cos(tri/2) = sqrt((1+cos_2theta)/2.0) 1853 // cos(arc/2) = sin(tri/2) = sqrt((1-cos_2theta)/2.0) 1854 // We compute cos(arc/2) here as we need it in either case below 1855 double coshalfarc = Math.sqrt((1.0 - cos_2theta) / 2.0); 1856 boolean ccw = (ty0 - cy) * (tx1 - cx) > (ty1 - cy) * (tx0 - cx); 1857 // If the arc covers more than 90 degrees then we must use 2 1858 // cubic beziers to get a decent approximation. 1859 // arc = 180-tri 1860 // arc = 180-2*theta 1861 // arc > 90 implies 2*theta < 90 1862 // 2*theta < 90 implies cos_2theta > 0 1863 // So, we need 2 cubics if cos_2theta > 0 1864 if (cos_2theta <= 0.0) { 1865 // 1 cubic bezier 1866 double sinhalfarc = Math.sqrt((1.0 + cos_2theta) / 2.0); 1867 double cv = 4.0 / 3.0 * sinhalfarc / (1.0 + coshalfarc); 1868 if (ccw) cv = -cv; 1869 double cpx0 = tx0 - cv * (ty0 - cy); 1870 double cpy0 = ty0 + cv * (tx0 - cx); 1871 double cpx1 = tx1 + cv * (ty1 - cy); 1872 double cpy1 = ty1 - cv * (tx1 - cx); 1873 bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1); 1874 } else { 1875 // 2 cubic beziers 1876 // We need sin(arc/4) and cos(arc/4) 1877 // We computed cos(arc/2), so we can compute them as follows: 1878 // sin(arc/4) = sqrt((1 - cos(arc/2)) / 2) 1879 // cos(arc/4) = sart((1 + cos(arc/2)) / 2) 1880 double sinqtrarc = Math.sqrt((1.0 - coshalfarc) / 2.0); 1881 double cosqtrarc = Math.sqrt((1.0 + coshalfarc) / 2.0); 1882 double cv = 4.0 / 3.0 * sinqtrarc / (1.0 + cosqtrarc); 1883 if (ccw) cv = -cv; 1884 double midratio = radius / Math.sqrt(lenratioden); 1885 double midarcx = cx + (x1 - mx) * midratio; 1886 double midarcy = cy + (y1 - my) * midratio; 1887 double cpx0 = tx0 - cv * (ty0 - cy); 1888 double cpy0 = ty0 + cv * (tx0 - cx); 1889 double cpx1 = midarcx + cv * (midarcy - cy); 1890 double cpy1 = midarcy - cv * (midarcx - cx); 1891 bezierCurveTo(cpx0, cpy0, cpx1, cpy1, midarcx, midarcy); 1892 cpx0 = midarcx - cv * (midarcy - cy); 1893 cpy0 = midarcy + cv * (midarcx - cx); 1894 cpx1 = tx1 + cv * (ty1 - cy); 1895 cpy1 = ty1 - cv * (tx1 - cx); 1896 bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1); 1897 } 1898 return true; 1899 } 1900 1901 /** 1902 * Adds path elements to the current path to make an arc that uses Euclidean 1903 * degrees. This Euclidean orientation sweeps from East to North, then West, 1904 * then South, then back to East. 1905 * The coordinates are transformed by the current transform as they are 1906 * added to the path and unaffected by subsequent changes to the transform. 1907 * The current path is a <a href="#path-attr">path attribute</a> 1908 * used for any of the path methods as specified in the 1909 * <a href="#attr-ops-table">Rendering Attributes Table</a> 1910 * and <b>is not affected</b> by the {@link #save()} and 1911 * {@link #restore()} operations. 1912 * 1913 * @param centerX the center x position of the arc. 1914 * @param centerY the center y position of the arc. 1915 * @param radiusX the x radius of the arc. 1916 * @param radiusY the y radius of the arc. 1917 * @param startAngle the starting angle of the arc in the range {@code 0-360.0} 1918 * @param length the length of the baseline of the arc. 1919 */ 1920 public void arc(double centerX, double centerY, 1921 double radiusX, double radiusY, 1922 double startAngle, double length) 1923 { 1924 Arc2D arc = new Arc2D((float) (centerX - radiusX), // x 1925 (float) (centerY - radiusY), // y 1926 (float) (radiusX * 2.0), // w 1927 (float) (radiusY * 2.0), // h 1928 (float) startAngle, 1929 (float) length, 1930 Arc2D.OPEN); 1931 path.append(arc.getPathIterator(curState.transform), true); 1932 markPathDirty(); 1933 } 1934 1935 /** 1936 * Adds path elements to the current path to make a rectangle. 1937 * The coordinates are transformed by the current transform as they are 1938 * added to the path and unaffected by subsequent changes to the transform. 1939 * The current path is a <a href="#path-attr">path attribute</a> 1940 * used for any of the path methods as specified in the 1941 * <a href="#attr-ops-table">Rendering Attributes Table</a> 1942 * and <b>is not affected</b> by the {@link #save()} and 1943 * {@link #restore()} operations. 1944 * 1945 * @param x x position of the upper left corner of the rectangle. 1946 * @param y y position of the upper left corner of the rectangle. 1947 * @param w width of the rectangle. 1948 * @param h height of the rectangle. 1949 */ 1950 public void rect(double x, double y, double w, double h) { 1951 coords[0] = (float) x; 1952 coords[1] = (float) y; 1953 coords[2] = (float) w; 1954 coords[3] = (float) 0; 1955 coords[4] = (float) 0; 1956 coords[5] = (float) h; 1957 curState.transform.deltaTransform(coords, 0, coords, 0, 3); 1958 float x0 = coords[0] + (float) curState.transform.getMxt(); 1959 float y0 = coords[1] + (float) curState.transform.getMyt(); 1960 float dx1 = coords[2]; 1961 float dy1 = coords[3]; 1962 float dx2 = coords[4]; 1963 float dy2 = coords[5]; 1964 path.moveTo(x0, y0); 1965 path.lineTo(x0+dx1, y0+dy1); 1966 path.lineTo(x0+dx1+dx2, y0+dy1+dy2); 1967 path.lineTo(x0+dx2, y0+dy2); 1968 path.closePath(); 1969 markPathDirty(); 1970 // path.moveTo(x0, y0); // not needed, closepath leaves pen at moveto 1971 } 1972 1973 /** 1974 * Appends an SVG Path string to the current path. If there is no current 1975 * path the string must then start with either type of move command. 1976 * A {@code null} value or incorrect SVG path will be ignored. 1977 * The coordinates are transformed by the current transform as they are 1978 * added to the path and unaffected by subsequent changes to the transform. 1979 * The current path is a <a href="#path-attr">path attribute</a> 1980 * used for any of the path methods as specified in the 1981 * <a href="#attr-ops-table">Rendering Attributes Table</a> 1982 * and <b>is not affected</b> by the {@link #save()} and 1983 * {@link #restore()} operations. 1984 * 1985 * @param svgpath the SVG Path string. 1986 */ 1987 public void appendSVGPath(String svgpath) { 1988 if (svgpath == null) return; 1989 boolean prependMoveto = true; 1990 boolean skipMoveto = true; 1991 for (int i = 0; i < svgpath.length(); i++) { 1992 switch (svgpath.charAt(i)) { 1993 case ' ': 1994 case '\t': 1995 case '\r': 1996 case '\n': 1997 continue; 1998 case 'M': 1999 prependMoveto = skipMoveto = false; 2000 break; 2001 case 'm': 2002 if (path.getNumCommands() == 0) { 2003 // An initial relative moveTo becomes absolute 2004 prependMoveto = false; 2005 } 2006 // Even if we prepend an initial moveTo in the temp 2007 // path, we do not want to delete the resulting initial 2008 // moveTo because the relative moveto will be folded 2009 // into it by an optimization in the Path2D object. 2010 skipMoveto = false; 2011 break; 2012 } 2013 break; 2014 } 2015 Path2D p2d = new Path2D(); 2016 if (prependMoveto && path.getNumCommands() > 0) { 2017 float x0, y0; 2018 if (curState.transform.isTranslateOrIdentity()) { 2019 x0 = (float) (path.getCurrentX() - curState.transform.getMxt()); 2020 y0 = (float) (path.getCurrentY() - curState.transform.getMyt()); 2021 } else { 2022 coords[0] = path.getCurrentX(); 2023 coords[1] = path.getCurrentY(); 2024 try { 2025 curState.transform.inverseTransform(coords, 0, coords, 0, 1); 2026 } catch (NoninvertibleTransformException e) { 2027 } 2028 x0 = coords[0]; 2029 y0 = coords[1]; 2030 } 2031 p2d.moveTo(x0, y0); 2032 } else { 2033 skipMoveto = false; 2034 } 2035 try { 2036 p2d.appendSVGPath(svgpath); 2037 PathIterator pi = p2d.getPathIterator(curState.transform); 2038 if (skipMoveto) { 2039 // We need to delete the initial moveto and let the path 2040 // extend from the actual existing geometry. 2041 pi.next(); 2042 } 2043 path.append(pi, false); 2044 } catch (IllegalArgumentException | IllegalPathStateException ex) { 2045 //Ignore incorrect path 2046 } 2047 } 2048 2049 /** 2050 * Closes the path. 2051 * The current path is a <a href="#path-attr">path attribute</a> 2052 * used for any of the path methods as specified in the 2053 * <a href="#attr-ops-table">Rendering Attributes Table</a> 2054 * and <b>is not affected</b> by the {@link #save()} and 2055 * {@link #restore()} operations. 2056 */ 2057 public void closePath() { 2058 if (path.getNumCommands() > 0) { 2059 path.closePath(); 2060 markPathDirty(); 2061 } 2062 } 2063 2064 /** 2065 * Fills the path with the current fill paint. 2066 * <p> 2067 * This method will be affected by any of the 2068 * <a href="#comm-attr">global common</a>, 2069 * <a href="#fill-attr">fill</a>, 2070 * or <a href="#path-attr">path</a> 2071 * attributes as specified in the 2072 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2073 * Note that the path segments were transformed as they were originally 2074 * added to the current path so the current transform will not affect 2075 * those path segments again, but it may affect other attributes in 2076 * affect at the time of the {@code fill()} operation. 2077 * </p> 2078 */ 2079 public void fill() { 2080 writePath(NGCanvas.FILL_PATH); 2081 } 2082 2083 /** 2084 * Strokes the path with the current stroke paint. 2085 * <p> 2086 * This method will be affected by any of the 2087 * <a href="#comm-attr">global common</a>, 2088 * <a href="#strk-attr">stroke</a>, 2089 * or <a href="#path-attr">path</a> 2090 * attributes as specified in the 2091 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2092 * Note that the path segments were transformed as they were originally 2093 * added to the current path so the current transform will not affect 2094 * those path segments again, but it may affect other attributes in 2095 * affect at the time of the {@code stroke()} operation. 2096 * </p> 2097 */ 2098 public void stroke() { 2099 writePath(NGCanvas.STROKE_PATH); 2100 } 2101 2102 /** 2103 * Intersects the current clip with the current path and applies it to 2104 * subsequent rendering operation as an anti-aliased mask. 2105 * The current clip is a <a href="#comm-attr">common attribute</a> 2106 * used for nearly all rendering operations as specified in the 2107 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2108 * <p> 2109 * This method will itself be affected only by the 2110 * <a href="#path-attr">path</a> 2111 * attributes as specified in the 2112 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2113 * Note that the path segments were transformed as they were originally 2114 * added to the current path so the current transform will not affect 2115 * those path segments again, but it may affect other attributes in 2116 * affect at the time of the {@code stroke()} operation. 2117 * </p> 2118 */ 2119 public void clip() { 2120 Path2D clip = new Path2D(path); 2121 clipStack.addLast(clip); 2122 curState.numClipPaths++; 2123 GrowableDataBuffer buf = getBuffer(); 2124 buf.putByte(NGCanvas.PUSH_CLIP); 2125 buf.putObject(clip); 2126 } 2127 2128 /** 2129 * Returns true if the the given x,y point is inside the path. 2130 * 2131 * @param x the X coordinate to use for the check. 2132 * @param y the Y coordinate to use for the check. 2133 * @return true if the point given is inside the path, false 2134 * otherwise. 2135 */ 2136 public boolean isPointInPath(double x, double y) { 2137 // TODO: HTML5 considers points on the path to be inside, but we 2138 // implement a halfin-halfout approach... 2139 return path.contains((float) x, (float) y); 2140 } 2141 2142 /** 2143 * Clears a portion of the canvas with a transparent color value. 2144 * <p> 2145 * This method will be affected only by the current transform, clip, 2146 * and effect. 2147 * </p> 2148 * 2149 * @param x X position of the upper left corner of the rectangle. 2150 * @param y Y position of the upper left corner of the rectangle. 2151 * @param w width of the rectangle. 2152 * @param h height of the rectangle. 2153 */ 2154 public void clearRect(double x, double y, double w, double h) { 2155 if (w != 0 && h != 0) { 2156 resetIfCovers(null, x, y, w, h); 2157 writeOp4(x, y, w, h, NGCanvas.CLEAR_RECT); 2158 } 2159 } 2160 2161 /** 2162 * Fills a rectangle using the current fill paint. 2163 * <p> 2164 * This method will be affected by any of the 2165 * <a href="#comm-attr">global common</a> 2166 * or <a href="#fill-attr">fill</a> 2167 * attributes as specified in the 2168 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2169 * </p> 2170 * 2171 * @param x the X position of the upper left corner of the rectangle. 2172 * @param y the Y position of the upper left corner of the rectangle. 2173 * @param w the width of the rectangle. 2174 * @param h the height of the rectangle. 2175 */ 2176 public void fillRect(double x, double y, double w, double h) { 2177 if (w != 0 && h != 0) { 2178 resetIfCovers(this.curState.fill, x, y, w, h); 2179 writeOp4(x, y, w, h, NGCanvas.FILL_RECT); 2180 } 2181 } 2182 2183 /** 2184 * Strokes a rectangle using the current stroke paint. 2185 * <p> 2186 * This method will be affected by any of the 2187 * <a href="#comm-attr">global common</a> 2188 * or <a href="#strk-attr">stroke</a> 2189 * attributes as specified in the 2190 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2191 * </p> 2192 * 2193 * @param x the X position of the upper left corner of the rectangle. 2194 * @param y the Y position of the upper left corner of the rectangle. 2195 * @param w the width of the rectangle. 2196 * @param h the height of the rectangle. 2197 */ 2198 public void strokeRect(double x, double y, double w, double h) { 2199 if (w != 0 || h != 0) { 2200 writeOp4(x, y, w, h, NGCanvas.STROKE_RECT); 2201 } 2202 } 2203 2204 /** 2205 * Fills an oval using the current fill paint. 2206 * <p> 2207 * This method will be affected by any of the 2208 * <a href="#comm-attr">global common</a> 2209 * or <a href="#fill-attr">fill</a> 2210 * attributes as specified in the 2211 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2212 * </p> 2213 * 2214 * @param x the X coordinate of the upper left bound of the oval. 2215 * @param y the Y coordinate of the upper left bound of the oval. 2216 * @param w the width at the center of the oval. 2217 * @param h the height at the center of the oval. 2218 */ 2219 public void fillOval(double x, double y, double w, double h) { 2220 if (w != 0 && h != 0) { 2221 writeOp4(x, y, w, h, NGCanvas.FILL_OVAL); 2222 } 2223 } 2224 2225 /** 2226 * Strokes an oval using the current stroke paint. 2227 * <p> 2228 * This method will be affected by any of the 2229 * <a href="#comm-attr">global common</a> 2230 * or <a href="#strk-attr">stroke</a> 2231 * attributes as specified in the 2232 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2233 * </p> 2234 * 2235 * @param x the X coordinate of the upper left bound of the oval. 2236 * @param y the Y coordinate of the upper left bound of the oval. 2237 * @param w the width at the center of the oval. 2238 * @param h the height at the center of the oval. 2239 */ 2240 public void strokeOval(double x, double y, double w, double h) { 2241 if (w != 0 || h != 0) { 2242 writeOp4(x, y, w, h, NGCanvas.STROKE_OVAL); 2243 } 2244 } 2245 2246 /** 2247 * Fills an arc using the current fill paint. A {@code null} ArcType or 2248 * non positive width or height will cause the render command to be ignored. 2249 * <p> 2250 * This method will be affected by any of the 2251 * <a href="#comm-attr">global common</a> 2252 * or <a href="#fill-attr">fill</a> 2253 * attributes as specified in the 2254 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2255 * </p> 2256 * 2257 * @param x the X coordinate of the arc. 2258 * @param y the Y coordinate of the arc. 2259 * @param w the width of the arc. 2260 * @param h the height of the arc. 2261 * @param startAngle the starting angle of the arc in degrees. 2262 * @param arcExtent the angular extent of the arc in degrees. 2263 * @param closure closure type (Round, Chord, Open) or null. 2264 */ 2265 public void fillArc(double x, double y, double w, double h, 2266 double startAngle, double arcExtent, ArcType closure) 2267 { 2268 if (w != 0 && h != 0 && closure != null) { 2269 writeArcType(closure); 2270 writeOp6(x, y, w, h, startAngle, arcExtent, NGCanvas.FILL_ARC); 2271 } 2272 } 2273 2274 /** 2275 * Strokes an Arc using the current stroke paint. A {@code null} ArcType or 2276 * non positive width or height will cause the render command to be ignored. 2277 * <p> 2278 * This method will be affected by any of the 2279 * <a href="#comm-attr">global common</a> 2280 * or <a href="#strk-attr">stroke</a> 2281 * attributes as specified in the 2282 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2283 * </p> 2284 * 2285 * @param x the X coordinate of the arc. 2286 * @param y the Y coordinate of the arc. 2287 * @param w the width of the arc. 2288 * @param h the height of the arc. 2289 * @param startAngle the starting angle of the arc in degrees. 2290 * @param arcExtent arcExtent the angular extent of the arc in degrees. 2291 * @param closure closure type (Round, Chord, Open) or null 2292 */ 2293 public void strokeArc(double x, double y, double w, double h, 2294 double startAngle, double arcExtent, ArcType closure) 2295 { 2296 if (w != 0 && h != 0 && closure != null) { 2297 writeArcType(closure); 2298 writeOp6(x, y, w, h, startAngle, arcExtent, NGCanvas.STROKE_ARC); 2299 } 2300 } 2301 2302 /** 2303 * Fills a rounded rectangle using the current fill paint. 2304 * <p> 2305 * This method will be affected by any of the 2306 * <a href="#comm-attr">global common</a> 2307 * or <a href="#fill-attr">fill</a> 2308 * attributes as specified in the 2309 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2310 * </p> 2311 * 2312 * @param x the X coordinate of the upper left bound of the oval. 2313 * @param y the Y coordinate of the upper left bound of the oval. 2314 * @param w the width at the center of the oval. 2315 * @param h the height at the center of the oval. 2316 * @param arcWidth the arc width of the rectangle corners. 2317 * @param arcHeight the arc height of the rectangle corners. 2318 */ 2319 public void fillRoundRect(double x, double y, double w, double h, 2320 double arcWidth, double arcHeight) 2321 { 2322 if (w != 0 && h != 0) { 2323 writeOp6(x, y, w, h, arcWidth, arcHeight, NGCanvas.FILL_ROUND_RECT); 2324 } 2325 } 2326 2327 /** 2328 * Strokes a rounded rectangle using the current stroke paint. 2329 * <p> 2330 * This method will be affected by any of the 2331 * <a href="#comm-attr">global common</a> 2332 * or <a href="#strk-attr">stroke</a> 2333 * attributes as specified in the 2334 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2335 * </p> 2336 * 2337 * @param x the X coordinate of the upper left bound of the oval. 2338 * @param y the Y coordinate of the upper left bound of the oval. 2339 * @param w the width at the center of the oval. 2340 * @param h the height at the center of the oval. 2341 * @param arcWidth the arc width of the rectangle corners. 2342 * @param arcHeight the arc height of the rectangle corners. 2343 */ 2344 public void strokeRoundRect(double x, double y, double w, double h, 2345 double arcWidth, double arcHeight) 2346 { 2347 if (w != 0 && h != 0) { 2348 writeOp6(x, y, w, h, arcWidth, arcHeight, NGCanvas.STROKE_ROUND_RECT); 2349 } 2350 } 2351 2352 /** 2353 * Strokes a line using the current stroke paint. 2354 * <p> 2355 * This method will be affected by any of the 2356 * <a href="#comm-attr">global common</a> 2357 * or <a href="#strk-attr">stroke</a> 2358 * attributes as specified in the 2359 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2360 * </p> 2361 * 2362 * @param x1 the X coordinate of the starting point of the line. 2363 * @param y1 the Y coordinate of the starting point of the line. 2364 * @param x2 the X coordinate of the ending point of the line. 2365 * @param y2 the Y coordinate of the ending point of the line. 2366 */ 2367 public void strokeLine(double x1, double y1, double x2, double y2) { 2368 writeOp4(x1, y1, x2, y2, NGCanvas.STROKE_LINE); 2369 } 2370 2371 /** 2372 * Fills a polygon with the given points using the currently set fill paint. 2373 * A {@code null} value for any of the arrays will be ignored and nothing will be drawn. 2374 * <p> 2375 * This method will be affected by any of the 2376 * <a href="#comm-attr">global common</a>, 2377 * <a href="#fill-attr">fill</a>, 2378 * or <a href="#path-attr">Fill Rule</a> 2379 * attributes as specified in the 2380 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2381 * </p> 2382 * 2383 * @param xPoints array containing the x coordinates of the polygon's points or null. 2384 * @param yPoints array containing the y coordinates of the polygon's points or null. 2385 * @param nPoints the number of points that make the polygon. 2386 */ 2387 public void fillPolygon(double xPoints[], double yPoints[], int nPoints) { 2388 if (nPoints >= 3) { 2389 writePoly(xPoints, yPoints, nPoints, true, NGCanvas.FILL_PATH); 2390 } 2391 } 2392 2393 /** 2394 * Strokes a polygon with the given points using the currently set stroke paint. 2395 * A {@code null} value for any of the arrays will be ignored and nothing will be drawn. 2396 * <p> 2397 * This method will be affected by any of the 2398 * <a href="#comm-attr">global common</a> 2399 * or <a href="#strk-attr">stroke</a> 2400 * attributes as specified in the 2401 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2402 * </p> 2403 * 2404 * @param xPoints array containing the x coordinates of the polygon's points or null. 2405 * @param yPoints array containing the y coordinates of the polygon's points or null. 2406 * @param nPoints the number of points that make the polygon. 2407 */ 2408 public void strokePolygon(double xPoints[], double yPoints[], int nPoints) { 2409 if (nPoints >= 2) { 2410 writePoly(xPoints, yPoints, nPoints, true, NGCanvas.STROKE_PATH); 2411 } 2412 } 2413 2414 /** 2415 * Strokes a polyline with the given points using the currently set stroke 2416 * paint attribute. 2417 * A {@code null} value for any of the arrays will be ignored and nothing will be drawn. 2418 * <p> 2419 * This method will be affected by any of the 2420 * <a href="#comm-attr">global common</a> 2421 * or <a href="#strk-attr">stroke</a> 2422 * attributes as specified in the 2423 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2424 * </p> 2425 * 2426 * @param xPoints array containing the x coordinates of the polyline's points or null. 2427 * @param yPoints array containing the y coordinates of the polyline's points or null. 2428 * @param nPoints the number of points that make the polyline. 2429 */ 2430 public void strokePolyline(double xPoints[], double yPoints[], int nPoints) { 2431 if (nPoints >= 2) { 2432 writePoly(xPoints, yPoints, nPoints, false, NGCanvas.STROKE_PATH); 2433 } 2434 } 2435 2436 /** 2437 * Draws an image at the given x, y position using the width 2438 * and height of the given image. 2439 * A {@code null} image value or an image still in progress will be ignored. 2440 * <p> 2441 * This method will be affected by any of the 2442 * <a href="#comm-attr">global common</a> 2443 * attributes as specified in the 2444 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2445 * </p> 2446 * 2447 * @param img the image to be drawn or null. 2448 * @param x the X coordinate on the destination for the upper left of the image. 2449 * @param y the Y coordinate on the destination for the upper left of the image. 2450 */ 2451 public void drawImage(Image img, double x, double y) { 2452 if (img == null) return; 2453 double sw = img.getWidth(); 2454 double sh = img.getHeight(); 2455 writeImage(img, x, y, sw, sh); 2456 } 2457 2458 /** 2459 * Draws an image into the given destination rectangle of the canvas. The 2460 * Image is scaled to fit into the destination rectagnle. 2461 * A {@code null} image value or an image still in progress will be ignored. 2462 * <p> 2463 * This method will be affected by any of the 2464 * <a href="#comm-attr">global common</a> 2465 * attributes as specified in the 2466 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2467 * </p> 2468 * 2469 * @param img the image to be drawn or null. 2470 * @param x the X coordinate on the destination for the upper left of the image. 2471 * @param y the Y coordinate on the destination for the upper left of the image. 2472 * @param w the width of the destination rectangle. 2473 * @param h the height of the destination rectangle. 2474 */ 2475 public void drawImage(Image img, double x, double y, double w, double h) { 2476 writeImage(img, x, y, w, h); 2477 } 2478 2479 /** 2480 * Draws the specified source rectangle of the given image to the given 2481 * destination rectangle of the Canvas. 2482 * A {@code null} image value or an image still in progress will be ignored. 2483 * <p> 2484 * This method will be affected by any of the 2485 * <a href="#comm-attr">global common</a> 2486 * attributes as specified in the 2487 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2488 * </p> 2489 * 2490 * @param img the image to be drawn or null. 2491 * @param sx the source rectangle's X coordinate position. 2492 * @param sy the source rectangle's Y coordinate position. 2493 * @param sw the source rectangle's width. 2494 * @param sh the source rectangle's height. 2495 * @param dx the destination rectangle's X coordinate position. 2496 * @param dy the destination rectangle's Y coordinate position. 2497 * @param dw the destination rectangle's width. 2498 * @param dh the destination rectangle's height. 2499 */ 2500 public void drawImage(Image img, 2501 double sx, double sy, double sw, double sh, 2502 double dx, double dy, double dw, double dh) 2503 { 2504 writeImage(img, dx, dy, dw, dh, sx, sy, sw, sh); 2505 } 2506 2507 private PixelWriter writer; 2508 /** 2509 * Returns a {@link PixelWriter} object that can be used to modify 2510 * the pixels of the {@link Canvas} associated with this 2511 * {@code GraphicsContext}. 2512 * All coordinates in the {@code PixelWriter} methods on the returned 2513 * object will be in device space since they refer directly to pixels 2514 * and no other rendering attributes will be applied when modifying 2515 * pixels using this object. 2516 * 2517 * @return the {@code PixelWriter} for modifying the pixels of this 2518 * {@code Canvas} 2519 */ 2520 public PixelWriter getPixelWriter() { 2521 if (writer == null) { 2522 writer = new PixelWriter() { 2523 @Override 2524 public PixelFormat getPixelFormat() { 2525 return PixelFormat.getByteBgraPreInstance(); 2526 } 2527 2528 private BytePixelSetter getSetter() { 2529 return ByteBgraPre.setter; 2530 } 2531 2532 @Override 2533 public void setArgb(int x, int y, int argb) { 2534 GrowableDataBuffer buf = getBuffer(); 2535 buf.putByte(NGCanvas.PUT_ARGB); 2536 buf.putInt(x); 2537 buf.putInt(y); 2538 buf.putInt(argb); 2539 } 2540 2541 @Override 2542 public void setColor(int x, int y, Color c) { 2543 if (c == null) throw new NullPointerException("Color cannot be null"); 2544 int a = (int) Math.round(c.getOpacity() * 255.0); 2545 int r = (int) Math.round(c.getRed() * 255.0); 2546 int g = (int) Math.round(c.getGreen() * 255.0); 2547 int b = (int) Math.round(c.getBlue() * 255.0); 2548 setArgb(x, y, (a << 24) | (r << 16) | (g << 8) | b); 2549 } 2550 2551 private void writePixelBuffer(int x, int y, int w, int h, 2552 byte[] pixels) 2553 { 2554 GrowableDataBuffer buf = getBuffer(); 2555 buf.putByte(NGCanvas.PUT_ARGBPRE_BUF); 2556 buf.putInt(x); 2557 buf.putInt(y); 2558 buf.putInt(w); 2559 buf.putInt(h); 2560 buf.putObject(pixels); 2561 } 2562 2563 private int[] checkBounds(int x, int y, int w, int h, 2564 PixelFormat pf, int scan) 2565 { 2566 // assert (w >= 0 && h >= 0) - checked by caller 2567 int cw = (int) Math.ceil(theCanvas.getWidth()); 2568 int ch = (int) Math.ceil(theCanvas.getHeight()); 2569 if (x >= 0 && y >= 0 && x+w <= cw && y+h <= ch) { 2570 return null; 2571 } 2572 int offset = 0; 2573 if (x < 0) { 2574 w += x; 2575 if (w < 0) return null; 2576 if (pf != null) { 2577 switch (pf.getType()) { 2578 case BYTE_BGRA: 2579 case BYTE_BGRA_PRE: 2580 offset -= x * 4; 2581 break; 2582 case BYTE_RGB: 2583 offset -= x * 3; 2584 break; 2585 case BYTE_INDEXED: 2586 case INT_ARGB: 2587 case INT_ARGB_PRE: 2588 offset -= x; 2589 break; 2590 default: 2591 throw new InternalError("unknown Pixel Format"); 2592 } 2593 } 2594 x = 0; 2595 } 2596 if (y < 0) { 2597 h += y; 2598 if (h < 0) return null; 2599 offset -= y * scan; 2600 y = 0; 2601 } 2602 if (x + w > cw) { 2603 w = cw - x; 2604 if (w < 0) return null; 2605 } 2606 if (y + h > ch) { 2607 h = ch - y; 2608 if (h < 0) return null; 2609 } 2610 return new int[] { 2611 x, y, w, h, offset 2612 }; 2613 } 2614 2615 @Override 2616 public <T extends Buffer> void 2617 setPixels(int x, int y, int w, int h, 2618 PixelFormat<T> pixelformat, 2619 T buffer, int scan) 2620 { 2621 if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null"); 2622 if (buffer == null) throw new NullPointerException("Buffer cannot be null"); 2623 if (w <= 0 || h <= 0) return; 2624 int offset = buffer.position(); 2625 int adjustments[] = checkBounds(x, y, w, h, 2626 pixelformat, scan); 2627 if (adjustments != null) { 2628 x = adjustments[0]; 2629 y = adjustments[1]; 2630 w = adjustments[2]; 2631 h = adjustments[3]; 2632 offset += adjustments[4]; 2633 } 2634 2635 byte pixels[] = new byte[w * h * 4]; 2636 ByteBuffer dst = ByteBuffer.wrap(pixels); 2637 2638 PixelGetter<T> getter = PixelUtils.getGetter(pixelformat); 2639 PixelConverter<T, ByteBuffer> converter = 2640 PixelUtils.getConverter(getter, getSetter()); 2641 converter.convert(buffer, offset, scan, 2642 dst, 0, w * 4, 2643 w, h); 2644 writePixelBuffer(x, y, w, h, pixels); 2645 } 2646 2647 @Override 2648 public void setPixels(int x, int y, int w, int h, 2649 PixelFormat<ByteBuffer> pixelformat, 2650 byte[] buffer, int offset, int scanlineStride) 2651 { 2652 if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null"); 2653 if (buffer == null) throw new NullPointerException("Buffer cannot be null"); 2654 if (w <= 0 || h <= 0) return; 2655 int adjustments[] = checkBounds(x, y, w, h, 2656 pixelformat, scanlineStride); 2657 if (adjustments != null) { 2658 x = adjustments[0]; 2659 y = adjustments[1]; 2660 w = adjustments[2]; 2661 h = adjustments[3]; 2662 offset += adjustments[4]; 2663 } 2664 2665 byte pixels[] = new byte[w * h * 4]; 2666 2667 BytePixelGetter getter = PixelUtils.getByteGetter(pixelformat); 2668 ByteToBytePixelConverter converter = 2669 PixelUtils.getB2BConverter(getter, getSetter()); 2670 converter.convert(buffer, offset, scanlineStride, 2671 pixels, 0, w * 4, 2672 w, h); 2673 writePixelBuffer(x, y, w, h, pixels); 2674 } 2675 2676 @Override 2677 public void setPixels(int x, int y, int w, int h, 2678 PixelFormat<IntBuffer> pixelformat, 2679 int[] buffer, int offset, int scanlineStride) 2680 { 2681 if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null"); 2682 if (buffer == null) throw new NullPointerException("Buffer cannot be null"); 2683 if (w <= 0 || h <= 0) return; 2684 int adjustments[] = checkBounds(x, y, w, h, 2685 pixelformat, scanlineStride); 2686 if (adjustments != null) { 2687 x = adjustments[0]; 2688 y = adjustments[1]; 2689 w = adjustments[2]; 2690 h = adjustments[3]; 2691 offset += adjustments[4]; 2692 } 2693 2694 byte pixels[] = new byte[w * h * 4]; 2695 2696 IntPixelGetter getter = PixelUtils.getIntGetter(pixelformat); 2697 IntToBytePixelConverter converter = 2698 PixelUtils.getI2BConverter(getter, getSetter()); 2699 converter.convert(buffer, offset, scanlineStride, 2700 pixels, 0, w * 4, 2701 w, h); 2702 writePixelBuffer(x, y, w, h, pixels); 2703 } 2704 2705 @Override 2706 public void setPixels(int dstx, int dsty, int w, int h, 2707 PixelReader reader, int srcx, int srcy) 2708 { 2709 if (reader == null) throw new NullPointerException("Reader cannot be null"); 2710 if (w <= 0 || h <= 0) return; 2711 int adjustments[] = checkBounds(dstx, dsty, w, h, null, 0); 2712 if (adjustments != null) { 2713 int newx = adjustments[0]; 2714 int newy = adjustments[1]; 2715 srcx += newx - dstx; 2716 srcy += newy - dsty; 2717 dstx = newx; 2718 dsty = newy; 2719 w = adjustments[2]; 2720 h = adjustments[3]; 2721 } 2722 2723 byte pixels[] = new byte[w * h * 4]; 2724 reader.getPixels(srcx, srcy, w, h, 2725 PixelFormat.getByteBgraPreInstance(), 2726 pixels, 0, w * 4); 2727 writePixelBuffer(dstx, dsty, w, h, pixels); 2728 } 2729 }; 2730 } 2731 return writer; 2732 } 2733 2734 /** 2735 * Sets the effect to be applied after the next draw call, or null to 2736 * disable effects. 2737 * The current effect is a <a href="#comm-attr">common attribute</a> 2738 * used for nearly all rendering operations as specified in the 2739 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2740 * 2741 * @param e the effect to use, or null to disable effects 2742 */ 2743 public void setEffect(Effect e) { 2744 GrowableDataBuffer buf = getBuffer(); 2745 buf.putByte(NGCanvas.EFFECT); 2746 if (e == null) { 2747 curState.effect = null; 2748 buf.putObject(null); 2749 } else { 2750 curState.effect = e.impl_copy(); 2751 curState.effect.impl_sync(); 2752 buf.putObject(curState.effect.impl_getImpl()); 2753 } 2754 } 2755 2756 /** 2757 * Gets a copy of the effect to be applied after the next draw call. 2758 * A null return value means that no effect will be applied after subsequent 2759 * rendering calls. 2760 * The current effect is a <a href="#comm-attr">common attribute</a> 2761 * used for nearly all rendering operations as specified in the 2762 * <a href="#attr-ops-table">Rendering Attributes Table</a>. 2763 * 2764 * @param e an {@code Effect} object that may be used to store the 2765 * copy of the current effect, if it is of a compatible type 2766 * @return the current effect used for all rendering calls, 2767 * or null if there is no current effect 2768 */ 2769 public Effect getEffect(Effect e) { 2770 return curState.effect == null ? null : curState.effect.impl_copy(); 2771 } 2772 2773 /** 2774 * Applies the given effect to the entire bounds of the canvas and stores 2775 * the result back into the same canvas. 2776 * A {@code null} value will be ignored. 2777 * The effect will be applied without any other rendering attributes and 2778 * under an Identity coordinate transform. 2779 * Since the effect is applied to the entire bounds of the canvas, some 2780 * effects may have a confusing result, such as a Reflection effect 2781 * that will apply its reflection off of the bottom of the canvas even if 2782 * only a portion of the canvas has been rendered to and will not be 2783 * visible unless a negative offset is used to bring the reflection back 2784 * into view. 2785 * 2786 * @param e the effect to apply onto the entire destination or null. 2787 */ 2788 public void applyEffect(Effect e) { 2789 if (e == null) return; 2790 GrowableDataBuffer buf = getBuffer(); 2791 buf.putByte(NGCanvas.FX_APPLY_EFFECT); 2792 Effect effect = e.impl_copy(); 2793 effect.impl_sync(); 2794 buf.putObject(effect.impl_getImpl()); 2795 } 2796 }