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