Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.canvas; 027 028import com.sun.javafx.geom.Arc2D; 029import com.sun.javafx.geom.Path2D; 030import com.sun.javafx.geom.PathIterator; 031import com.sun.javafx.geom.transform.Affine2D; 032import com.sun.javafx.geom.transform.NoninvertibleTransformException; 033import com.sun.javafx.image.BytePixelGetter; 034import com.sun.javafx.image.BytePixelSetter; 035import com.sun.javafx.image.ByteToBytePixelConverter; 036import com.sun.javafx.image.IntPixelGetter; 037import com.sun.javafx.image.IntToBytePixelConverter; 038import com.sun.javafx.image.PixelConverter; 039import com.sun.javafx.image.PixelGetter; 040import com.sun.javafx.image.PixelUtils; 041import com.sun.javafx.image.impl.ByteBgraPre; 042import com.sun.javafx.sg.GrowableDataBuffer; 043import com.sun.javafx.sg.PGCanvas; 044import com.sun.javafx.tk.Toolkit; 045import java.nio.Buffer; 046import java.nio.ByteBuffer; 047import java.nio.IntBuffer; 048import java.util.LinkedList; 049import javafx.geometry.VPos; 050import javafx.scene.effect.Blend; 051import javafx.scene.effect.BlendMode; 052import javafx.scene.effect.Effect; 053import javafx.scene.image.Image; 054import javafx.scene.image.PixelFormat; 055import javafx.scene.image.PixelReader; 056import javafx.scene.image.PixelWriter; 057import javafx.scene.paint.Color; 058import javafx.scene.paint.Paint; 059import javafx.scene.shape.ArcType; 060import javafx.scene.shape.FillRule; 061import javafx.scene.shape.StrokeLineCap; 062import javafx.scene.shape.StrokeLineJoin; 063import javafx.scene.text.Font; 064import javafx.scene.text.TextAlignment; 065import javafx.scene.transform.Affine; 066 067/** 068 * This class is used to issue draw calls to a {@code Canvas} using a buffer. 069 * <p> 070 * Each call pushes the necessary parameters onto the buffer 071 * where it is executed on the image of the {@code Canvas} node. 072 * <p> 073 * A {@code Canvas} only contains one {@code GraphicsContext}, and only one buffer. 074 * If it is not attached to any scene, then it can be modified by any thread, 075 * as long as it is only used from one thread at a time. Once a {@code Canvas} 076 * node is attached to a scene, it must be modified on the JavaFX Application 077 * Thread. 078 * <p> 079 * Calling any method on the {@code GraphicsContext} is considered modifying 080 * its corresponding {@code Canvas} and is subject to the same threading 081 * rules. 082 * <p> 083 * A {@code GraphicsContext} also manages a stack of state objects that can 084 * be saved or restored at anytime. 085 * 086 * <p>Example:</p> 087 * 088 * <p> 089 * <pre> 090import javafx.scene.*; 091import javafx.scene.paint.*; 092import javafx.scene.canvas.*; 093 094Group root = new Group(); 095Scene s = new Scene(root, 300, 300, Color.BLACK); 096 097final Canvas canvas = new Canvas(250,250); 098GraphicsContext gc = canvas.getGraphicsContext2D(); 099 100gc.setFill(Color.BLUE); 101gc.fillRect(75,75,100,100); 102 103root.getChildren().add(canvas); 104 * </pre> 105 * </p> 106 * 107 * @since 2.2 108 */ 109public final class GraphicsContext { 110 Canvas theCanvas; 111 Path2D path; 112 boolean pathDirty; 113 114 State curState; 115 LinkedList<State> stateStack; 116 LinkedList<Path2D> clipStack; 117 118 GraphicsContext(Canvas theCanvas) { 119 this.theCanvas = theCanvas; 120 this.path = new Path2D(); 121 pathDirty = true; 122 123 this.curState = new State(); 124 this.stateStack = new LinkedList<State>(); 125 this.clipStack = new LinkedList<Path2D>(); 126 } 127 128 static class State { 129 double globalAlpha; 130 BlendMode blendop; 131 Affine2D transform; 132 Paint fill; 133 Paint stroke; 134 double linewidth; 135 StrokeLineCap linecap; 136 StrokeLineJoin linejoin; 137 double miterlimit; 138 int numClipPaths; 139 Font font; 140 TextAlignment textalign; 141 VPos textbaseline; 142 Effect effect; 143 FillRule fillRule; 144 145 State() { 146 this(1.0, BlendMode.SRC_OVER, 147 new Affine2D(), 148 Color.BLACK, Color.BLACK, 149 1.0, StrokeLineCap.SQUARE, StrokeLineJoin.MITER, 10.0, 150 0, Font.getDefault(), TextAlignment.LEFT, VPos.BASELINE, 151 null, FillRule.NON_ZERO); 152 } 153 154 State(State copy) { 155 this(copy.globalAlpha, copy.blendop, 156 new Affine2D(copy.transform), 157 copy.fill, copy.stroke, 158 copy.linewidth, copy.linecap, copy.linejoin, copy.miterlimit, 159 copy.numClipPaths, 160 copy.font, copy.textalign, copy.textbaseline, 161 copy.effect, copy.fillRule); 162 } 163 164 State(double globalAlpha, BlendMode blendop, 165 Affine2D transform, Paint fill, Paint stroke, 166 double linewidth, StrokeLineCap linecap, 167 StrokeLineJoin linejoin, double miterlimit, 168 int numClipPaths, 169 Font font, TextAlignment align, VPos baseline, 170 Effect effect, FillRule fillRule) 171 { 172 this.globalAlpha = globalAlpha; 173 this.blendop = blendop; 174 this.transform = transform; 175 this.fill = fill; 176 this.stroke = stroke; 177 this.linewidth = linewidth; 178 this.linecap = linecap; 179 this.linejoin = linejoin; 180 this.miterlimit = miterlimit; 181 this.numClipPaths = numClipPaths; 182 this.font = font; 183 this.textalign = align; 184 this.textbaseline = baseline; 185 this.effect = effect; 186 this.fillRule = fillRule; 187 } 188 189 State copy() { 190 return new State(this); 191 } 192 193 void restore(GraphicsContext ctx) { 194 ctx.setGlobalAlpha(globalAlpha); 195 ctx.setGlobalBlendMode(blendop); 196 ctx.setTransform(transform.getMxx(), transform.getMyx(), 197 transform.getMxy(), transform.getMyy(), 198 transform.getMxt(), transform.getMyt()); 199 ctx.setFill(fill); 200 ctx.setStroke(stroke); 201 ctx.setLineWidth(linewidth); 202 ctx.setLineCap(linecap); 203 ctx.setLineJoin(linejoin); 204 ctx.setMiterLimit(miterlimit); 205 GrowableDataBuffer buf = ctx.getBuffer(); 206 while (ctx.curState.numClipPaths > numClipPaths) { 207 ctx.curState.numClipPaths--; 208 ctx.clipStack.removeLast(); 209 buf.putByte(PGCanvas.POP_CLIP); 210 } 211 ctx.setFillRule(fillRule); 212 ctx.setFont(font); 213 ctx.setTextAlign(textalign); 214 ctx.setTextBaseline(textbaseline); 215 ctx.setEffect(effect); 216 } 217 } 218 219 private GrowableDataBuffer getBuffer() { 220 return theCanvas.getBuffer(); 221 } 222 223 private static float coords[] = new float[6]; 224 private static final byte pgtype[] = { 225 PGCanvas.MOVETO, 226 PGCanvas.LINETO, 227 PGCanvas.QUADTO, 228 PGCanvas.CUBICTO, 229 PGCanvas.CLOSEPATH, 230 }; 231 private static final int numsegs[] = { 2, 2, 4, 6, 0, }; 232 233 private void markPathDirty() { 234 pathDirty = true; 235 } 236 237 private void writePath(byte command) { 238 updateTransform(); 239 GrowableDataBuffer buf = getBuffer(); 240 if (pathDirty) { 241 buf.putByte(PGCanvas.PATHSTART); 242 PathIterator pi = path.getPathIterator(null); 243 while (!pi.isDone()) { 244 int pitype = pi.currentSegment(coords); 245 buf.putByte(pgtype[pitype]); 246 for (int i = 0; i < numsegs[pitype]; i++) { 247 buf.putFloat(coords[i]); 248 } 249 pi.next(); 250 } 251 buf.putByte(PGCanvas.PATHEND); 252 pathDirty = false; 253 } 254 buf.putByte(command); 255 } 256 257 private void writePaint(Paint p, byte command) { 258 GrowableDataBuffer buf = getBuffer(); 259 buf.putByte(command); 260 buf.putObject(Toolkit.getPaintAccessor().getPlatformPaint(p)); 261 } 262 263 private void writeArcType(ArcType closure) { 264 byte type; 265 switch (closure) { 266 case OPEN: type = PGCanvas.ARC_OPEN; break; 267 case CHORD: type = PGCanvas.ARC_CHORD; break; 268 case ROUND: type = PGCanvas.ARC_PIE; break; 269 default: return; // ignored for consistency with other attributes 270 } 271 writeParam(type, PGCanvas.ARC_TYPE); 272 } 273 274 private void writeRectParams(GrowableDataBuffer buf, 275 double x, double y, double w, double h, 276 byte command) 277 { 278 buf.putByte(command); 279 buf.putFloat((float) x); 280 buf.putFloat((float) y); 281 buf.putFloat((float) w); 282 buf.putFloat((float) h); 283 } 284 285 private void writeOp4(double x, double y, double w, double h, byte command) { 286 updateTransform(); 287 writeRectParams(getBuffer(), x, y, w, h, command); 288 } 289 290 private void writeOp6(double x, double y, double w, double h, 291 double v1, double v2, byte command) 292 { 293 updateTransform(); 294 GrowableDataBuffer buf = getBuffer(); 295 buf.putByte(command); 296 buf.putFloat((float) x); 297 buf.putFloat((float) y); 298 buf.putFloat((float) w); 299 buf.putFloat((float) h); 300 buf.putFloat((float) v1); 301 buf.putFloat((float) v2); 302 } 303 304 private float polybuf[] = new float[512]; 305 private void flushPolyBuf(GrowableDataBuffer buf, 306 float polybuf[], int n, byte command) 307 { 308 curState.transform.deltaTransform(polybuf, 0, polybuf, 0, n/2); 309 for (int i = 0; i < n; i += 2) { 310 buf.putByte(command); 311 buf.putFloat(polybuf[i]); 312 buf.putFloat(polybuf[i+1]); 313 command = PGCanvas.LINETO; 314 } 315 } 316 private void writePoly(double xPoints[], double yPoints[], int nPoints, 317 boolean close, byte command) 318 { 319 GrowableDataBuffer buf = getBuffer(); 320 buf.putByte(PGCanvas.PATHSTART); 321 int pos = 0; 322 byte polycmd = PGCanvas.MOVETO; 323 for (int i = 0; i < nPoints; i++) { 324 if (pos >= polybuf.length) { 325 flushPolyBuf(buf, polybuf, pos, polycmd); 326 polycmd = PGCanvas.LINETO; 327 } 328 polybuf[pos++] = (float) xPoints[i]; 329 polybuf[pos++] = (float) yPoints[i]; 330 } 331 flushPolyBuf(buf, polybuf, pos, polycmd); 332 if (close) { 333 buf.putByte(PGCanvas.CLOSEPATH); 334 } 335 buf.putByte(PGCanvas.PATHEND); 336 buf.putByte(command); 337 // Now that we have changed the PG layer path, we need to mark our path dirty. 338 markPathDirty(); 339 } 340 341 private void writeImage(Image img, 342 double dx, double dy, double dw, double dh) 343 { 344 if (img.getProgress() < 1.0) return; 345 Object platformImg = img.impl_getPlatformImage(); 346 if (platformImg == null) return; 347 updateTransform(); 348 GrowableDataBuffer buf = getBuffer(); 349 writeRectParams(buf, dx, dy, dw, dh, PGCanvas.DRAW_IMAGE); 350 buf.putObject(platformImg); 351 } 352 353 private void writeImage(Image img, 354 double dx, double dy, double dw, double dh, 355 double sx, double sy, double sw, double sh) 356 { 357 if (img.getProgress() < 1.0) return; 358 Object platformImg = img.impl_getPlatformImage(); 359 if (platformImg == null) return; 360 updateTransform(); 361 GrowableDataBuffer buf = getBuffer(); 362 writeRectParams(buf, dx, dy, dw, dh, PGCanvas.DRAW_SUBIMAGE); 363 buf.putFloat((float) sx); 364 buf.putFloat((float) sy); 365 buf.putFloat((float) sw); 366 buf.putFloat((float) sh); 367 buf.putObject(platformImg); 368 } 369 370 private void writeText(String text, double x, double y, double maxWidth, 371 byte command) 372 { 373 updateTransform(); 374 GrowableDataBuffer buf = getBuffer(); 375 buf.putByte(command); 376 buf.putFloat((float) x); 377 buf.putFloat((float) y); 378 buf.putFloat((float) maxWidth); 379 buf.putObject(text); 380 } 381 382 private void writeParam(double v, byte command) { 383 GrowableDataBuffer buf = getBuffer(); 384 buf.putByte(command); 385 buf.putFloat((float) v); 386 } 387 388 private void writeParam(byte v, byte command) { 389 GrowableDataBuffer buf = getBuffer(); 390 buf.putByte(command); 391 buf.putByte(v); 392 } 393 394 private boolean txdirty; 395 private void updateTransform() { 396 if (txdirty) { 397 txdirty = false; 398 GrowableDataBuffer buf = getBuffer(); 399 buf.putByte(PGCanvas.TRANSFORM); 400 buf.putDouble(curState.transform.getMxx()); 401 buf.putDouble(curState.transform.getMxy()); 402 buf.putDouble(curState.transform.getMxt()); 403 buf.putDouble(curState.transform.getMyx()); 404 buf.putDouble(curState.transform.getMyy()); 405 buf.putDouble(curState.transform.getMyt()); 406 } 407 } 408 409 /** 410 * Gets the {@code Canvas} that the {@code GraphicsContext} is issuing draw 411 * commands to. There is only ever one {@code Canvas} for a 412 * {@code GraphicsContext}. 413 * 414 * @return Canvas the canvas that this {@code GraphicsContext} is issuing draw 415 * commands to. 416 */ 417 public Canvas getCanvas() { 418 return theCanvas; 419 } 420 421 /** 422 * Saves the following attributes onto a stack. 423 * <ul> 424 * <li>Global Alpha</li> 425 * <li>Global Blend Operation</li> 426 * <li>Transform</li> 427 * <li>Fill Paint</li> 428 * <li>Stroke Paint</li> 429 * <li>Line Width</li> 430 * <li>Line Cap</li> 431 * <li>Line Join</li> 432 * <li>Miter Limit</li> 433 * <li>Number of Clip Paths</li> 434 * <li>Font</li> 435 * <li>Text Align</li> 436 * <li>Text Baseline</li> 437 * <li>Effect</li> 438 * <li>Fill Rule</li> 439 * </ul> 440 * This method does NOT alter the current state in any way. Also, not that 441 * the current path is not saved. 442 */ 443 public void save() { 444 stateStack.push(curState.copy()); 445 } 446 447 /** 448 * Pops the state off of the stack, setting the following attributes to their 449 * value at the time when that state was pushed onto the stack. If the stack 450 * is empty then nothing is changed. 451 * 452 * <ul> 453 * <li>Global Alpha</li> 454 * <li>Global Blend Operation</li> 455 * <li>Transform</li> 456 * <li>Fill Paint</li> 457 * <li>Stroke Paint</li> 458 * <li>Line Width</li> 459 * <li>Line Cap</li> 460 * <li>Line Join</li> 461 * <li>Miter Limit</li> 462 * <li>Number of Clip Paths</li> 463 * <li>Font</li> 464 * <li>Text Align</li> 465 * <li>Text Baseline</li> 466 * <li>Effect</li> 467 * <li>Fill Rule</li> 468 * </ul> 469 */ 470 public void restore() { 471 if (!stateStack.isEmpty()) { 472 State savedState = stateStack.pop(); 473 savedState.restore(this); 474 txdirty = true; 475 } 476 } 477 478 /** 479 * Translates the current transform by x, y. 480 * @param x value to translate along the x axis. 481 * @param y value to translate along the y axis. 482 */ 483 public void translate(double x, double y) { 484 curState.transform.translate(x, y); 485 txdirty = true; 486 } 487 488 /** 489 * Scales the current transform by x, y. 490 * @param x value to scale in the x axis. 491 * @param y value to scale in the y axis. 492 */ 493 public void scale(double x, double y) { 494 curState.transform.scale(x, y); 495 txdirty = true; 496 } 497 498 /** 499 * Rotates the current transform in degrees. 500 * @param degrees value in degrees to rotate the current transform. 501 */ 502 public void rotate(double degrees) { 503 curState.transform.rotate(Math.toRadians(degrees)); 504 txdirty = true; 505 } 506 507 private static Affine2D scratchTX = new Affine2D(); 508 /** 509 * Concatenates the input with the current transform. 510 * 511 * @param mxx - the X coordinate scaling element of the 3x4 matrix 512 * @param myx - the Y coordinate shearing element of the 3x4 matrix 513 * @param mxy - the X coordinate shearing element of the 3x4 matrix 514 * @param myy - the Y coordinate scaling element of the 3x4 matrix 515 * @param mxt - the X coordinate translation element of the 3x4 matrix 516 * @param myt - the Y coordinate translation element of the 3x4 matrix 517 */ 518 public void transform(double mxx, double myx, 519 double mxy, double myy, 520 double mxt, double myt) 521 { 522 scratchTX.setTransform(mxx, myx, 523 mxy, myy, 524 mxt, myt); 525 curState.transform.concatenate(scratchTX); 526 txdirty = true; 527 } 528 529 /** 530 * Concatenates the input with the current transform. Only 2D transforms are 531 * supported. The only values used are the X and Y scaling, translation, and 532 * shearing components of a transform. 533 * 534 * @param xform The affine to be concatenated with the current transform. 535 */ 536 public void transform(Affine xform) { 537 scratchTX.setTransform(xform.getMxx(), xform.getMyx(), 538 xform.getMxy(), xform.getMyy(), 539 xform.getTx(), xform.getTy()); 540 curState.transform.concatenate(scratchTX); 541 txdirty = true; 542 } 543 544 /** 545 * Sets the current transform. 546 * @param mxx - the X coordinate scaling element of the 3x4 matrix 547 * @param myx - the Y coordinate shearing element of the 3x4 matrix 548 * @param mxy - the X coordinate shearing element of the 3x4 matrix 549 * @param myy - the Y coordinate scaling element of the 3x4 matrix 550 * @param mxt - the X coordinate translation element of the 3x4 matrix 551 * @param myt - the Y coordinate translation element of the 3x4 matrix 552 */ 553 public void setTransform(double mxx, double myx, 554 double mxy, double myy, 555 double mxt, double myt) 556 { 557 curState.transform.setTransform(mxx, myx, 558 mxy, myy, 559 mxt, myt); 560 txdirty = true; 561 } 562 563 /** 564 * Sets the current transform. Only 2D transforms are supported. The only 565 * values used are the X and Y scaling, translation, and shearing components 566 * of a transform. 567 * 568 * @param xform The affine to be copied and used as the current transform. 569 */ 570 public void setTransform(Affine xform) { 571 curState.transform.setTransform(xform.getMxx(), xform.getMyx(), 572 xform.getMxy(), xform.getMyy(), 573 xform.getTx(), xform.getTy()); 574 txdirty = true; 575 } 576 577 /** 578 * Returns a copy of the current transform. 579 * 580 * @param xform A transform object that will be used to hold the result. 581 * If xform is non null, then this method will copy the current transform 582 * into that object. If xform is null a new transform object will be 583 * constructed. In either case, the return value is a copy of the current 584 * transform. 585 * 586 * @return A copy of the current transform. 587 */ 588 public Affine getTransform(Affine xform) { 589 if (xform == null) { 590 xform = new Affine(); 591 } 592 593 xform.setMxx(curState.transform.getMxx()); 594 xform.setMxy(curState.transform.getMxy()); 595 xform.setMxz(0); 596 xform.setTx(curState.transform.getMxt()); 597 xform.setMyx(curState.transform.getMyx()); 598 xform.setMyy(curState.transform.getMyy()); 599 xform.setMyz(0); 600 xform.setTy(curState.transform.getMyt()); 601 xform.setMzx(0); 602 xform.setMzy(0); 603 xform.setMzz(1); 604 xform.setTz(0); 605 606 return xform; 607 } 608 609 /** 610 * Returns a copy of the current transform. 611 * 612 * @return a copy of the transform of the current state. 613 */ 614 public Affine getTransform() { 615 return getTransform(null); 616 } 617 618 /** 619 * Sets the global alpha of the current state. 620 * 621 * @param alpha value in the range {@code 0.0-1.0}. The value is clamped if it is 622 * out of range. 623 */ 624 public void setGlobalAlpha(double alpha) { 625 if (curState.globalAlpha != alpha) { 626 curState.globalAlpha = alpha; 627 alpha = (alpha > 1.0) ? 1.0 : (alpha < 0.0) ? 0.0 : alpha; 628 writeParam(alpha, PGCanvas.GLOBAL_ALPHA); 629 } 630 } 631 632 /** 633 * Gets the current global alpha. 634 * 635 * @return the current global alpha. 636 */ 637 public double getGlobalAlpha() { 638 return curState.globalAlpha; 639 } 640 641 private static Blend TMP_BLEND = new Blend(BlendMode.SRC_OVER); 642 /** 643 * Sets the global blend mode. 644 * 645 * @param op the {@code BlendMode} that will be set. 646 */ 647 public void setGlobalBlendMode(BlendMode op) { 648 if (op != null && op != curState.blendop) { 649 GrowableDataBuffer buf = getBuffer(); 650 curState.blendop = op; 651 TMP_BLEND.setMode(op); 652 TMP_BLEND.impl_sync(); 653 buf.putByte(PGCanvas.COMP_MODE); 654 buf.putObject(((com.sun.scenario.effect.Blend)TMP_BLEND.impl_getImpl()).getMode()); 655 } 656 } 657 658 /** 659 * Gets the global blend mode. 660 * 661 * @return the global {@code BlendMode} of the current state. 662 */ 663 public BlendMode getGlobalBlendMode() { 664 return curState.blendop; 665 } 666 667 /** 668 * Sets the current fill attribute. This method affects the paint used for any 669 * method with "fill" in it. For Example, fillRect(...), fillOval(...). 670 * 671 * @param p The {@code Paint} to be used as the fill {@code Paint}. 672 */ 673 public void setFill(Paint p) { 674 if (curState.fill != p) { 675 curState.fill = p; 676 writePaint(p, PGCanvas.FILL_PAINT); 677 } 678 } 679 680 /** 681 * Gets the current fill attribute. This method affects the paint used for any 682 * method with "fill" in it. For Example, fillRect(...), fillOval(...). 683 * 684 * @return p The {@code Paint} to be used as the fill {@code Paint}. 685 */ 686 public Paint getFill() { 687 return curState.fill; 688 } 689 690 /** 691 * Sets the current stroke. 692 * 693 * @param p The Paint to be used as the stroke Paint. 694 */ 695 public void setStroke(Paint p) { 696 if (curState.stroke != p) { 697 curState.stroke = p; 698 writePaint(p, PGCanvas.STROKE_PAINT); 699 } 700 } 701 702 /** 703 * Gets the current stroke. 704 * 705 * @return the {@code Paint} to be used as the stroke {@code Paint}. 706 */ 707 public Paint getStroke() { 708 return curState.stroke; 709 } 710 711 /** 712 * Sets the current line width. 713 * 714 * @param lw value in the range {0-positive infinity}, with any other value 715 * being ignored and leaving the value unchanged. 716 */ 717 public void setLineWidth(double lw) { 718 // Per W3C spec: On setting, zero, negative, infinite, and NaN 719 // values must be ignored, leaving the value unchanged 720 if (lw > 0 && lw < Double.POSITIVE_INFINITY) { 721 if (curState.linewidth != lw) { 722 curState.linewidth = lw; 723 writeParam(lw, PGCanvas.LINE_WIDTH); 724 } 725 } 726 } 727 728 /** 729 * Gets the current line width. 730 * 731 * @return value between 0 and infinity. 732 */ 733 public double getLineWidth() { 734 return curState.linewidth; 735 } 736 737 /** 738 * Sets the current stroke line cap. 739 * 740 * @param cap {@code StrokeLineCap} with a value of Butt, Round, or Square. 741 */ 742 public void setLineCap(StrokeLineCap cap) { 743 if (curState.linecap != cap) { 744 byte v; 745 switch (cap) { 746 case BUTT: v = PGCanvas.CAP_BUTT; break; 747 case ROUND: v = PGCanvas.CAP_ROUND; break; 748 case SQUARE: v = PGCanvas.CAP_SQUARE; break; 749 default: return; 750 } 751 curState.linecap = cap; 752 writeParam(v, PGCanvas.LINE_CAP); 753 } 754 } 755 756 /** 757 * Gets the current stroke line cap. 758 * 759 * @return {@code StrokeLineCap} with a value of Butt, Round, or Square. 760 */ 761 public StrokeLineCap getLineCap() { 762 return curState.linecap; 763 } 764 765 /** 766 * Sets the current stroke line join. 767 * 768 * @param join {@code StrokeLineJoin} with a value of Miter, Bevel, or Round. 769 */ 770 public void setLineJoin(StrokeLineJoin join) { 771 if (curState.linejoin != join) { 772 byte v; 773 switch (join) { 774 case MITER: v = PGCanvas.JOIN_MITER; break; 775 case BEVEL: v = PGCanvas.JOIN_BEVEL; break; 776 case ROUND: v = PGCanvas.JOIN_ROUND; break; 777 default: return; 778 } 779 curState.linejoin = join; 780 writeParam(v, PGCanvas.LINE_JOIN); 781 } 782 } 783 784 /** 785 * Gets the current stroke line join. 786 * 787 * @return {@code StrokeLineJoin} with a value of Miter, Bevel, or Round. 788 */ 789 public StrokeLineJoin getLineJoin() { 790 return curState.linejoin; 791 } 792 793 /** 794 * Sets the current miter limit. 795 * 796 * @param ml miter limit value between 0 and positive infinity with 797 * any other value being ignored and leaving the value unchanged. 798 */ 799 public void setMiterLimit(double ml) { 800 // Per W3C spec: On setting, zero, negative, infinite, and NaN 801 // values must be ignored, leaving the value unchanged 802 if (ml > 0.0 && ml < Double.POSITIVE_INFINITY) { 803 if (curState.miterlimit != ml) { 804 curState.miterlimit = ml; 805 writeParam(ml, PGCanvas.MITER_LIMIT); 806 } 807 } 808 } 809 810 /** 811 * Gets the current miter limit. 812 * 813 * @return the miter limit value in the range {@code 0.0-positive infinity} 814 */ 815 public double getMiterLimit() { 816 return curState.miterlimit; 817 } 818 819 /** 820 * Sets the current Font. 821 * 822 * @param f the Font 823 */ 824 public void setFont(Font f) { 825 if (curState.font != f) { 826 curState.font = f; 827 GrowableDataBuffer buf = getBuffer(); 828 buf.putByte(PGCanvas.FONT); 829 buf.putObject(f.impl_getNativeFont()); 830 } 831 } 832 833 /** 834 * Gets the current Font. 835 * 836 * @return the Font 837 */ 838 public Font getFont() { 839 return curState.font; 840 } 841 842 /** 843 * Defines horizontal text alignment, relative to the text {@code x} origin. 844 * <p> 845 * Let horizontal bounds represent the logical width of a single line of 846 * text. Where each line of text has a separate horizontal bounds. 847 * <p> 848 * Then TextAlignment is specified as: 849 * <ul> 850 * <li>Left: the left edge of the horizontal bounds will be at {@code x}. 851 * <li>Center: the center, halfway between left and right edge, of the 852 * horizontal bounds will be at {@code x}. 853 * <li>Right: the right edge of the horizontal bounds will be at {@code x}. 854 * </ul> 855 * <p> 856 * 857 * Note: Canvas does not support line wrapping, therefore the text 858 * alignment Justify is identical to left aligned text. 859 * <p> 860 * 861 * @param align {@code TextAlignment} with values of Left, Center, Right. 862 */ 863 public void setTextAlign(TextAlignment align) { 864 if (curState.textalign != align) { 865 byte a; 866 switch (align) { 867 case LEFT: a = PGCanvas.ALIGN_LEFT; break; 868 case CENTER: a = PGCanvas.ALIGN_CENTER; break; 869 case RIGHT: a = PGCanvas.ALIGN_RIGHT; break; 870 case JUSTIFY: a = PGCanvas.ALIGN_JUSTIFY; break; 871 default: return; 872 } 873 curState.textalign = align; 874 writeParam(a, PGCanvas.TEXT_ALIGN); 875 } 876 } 877 878 /** 879 * Gets the current {@code TextAlignment}. 880 * 881 * @return {@code TextAlignment} with values of Left, Center, Right, or 882 * Justify. 883 */ 884 public TextAlignment getTextAlign() { 885 return curState.textalign; 886 } 887 888 /** 889 * Sets the current Text Baseline. 890 * 891 * @param baseline {@code VPos} with values of Top, Center, Baseline, or Bottom 892 */ 893 public void setTextBaseline(VPos baseline) { 894 if (curState.textbaseline != baseline) { 895 byte b; 896 switch (baseline) { 897 case TOP: b = PGCanvas.BASE_TOP; break; 898 case CENTER: b = PGCanvas.BASE_MIDDLE; break; 899 case BASELINE: b = PGCanvas.BASE_ALPHABETIC; break; 900 case BOTTOM: b = PGCanvas.BASE_BOTTOM; break; 901 default: return; 902 } 903 curState.textbaseline = baseline; 904 writeParam(b, PGCanvas.TEXT_BASELINE); 905 } 906 } 907 908 /** 909 * Gets the current Text Baseline. 910 * 911 * @return {@code VPos} with values of Top, Center, Baseline, or Bottom 912 */ 913 public VPos getTextBaseline() { 914 return curState.textbaseline; 915 } 916 917 /** 918 * Fills the given string of text at position x, y (0,0 at top left) 919 * with the current fill paint attribute. 920 * 921 * @param text the string of text. 922 * @param x position on the x axis. 923 * @param y position on the y axis. 924 */ 925 public void fillText(String text, double x, double y) { 926 writeText(text, x, y, 0, PGCanvas.FILL_TEXT); 927 } 928 929 /** 930 * draws the given string of text at position x, y (0,0 at top left) 931 * with the current stroke paint attribute. 932 * 933 * @param text the string of text. 934 * @param x position on the x axis. 935 * @param y position on the y axis. 936 */ 937 public void strokeText(String text, double x, double y) { 938 writeText(text, x, y, 0, PGCanvas.STROKE_TEXT); 939 } 940 941 /** 942 * Fills text and includes a maximum width of the string. 943 * 944 * If the width of the text extends past max width, then it will be sized 945 * to fit. 946 * 947 * @param text the string of text. 948 * @param x position on the x axis. 949 * @param y position on the y axis. 950 * @param maxWidth maximum width the text string can have. 951 */ 952 public void fillText(String text, double x, double y, double maxWidth) { 953 if (maxWidth <= 0) return; 954 writeText(text, x, y, maxWidth, PGCanvas.FILL_TEXT); 955 } 956 957 /** 958 * Draws text with stroke paint and includes a maximum width of the string. 959 * 960 * If the width of the text extends past max width, then it will be sized 961 * to fit. 962 * 963 * @param text the string of text. 964 * @param x position on the x axis. 965 * @param y position on the y axis. 966 * @param maxWidth maximum width the text string can have. 967 */ 968 public void strokeText(String text, double x, double y, double maxWidth) { 969 if (maxWidth <= 0) return; 970 writeText(text, x, y, maxWidth, PGCanvas.STROKE_TEXT); 971 } 972 973 974 /** 975 * Set the filling rule constant for determining the interior of the path. 976 * The value must be one of the following constants: 977 * {@code FillRile.EVEN_ODD} or {@code FillRule.NON_ZERO}. 978 * The default value is {@code FillRule.NON_ZERO}. 979 * 980 * @defaultValue FillRule.NON_ZERO 981 */ 982 public void setFillRule(FillRule fillRule) { 983 if (curState.fillRule != fillRule) { 984 byte b; 985 if (fillRule == FillRule.EVEN_ODD) { 986 b = PGCanvas.FILL_RULE_EVEN_ODD; 987 } else { 988 b = PGCanvas.FILL_RULE_NON_ZERO; 989 } 990 curState.fillRule = fillRule; 991 writeParam(b, PGCanvas.FILL_RULE); 992 } 993 } 994 995 /** 996 * Get the filling rule constant for determining the interior of the path. 997 * The default value is {@code FillRule.NON_ZERO}. 998 * 999 * @return current fill rule. 1000 */ 1001 public FillRule getFillRule() { 1002 return curState.fillRule; 1003 } 1004 1005 /** 1006 * Starts a Path 1007 */ 1008 public void beginPath() { 1009 path.reset(); 1010 markPathDirty(); 1011 } 1012 1013 /** 1014 * Issues a move command for the current path to the given x,y coordinate. 1015 * 1016 * @param x0 the X position for the move to command. 1017 * @param y0 the Y position for the move to command. 1018 */ 1019 public void moveTo(double x0, double y0) { 1020 coords[0] = (float) x0; 1021 coords[1] = (float) y0; 1022 curState.transform.transform(coords, 0, coords, 0, 1); 1023 path.moveTo(coords[0], coords[1]); 1024 markPathDirty(); 1025 } 1026 1027 /** 1028 * Adds segments to the current path to make a line at the given x,y 1029 * coordinate. 1030 * 1031 * @param x1 the X coordinate of the ending point of the line. 1032 * @param y1 the Y coordinate of the ending point of the line. 1033 */ 1034 public void lineTo(double x1, double y1) { 1035 coords[0] = (float) x1; 1036 coords[1] = (float) y1; 1037 curState.transform.transform(coords, 0, coords, 0, 1); 1038 path.lineTo(coords[0], coords[1]); 1039 markPathDirty(); 1040 } 1041 1042 /** 1043 * Adds segments to the current path to make a quadratic curve. 1044 * 1045 * @param xc the X coordinate of the control point 1046 * @param yc the Y coordinate of the control point 1047 * @param x1 the X coordinate of the end point 1048 * @param y1 the Y coordinate of the end point 1049 */ 1050 public void quadraticCurveTo(double xc, double yc, double x1, double y1) { 1051 coords[0] = (float) xc; 1052 coords[1] = (float) yc; 1053 coords[2] = (float) x1; 1054 coords[3] = (float) y1; 1055 curState.transform.transform(coords, 0, coords, 0, 2); 1056 path.quadTo(coords[0], coords[1], coords[2], coords[3]); 1057 markPathDirty(); 1058 } 1059 1060 /** 1061 * Adds segments to the current path to make a cubic bezier curve. 1062 * 1063 * @param xc1 the X coordinate of first bezier control point. 1064 * @param yc1 the Y coordinate of the first bezier control point. 1065 * @param xc2 the X coordinate of the second bezier control point. 1066 * @param yc2 the Y coordinate of the second bezier control point. 1067 * @param x1 the X coordinate of the end point. 1068 * @param y1 the Y coordinate of the end point. 1069 */ 1070 public void bezierCurveTo(double xc1, double yc1, double xc2, double yc2, double x1, double y1) { 1071 coords[0] = (float) xc1; 1072 coords[1] = (float) yc1; 1073 coords[2] = (float) xc2; 1074 coords[3] = (float) yc2; 1075 coords[4] = (float) x1; 1076 coords[5] = (float) y1; 1077 curState.transform.transform(coords, 0, coords, 0, 3); 1078 path.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); 1079 markPathDirty(); 1080 } 1081 1082 /** 1083 * Adds segments to the current path to make an arc. 1084 * 1085 * @param x1 the X coordinate of the first point of the arc. 1086 * @param y1 the Y coordinate of the first point of the arc. 1087 * @param x2 the X coordinate of the second point of the arc. 1088 * @param y2 the Y coordinate of the second point of the arc. 1089 * @param radius the radius of the arc in the range {0.0-positive infinity}. 1090 */ 1091 public void arcTo(double x1, double y1, double x2, double y2, double radius) { 1092 if (path.getNumCommands() == 0) { 1093 moveTo(x1, y1); 1094 lineTo(x1, y1); 1095 } else if (!tryArcTo((float) x1, (float) y1, (float) x2, (float) y2, 1096 (float) radius)) 1097 { 1098 lineTo(x1, y1); 1099 } 1100 } 1101 1102 private static double lenSq(double x0, double y0, double x1, double y1) { 1103 x1 -= x0; 1104 y1 -= y0; 1105 return x1 * x1 + y1 * y1; 1106 } 1107 1108 private boolean tryArcTo(float x1, float y1, float x2, float y2, float radius) { 1109 float x0, y0; 1110 if (curState.transform.isTranslateOrIdentity()) { 1111 x0 = (float) (path.getCurrentX() - curState.transform.getMxt()); 1112 y0 = (float) (path.getCurrentY() - curState.transform.getMyt()); 1113 } else { 1114 coords[0] = path.getCurrentX(); 1115 coords[1] = path.getCurrentY(); 1116 try { 1117 curState.transform.inverseTransform(coords, 0, coords, 0, 1); 1118 } catch (NoninvertibleTransformException e) { 1119 return false; 1120 } 1121 x0 = coords[0]; 1122 y0 = coords[1]; 1123 } 1124 // call x1,y1 the corner point 1125 // If 2*theta is the angle described by p0->p1->p2 1126 // then theta is the angle described by p0->p1->centerpt and 1127 // centerpt->p1->p2 1128 // We know that the distance from the arc center to the tangent points 1129 // is r, and if A is the distance from the corner to the tangent point 1130 // then we know: 1131 // tan(theta) = r/A 1132 // A = r / sin(theta) 1133 // B = A * cos(theta) = r * (sin/cos) = r * tan 1134 // We use the cosine rule on the triangle to get the 2*theta angle: 1135 // cosB = (a^2 + c^2 - b^2) / (2ac) 1136 // where a and c are the adjacent sides and b is the opposite side 1137 // i.e. a = p0->p1, c=p1->p2, b=p0->p2 1138 // Then we can use the tan^2 identity to compute B: 1139 // tan^2 = (1 - cos(2theta)) / (1 + cos(2theta)) 1140 double lsq01 = lenSq(x0, y0, x1, y1); 1141 double lsq12 = lenSq(x1, y1, x2, y2); 1142 double lsq02 = lenSq(x0, y0, x2, y2); 1143 double len01 = Math.sqrt(lsq01); 1144 double len12 = Math.sqrt(lsq12); 1145 double cosnum = lsq01 + lsq12 - lsq02; 1146 double cosden = 2.0 * len01 * len12; 1147 if (cosden == 0.0 || radius <= 0f) { 1148 return false; 1149 } 1150 double cos_2theta = cosnum / cosden; 1151 double tansq_den = (1.0 + cos_2theta); 1152 if (tansq_den == 0.0) { 1153 return false; 1154 } 1155 double tansq_theta = (1.0 - cos_2theta) / tansq_den; 1156 double A = radius / Math.sqrt(tansq_theta); 1157 double tx0 = x1 + (A / len01) * (x0 - x1); 1158 double ty0 = y1 + (A / len01) * (y0 - y1); 1159 double tx1 = x1 + (A / len12) * (x2 - x1); 1160 double ty1 = y1 + (A / len12) * (y2 - y1); 1161 // The midpoint between the two tangent points 1162 double mx = (tx0 + tx1) / 2.0; 1163 double my = (ty0 + ty1) / 2.0; 1164 // similar triangles tell us that: 1165 // len(m,center)/len(m,tangent) = len(m,tangent)/len(corner,m) 1166 // len(m,center) = lensq(m,tangent)/len(corner,m) 1167 // center = m + (m - p1) * len(m,center) / len(corner,m) 1168 // = m + (m - p1) * (lensq(m,tangent) / lensq(corner,m)) 1169 double lenratioden = lenSq(mx, my, x1, y1); 1170 if (lenratioden == 0.0) { 1171 return false; 1172 } 1173 double lenratio = lenSq(mx, my, tx0, ty0) / lenratioden; 1174 double cx = mx + (mx - x1) * lenratio; 1175 double cy = my + (my - y1) * lenratio; 1176 if (!(cx == cx && cy == cy)) { 1177 return false; 1178 } 1179 // Looks like we are good to draw, first we have to get to the 1180 // initial tangent point with a line segment. 1181 if (tx0 != x0 || ty0 != y0) { 1182 lineTo(tx0, ty0); 1183 } 1184 // We need sin(arc/2), cos(arc/2) 1185 // and possibly sin(arc/4), cos(arc/4) if we need 2 cubic beziers 1186 // We have tan(theta) = tan(tri/2) 1187 // arc = 180-tri 1188 // arc/2 = (180-tri)/2 = 90-(tri/2) 1189 // sin(arc/2) = sin(90-(tri/2)) = cos(tri/2) 1190 // cos(arc/2) = cos(90-(tri/2)) = sin(tri/2) 1191 // 2theta = tri, therefore theta = tri/2 1192 // cos(tri/2)^2 = (1+cos(tri)) / 2.0 = (1+cos_2theta)/2.0 1193 // sin(tri/2)^2 = (1-cos(tri)) / 2.0 = (1-cos_2theta)/2.0 1194 // sin(arc/2) = cos(tri/2) = sqrt((1+cos_2theta)/2.0) 1195 // cos(arc/2) = sin(tri/2) = sqrt((1-cos_2theta)/2.0) 1196 // We compute cos(arc/2) here as we need it in either case below 1197 double coshalfarc = Math.sqrt((1.0 - cos_2theta) / 2.0); 1198 boolean ccw = (ty0 - cy) * (tx1 - cx) > (ty1 - cy) * (tx0 - cx); 1199 // If the arc covers more than 90 degrees then we must use 2 1200 // cubic beziers to get a decent approximation. 1201 // arc = 180-tri 1202 // arc = 180-2*theta 1203 // arc > 90 implies 2*theta < 90 1204 // 2*theta < 90 implies cos_2theta > 0 1205 // So, we need 2 cubics if cos_2theta > 0 1206 if (cos_2theta <= 0.0) { 1207 // 1 cubic bezier 1208 double sinhalfarc = Math.sqrt((1.0 + cos_2theta) / 2.0); 1209 double cv = 4.0 / 3.0 * sinhalfarc / (1.0 + coshalfarc); 1210 if (ccw) cv = -cv; 1211 double cpx0 = tx0 - cv * (ty0 - cy); 1212 double cpy0 = ty0 + cv * (tx0 - cx); 1213 double cpx1 = tx1 + cv * (ty1 - cy); 1214 double cpy1 = ty1 - cv * (tx1 - cx); 1215 bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1); 1216 } else { 1217 // 2 cubic beziers 1218 // We need sin(arc/4) and cos(arc/4) 1219 // We computed cos(arc/2), so we can compute them as follows: 1220 // sin(arc/4) = sqrt((1 - cos(arc/2)) / 2) 1221 // cos(arc/4) = sart((1 + cos(arc/2)) / 2) 1222 double sinqtrarc = Math.sqrt((1.0 - coshalfarc) / 2.0); 1223 double cosqtrarc = Math.sqrt((1.0 + coshalfarc) / 2.0); 1224 double cv = 4.0 / 3.0 * sinqtrarc / (1.0 + cosqtrarc); 1225 if (ccw) cv = -cv; 1226 double midratio = radius / Math.sqrt(lenratioden); 1227 double midarcx = cx + (x1 - mx) * midratio; 1228 double midarcy = cy + (y1 - my) * midratio; 1229 double cpx0 = tx0 - cv * (ty0 - cy); 1230 double cpy0 = ty0 + cv * (tx0 - cx); 1231 double cpx1 = midarcx + cv * (midarcy - cy); 1232 double cpy1 = midarcy - cv * (midarcx - cx); 1233 bezierCurveTo(cpx0, cpy0, cpx1, cpy1, midarcx, midarcy); 1234 cpx0 = midarcx - cv * (midarcy - cy); 1235 cpy0 = midarcy + cv * (midarcx - cx); 1236 cpx1 = tx1 + cv * (ty1 - cy); 1237 cpy1 = ty1 - cv * (tx1 - cx); 1238 bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1); 1239 } 1240 return true; 1241 } 1242 1243 private static final Arc2D TEMP_ARC = new Arc2D(); 1244 /** 1245 * Adds path elements to the current path to make an arc that uses Euclidean 1246 * degrees. This Euclidean orientation sweeps from East to North, then West, 1247 * then South, then back to East. 1248 * 1249 * @param centerX the center x position of the arc. 1250 * @param centerY the center y position of the arc. 1251 * @param radiusX the x radius of the arc. 1252 * @param radiusY the y radius of the arc. 1253 * @param startAngle the starting angle of the arc in the range {@code 0-360.0} 1254 * @param length the length of the baseline of the arc. 1255 */ 1256 public void arc(double centerX, double centerY, 1257 double radiusX, double radiusY, 1258 double startAngle, double length) 1259 { 1260 TEMP_ARC.setArc((float)(centerX - radiusX), // x 1261 (float)(centerY - radiusY), // y 1262 (float)(radiusX * 2.0), // w 1263 (float)(radiusY * 2.0), // h 1264 (float)startAngle, 1265 (float)length, 1266 Arc2D.OPEN); 1267 path.append(TEMP_ARC.getPathIterator(curState.transform), true); 1268 markPathDirty(); 1269 } 1270 1271 /** 1272 * Adds path elements to the current path to make a rectangle. 1273 * 1274 * @param x x position of the upper left corner of the rectangle. 1275 * @param y y position of the upper left corner of the rectangle. 1276 * @param w width of the rectangle. 1277 * @param h height of the rectangle. 1278 */ 1279 public void rect(double x, double y, double w, double h) { 1280 coords[0] = (float) x; 1281 coords[1] = (float) y; 1282 coords[2] = (float) w; 1283 coords[3] = (float) 0; 1284 coords[4] = (float) 0; 1285 coords[5] = (float) h; 1286 curState.transform.deltaTransform(coords, 0, coords, 0, 3); 1287 float x0 = coords[0] + (float) curState.transform.getMxt(); 1288 float y0 = coords[1] + (float) curState.transform.getMyt(); 1289 float dx1 = coords[2]; 1290 float dy1 = coords[3]; 1291 float dx2 = coords[4]; 1292 float dy2 = coords[5]; 1293 path.moveTo(x0, y0); 1294 path.lineTo(x0+dx1, y0+dy1); 1295 path.lineTo(x0+dx1+dx2, y0+dy1+dy2); 1296 path.lineTo(x0+dx2, y0+dy2); 1297 path.closePath(); 1298 markPathDirty(); 1299// path.moveTo(x0, y0); // not needed, closepath leaves pen at moveto 1300 } 1301 1302 /** 1303 * Appends an SVG Path string to the current path. If there is no current 1304 * path the string must then start with either type of move command. 1305 * 1306 * @param svgpath the SVG Path string. 1307 */ 1308 public void appendSVGPath(String svgpath) { 1309 boolean prependMoveto = true; 1310 boolean skipMoveto = true; 1311 for (int i = 0; i < svgpath.length(); i++) { 1312 switch (svgpath.charAt(i)) { 1313 case ' ': 1314 case '\t': 1315 case '\r': 1316 case '\n': 1317 continue; 1318 case 'M': 1319 prependMoveto = skipMoveto = false; 1320 break; 1321 case 'm': 1322 if (path.getNumCommands() == 0) { 1323 // An initial relative moveTo becomes absolute 1324 prependMoveto = false; 1325 } 1326 // Even if we prepend an initial moveTo in the temp 1327 // path, we do not want to delete the resulting initial 1328 // moveTo because the relative moveto will be folded 1329 // into it by an optimization in the Path2D object. 1330 skipMoveto = false; 1331 break; 1332 } 1333 break; 1334 } 1335 Path2D p2d = new Path2D(); 1336 if (prependMoveto && path.getNumCommands() > 0) { 1337 float x0, y0; 1338 if (curState.transform.isTranslateOrIdentity()) { 1339 x0 = (float) (path.getCurrentX() - curState.transform.getMxt()); 1340 y0 = (float) (path.getCurrentY() - curState.transform.getMyt()); 1341 } else { 1342 coords[0] = path.getCurrentX(); 1343 coords[1] = path.getCurrentY(); 1344 try { 1345 curState.transform.inverseTransform(coords, 0, coords, 0, 1); 1346 } catch (NoninvertibleTransformException e) { 1347 } 1348 x0 = coords[0]; 1349 y0 = coords[1]; 1350 } 1351 p2d.moveTo(x0, y0); 1352 } else { 1353 skipMoveto = false; 1354 } 1355 p2d.appendSVGPath(svgpath); 1356 PathIterator pi = p2d.getPathIterator(curState.transform); 1357 if (skipMoveto) { 1358 // We need to delete the initial moveto and let the path 1359 // extend from the actual existing geometry. 1360 pi.next(); 1361 } 1362 path.append(pi, false); 1363 } 1364 1365 /** 1366 * Closes the path. 1367 */ 1368 public void closePath() { 1369 path.closePath(); 1370 markPathDirty(); 1371 } 1372 1373 /** 1374 * Fills the path with the current fill paint. 1375 */ 1376 public void fill() { 1377 writePath(PGCanvas.FILL_PATH); 1378 } 1379 1380 /** 1381 * Strokes the path with the current stroke paint. 1382 */ 1383 public void stroke() { 1384 writePath(PGCanvas.STROKE_PATH); 1385 } 1386 1387 /** 1388 * Clips using the current path 1389 */ 1390 public void clip() { 1391 Path2D clip = new Path2D(path); 1392 clipStack.addLast(clip); 1393 curState.numClipPaths++; 1394 GrowableDataBuffer buf = getBuffer(); 1395 buf.putByte(PGCanvas.PUSH_CLIP); 1396 buf.putObject(clip); 1397 } 1398 1399 /** 1400 * Returns true if the the given x,y point is inside the path. 1401 * 1402 * @param x the X coordinate to use for the check. 1403 * @param y the Y coordinate to use for the check. 1404 * @return true if the point given is inside the path, false 1405 * otherwise. 1406 */ 1407 public boolean isPointInPath(double x, double y) { 1408 // TODO: HTML5 considers points on the path to be inside, but we 1409 // implement a halfin-halfout approach... 1410 return path.contains((float) x, (float) y); 1411 } 1412 1413 /** 1414 * Clears a portion of the canvas with a transparent color value. 1415 * 1416 * @param x X position of the upper left corner of the rectangle. 1417 * @param y Y position of the upper left corner of the rectangle. 1418 * @param w width of the rectangle. 1419 * @param h height of the rectangle. 1420 */ 1421 public void clearRect(double x, double y, double w, double h) { 1422 if (w != 0 && h != 0) { 1423 writeOp4(x, y, w, h, PGCanvas.CLEAR_RECT); 1424 } 1425 } 1426 1427 /** 1428 * Fills a rectangle using the current fill paint. 1429 * 1430 * @param x the X position of the upper left corner of the rectangle. 1431 * @param y the Y position of the upper left corner of the rectangle. 1432 * @param w the width of the rectangle. 1433 * @param h the height of the rectangle. 1434 */ 1435 public void fillRect(double x, double y, double w, double h) { 1436 if (w != 0 && h != 0) { 1437 writeOp4(x, y, w, h, PGCanvas.FILL_RECT); 1438 } 1439 } 1440 1441 /** 1442 * Strokes a rectangle using the current stroke paint. 1443 * 1444 * @param x the X position of the upper left corner of the rectangle. 1445 * @param y the Y position of the upper left corner of the rectangle. 1446 * @param w the width of the rectangle. 1447 * @param h the height of the rectangle. 1448 */ 1449 public void strokeRect(double x, double y, double w, double h) { 1450 if (w != 0 || h != 0) { 1451 writeOp4(x, y, w, h, PGCanvas.STROKE_RECT); 1452 } 1453 } 1454 1455 /** 1456 * Fills an oval using the current fill paint. 1457 * 1458 * @param x the X coordinate of the upper left bound of the oval. 1459 * @param y the Y coordinate of the upper left bound of the oval. 1460 * @param w the width at the center of the oval. 1461 * @param h the height at the center of the oval. 1462 */ 1463 public void fillOval(double x, double y, double w, double h) { 1464 if (w != 0 && h != 0) { 1465 writeOp4(x, y, w, h, PGCanvas.FILL_OVAL); 1466 } 1467 } 1468 1469 /** 1470 * Strokes a rectangle using the current stroke paint. 1471 * 1472 * @param x the X coordinate of the upper left bound of the oval. 1473 * @param y the Y coordinate of the upper left bound of the oval. 1474 * @param w the width at the center of the oval. 1475 * @param h the height at the center of the oval. 1476 */ 1477 public void strokeOval(double x, double y, double w, double h) { 1478 if (w != 0 || h != 0) { 1479 writeOp4(x, y, w, h, PGCanvas.STROKE_OVAL); 1480 } 1481 } 1482 1483 /** 1484 * Fills an arc using the current fill paint. 1485 * 1486 * @param x the X coordinate of the arc. 1487 * @param y the Y coordinate of the arc. 1488 * @param w the width of the arc. 1489 * @param h the height of the arc. 1490 * @param startAngle the starting angle of the arc in degrees. 1491 * @param arcExtent the angular extent of the arc in degrees. 1492 * @param closure closure type (Round, Chord, Open). 1493 */ 1494 public void fillArc(double x, double y, double w, double h, 1495 double startAngle, double arcExtent, ArcType closure) 1496 { 1497 if (w != 0 && h != 0) { 1498 writeArcType(closure); 1499 writeOp6(x, y, w, h, startAngle, arcExtent, PGCanvas.FILL_ARC); 1500 } 1501 } 1502 1503 /** 1504 * Strokes an Arc using the current stroke paint. 1505 * 1506 * @param x the X coordinate of the arc. 1507 * @param y the Y coordinate of the arc. 1508 * @param w the width of the arc. 1509 * @param h the height of the arc. 1510 * @param startAngle the starting angle of the arc in degrees. 1511 * @param arcExtent arcExtent the angular extent of the arc in degrees. 1512 * @param closure closure type (Round, Chord, Open). 1513 */ 1514 public void strokeArc(double x, double y, double w, double h, 1515 double startAngle, double arcExtent, ArcType closure) 1516 { 1517 if (w != 0 && h != 0) { 1518 writeArcType(closure); 1519 writeOp6(x, y, w, h, startAngle, arcExtent, PGCanvas.STROKE_ARC); 1520 } 1521 } 1522 1523 /** 1524 * Fills a rounded rectangle using the current fill paint. 1525 * 1526 * @param x the X coordinate of the upper left bound of the oval. 1527 * @param y the Y coordinate of the upper left bound of the oval. 1528 * @param w the width at the center of the oval. 1529 * @param h the height at the center of the oval. 1530 * @param arcWidth the arc width of the rectangle corners. 1531 * @param arcHeight the arc height of the rectangle corners. 1532 */ 1533 public void fillRoundRect(double x, double y, double w, double h, 1534 double arcWidth, double arcHeight) 1535 { 1536 if (w != 0 && h != 0) { 1537 writeOp6(x, y, w, h, arcWidth, arcHeight, PGCanvas.FILL_ROUND_RECT); 1538 } 1539 } 1540 1541 /** 1542 * Strokes a rounded rectangle using the current stroke paint. 1543 * 1544 * @param x the X coordinate of the upper left bound of the oval. 1545 * @param y the Y coordinate of the upper left bound of the oval. 1546 * @param w the width at the center of the oval. 1547 * @param h the height at the center of the oval. 1548 * @param arcWidth the arc width of the rectangle corners. 1549 * @param arcHeight the arc height of the rectangle corners. 1550 */ 1551 public void strokeRoundRect(double x, double y, double w, double h, 1552 double arcWidth, double arcHeight) 1553 { 1554 if (w != 0 && h != 0) { 1555 writeOp6(x, y, w, h, arcWidth, arcHeight, PGCanvas.STROKE_ROUND_RECT); 1556 } 1557 } 1558 1559 /** 1560 * Strokes a line using the current stroke paint. 1561 * 1562 * @param x1 the X coordinate of the starting point of the line. 1563 * @param y1 the Y coordinate of the starting point of the line. 1564 * @param x2 the X coordinate of the ending point of the line. 1565 * @param y2 the Y coordinate of the ending point of the line. 1566 */ 1567 public void strokeLine(double x1, double y1, double x2, double y2) { 1568 writeOp4(x1, y1, x2, y2, PGCanvas.STROKE_LINE); 1569 } 1570 1571 /** 1572 * Fills a polygon with the given points using the currently set fill paint. 1573 * 1574 * @param xPoints array containing the x coordinates of the polygon's points. 1575 * @param yPoints array containing the y coordinates of the polygon's points. 1576 * @param nPoints the number of points that make the polygon. 1577 */ 1578 public void fillPolygon(double xPoints[], double yPoints[], int nPoints) { 1579 if (nPoints >= 3) { 1580 writePoly(xPoints, yPoints, nPoints, true, PGCanvas.FILL_PATH); 1581 } 1582 } 1583 1584 /** 1585 * Strokes a polygon with the given points using the currently set stroke paint. 1586 * 1587 * @param xPoints array containing the x coordinates of the polygon's points. 1588 * @param yPoints array containing the y coordinates of the polygon's points. 1589 * @param nPoints the number of points that make the polygon. 1590 */ 1591 public void strokePolygon(double xPoints[], double yPoints[], int nPoints) { 1592 if (nPoints >= 2) { 1593 writePoly(xPoints, yPoints, nPoints, true, PGCanvas.STROKE_PATH); 1594 } 1595 } 1596 1597 /** 1598 * Draws a polyline with the given points using the currently set stroke 1599 * paint attribute. 1600 * 1601 * @param xPoints array containing the x coordinates of the polyline's points. 1602 * @param yPoints array containing the y coordinates of the polyline's points. 1603 * @param nPoints the number of points that make the polyline. 1604 */ 1605 public void strokePolyline(double xPoints[], double yPoints[], int nPoints) { 1606 if (nPoints >= 2) { 1607 writePoly(xPoints, yPoints, nPoints, false, PGCanvas.STROKE_PATH); 1608 } 1609 } 1610 1611 /** 1612 * Draws an image at the given x, y position using the width 1613 * and height of the given image. 1614 * 1615 * @param img the image to be drawn. 1616 * @param x the X coordinate on the destination for the upper left of the image. 1617 * @param y the Y coordinate on the destination for the upper left of the image. 1618 */ 1619 public void drawImage(Image img, double x, double y) { 1620 double sw = img.getWidth(); 1621 double sh = img.getHeight(); 1622 writeImage(img, x, y, sw, sh); 1623 } 1624 1625 /** 1626 * Draws an image into the given destination rectangle of the canvas. The 1627 * Image is scaled to fit into the destination rectagnle. 1628 * 1629 * @param img the image to be drawn. 1630 * @param x the X coordinate on the destination for the upper left of the image. 1631 * @param y the Y coordinate on the destination for the upper left of the image. 1632 * @param w the width of the destination rectangle. 1633 * @param h the height of the destination rectangle. 1634 */ 1635 public void drawImage(Image img, double x, double y, double w, double h) { 1636 writeImage(img, x, y, w, h); 1637 } 1638 1639 /** 1640 * Draws the current source rectangle of the given image to the given 1641 * destination rectangle of the Canvas. 1642 * 1643 * @param img the image to be drawn. 1644 * @param sx the source rectangle's X coordinate position. 1645 * @param sy the source rectangle's Y coordinate position. 1646 * @param sw the source rectangle's width. 1647 * @param sh the source rectangle's height. 1648 * @param dx the destination rectangle's X coordinate position. 1649 * @param dy the destination rectangle's Y coordinate position. 1650 * @param dw the destination rectangle's width. 1651 * @param dh the destination rectangle's height. 1652 */ 1653 public void drawImage(Image img, 1654 double sx, double sy, double sw, double sh, 1655 double dx, double dy, double dw, double dh) 1656 { 1657 writeImage(img, dx, dy, dw, dh, sx, sy, sw, sh); 1658 } 1659 1660 private PixelWriter writer; 1661 /** 1662 * Returns a {@link PixelWriter} object that can be used to modify 1663 * the pixels of the {@link Canvas} associated with this 1664 * {@code GraphicsContext}. 1665 * All coordinates in the {@code PixelWriter} methods on the returned 1666 * object will be in device space since they refer directly to pixels. 1667 * 1668 * @return the {@code PixelWriter} for modifying the pixels of this 1669 * {@code Canvas} 1670 */ 1671 public PixelWriter getPixelWriter() { 1672 if (writer == null) { 1673 writer = new PixelWriter() { 1674 @Override 1675 public PixelFormat getPixelFormat() { 1676 return PixelFormat.getByteBgraPreInstance(); 1677 } 1678 1679 private BytePixelSetter getSetter() { 1680 return ByteBgraPre.setter; 1681 } 1682 1683 @Override 1684 public void setArgb(int x, int y, int argb) { 1685 GrowableDataBuffer buf = getBuffer(); 1686 buf.putByte(PGCanvas.PUT_ARGB); 1687 buf.putInt(x); 1688 buf.putInt(y); 1689 buf.putInt(argb); 1690 } 1691 1692 @Override 1693 public void setColor(int x, int y, Color c) { 1694 int a = (int) Math.round(c.getOpacity() * 255.0); 1695 int r = (int) Math.round(c.getRed() * 255.0); 1696 int g = (int) Math.round(c.getGreen() * 255.0); 1697 int b = (int) Math.round(c.getBlue() * 255.0); 1698 setArgb(x, y, (a << 24) | (r << 16) | (g << 8) | b); 1699 } 1700 1701 private void writePixelBuffer(int x, int y, int w, int h, 1702 byte[] pixels) 1703 { 1704 GrowableDataBuffer buf = getBuffer(); 1705 buf.putByte(PGCanvas.PUT_ARGBPRE_BUF); 1706 buf.putInt(x); 1707 buf.putInt(y); 1708 buf.putInt(w); 1709 buf.putInt(h); 1710 buf.putObject(pixels); 1711 } 1712 1713 private int[] checkBounds(int x, int y, int w, int h, 1714 PixelFormat pf, int scan) 1715 { 1716 // assert (w >= 0 && h >= 0) - checked by caller 1717 int cw = (int) theCanvas.getWidth(); 1718 int ch = (int) theCanvas.getHeight(); 1719 if (x >= 0 && y >= 0 && x+w <= cw && y+h <= ch) { 1720 return null; 1721 } 1722 int offset = 0; 1723 if (x < 0) { 1724 w += x; 1725 if (w < 0) return null; 1726 if (pf != null) { 1727 switch (pf.getType()) { 1728 case BYTE_BGRA: 1729 case BYTE_BGRA_PRE: 1730 offset -= x * 4; 1731 break; 1732 case BYTE_RGB: 1733 offset -= x * 3; 1734 break; 1735 case BYTE_INDEXED: 1736 case INT_ARGB: 1737 case INT_ARGB_PRE: 1738 offset -= x; 1739 break; 1740 default: 1741 throw new InternalError("unknown Pixel Format"); 1742 } 1743 } 1744 x = 0; 1745 } 1746 if (y < 0) { 1747 h += y; 1748 if (h < 0) return null; 1749 offset -= y * scan; 1750 y = 0; 1751 } 1752 if (x + w > cw) { 1753 w = cw - x; 1754 if (w < 0) return null; 1755 } 1756 if (y + h > ch) { 1757 h = ch - y; 1758 if (h < 0) return null; 1759 } 1760 return new int[] { 1761 x, y, w, h, offset 1762 }; 1763 } 1764 1765 @Override 1766 public <T extends Buffer> void 1767 setPixels(int x, int y, int w, int h, 1768 PixelFormat<T> pixelformat, 1769 T buffer, int scan) 1770 { 1771 if (w <= 0 || h <= 0) return; 1772 int offset = buffer.position(); 1773 int adjustments[] = checkBounds(x, y, w, h, 1774 pixelformat, scan); 1775 if (adjustments != null) { 1776 x = adjustments[0]; 1777 y = adjustments[1]; 1778 w = adjustments[2]; 1779 h = adjustments[3]; 1780 offset += adjustments[4]; 1781 } 1782 1783 byte pixels[] = new byte[w * h * 4]; 1784 ByteBuffer dst = ByteBuffer.wrap(pixels); 1785 1786 PixelGetter<T> getter = PixelUtils.getGetter(pixelformat); 1787 PixelConverter<T, ByteBuffer> converter = 1788 PixelUtils.getConverter(getter, getSetter()); 1789 converter.convert(buffer, offset, scan, 1790 dst, 0, w * 4, 1791 w, h); 1792 writePixelBuffer(x, y, w, h, pixels); 1793 } 1794 1795 @Override 1796 public void setPixels(int x, int y, int w, int h, 1797 PixelFormat<ByteBuffer> pixelformat, 1798 byte[] buffer, int offset, int scanlineStride) 1799 { 1800 if (w <= 0 || h <= 0) return; 1801 int adjustments[] = checkBounds(x, y, w, h, 1802 pixelformat, scanlineStride); 1803 if (adjustments != null) { 1804 x = adjustments[0]; 1805 y = adjustments[1]; 1806 w = adjustments[2]; 1807 h = adjustments[3]; 1808 offset += adjustments[4]; 1809 } 1810 1811 byte pixels[] = new byte[w * h * 4]; 1812 1813 BytePixelGetter getter = PixelUtils.getByteGetter(pixelformat); 1814 ByteToBytePixelConverter converter = 1815 PixelUtils.getB2BConverter(getter, getSetter()); 1816 converter.convert(buffer, offset, scanlineStride, 1817 pixels, 0, w * 4, 1818 w, h); 1819 writePixelBuffer(x, y, w, h, pixels); 1820 } 1821 1822 @Override 1823 public void setPixels(int x, int y, int w, int h, 1824 PixelFormat<IntBuffer> pixelformat, 1825 int[] buffer, int offset, int scanlineStride) 1826 { 1827 if (w <= 0 || h <= 0) return; 1828 int adjustments[] = checkBounds(x, y, w, h, 1829 pixelformat, scanlineStride); 1830 if (adjustments != null) { 1831 x = adjustments[0]; 1832 y = adjustments[1]; 1833 w = adjustments[2]; 1834 h = adjustments[3]; 1835 offset += adjustments[4]; 1836 } 1837 1838 byte pixels[] = new byte[w * h * 4]; 1839 1840 IntPixelGetter getter = PixelUtils.getIntGetter(pixelformat); 1841 IntToBytePixelConverter converter = 1842 PixelUtils.getI2BConverter(getter, getSetter()); 1843 converter.convert(buffer, offset, scanlineStride, 1844 pixels, 0, w * 4, 1845 w, h); 1846 writePixelBuffer(x, y, w, h, pixels); 1847 } 1848 1849 @Override 1850 public void setPixels(int dstx, int dsty, int w, int h, 1851 PixelReader reader, int srcx, int srcy) 1852 { 1853 if (w <= 0 || h <= 0) return; 1854 int adjustments[] = checkBounds(dstx, dsty, w, h, null, 0); 1855 if (adjustments != null) { 1856 int newx = adjustments[0]; 1857 int newy = adjustments[1]; 1858 srcx += newx - dstx; 1859 srcy += newy - dsty; 1860 dstx = newx; 1861 dsty = newy; 1862 w = adjustments[2]; 1863 h = adjustments[3]; 1864 } 1865 1866 byte pixels[] = new byte[w * h * 4]; 1867 reader.getPixels(srcx, srcy, w, h, 1868 PixelFormat.getByteBgraPreInstance(), 1869 pixels, 0, w * 4); 1870 writePixelBuffer(dstx, dsty, w, h, pixels); 1871 } 1872 }; 1873 } 1874 return writer; 1875 } 1876 1877 /** 1878 * Sets the effect to be applied after the next draw call, or null to 1879 * disable effects. 1880 * @param e the effect to use, or null to disable effects 1881 */ 1882 public void setEffect(Effect e) { 1883 GrowableDataBuffer buf = getBuffer(); 1884 buf.putByte(PGCanvas.EFFECT); 1885 if (e == null) { 1886 curState.effect = null; 1887 buf.putObject(null); 1888 } else { 1889 curState.effect = e.impl_copy(); 1890 curState.effect.impl_sync(); 1891 buf.putObject(curState.effect.impl_getImpl()); 1892 } 1893 } 1894 1895 /** 1896 * Gets a copy of the effect to be applied after the next draw call. 1897 * A null return value means that no effect will be applied after future 1898 * rendering calls. 1899 * @param e an {@code Effect} object that may be used to store the 1900 * copy of the current effect, if it is of a compatible type 1901 * @return the current effect used for all rendering calls, 1902 * or null if there is no current effect 1903 */ 1904 public Effect getEffect(Effect e) { 1905 return curState.effect == null ? null : curState.effect.impl_copy(); 1906 } 1907 1908 /** 1909 * Applies the given effect to the entire canvas. 1910 * @param e the effect to apply onto the entire destination. 1911 */ 1912 public void applyEffect(Effect e) { 1913 GrowableDataBuffer buf = getBuffer(); 1914 buf.putByte(PGCanvas.FX_APPLY_EFFECT); 1915 Effect effect = e.impl_copy(); 1916 effect.impl_sync(); 1917 buf.putObject(effect.impl_getImpl()); 1918 } 1919}