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