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