1 /* 2 * Copyright (c) 2011, 2017, 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 com.sun.javafx.webkit.prism; 27 28 import com.sun.glass.ui.Screen; 29 import com.sun.javafx.font.FontStrike; 30 import com.sun.javafx.font.Metrics; 31 import com.sun.javafx.font.PGFont; 32 import com.sun.javafx.geom.*; 33 import com.sun.javafx.geom.transform.Affine2D; 34 import com.sun.javafx.geom.transform.Affine3D; 35 import com.sun.javafx.geom.transform.BaseTransform; 36 import com.sun.javafx.scene.text.GlyphList; 37 import com.sun.javafx.scene.text.TextLayout; 38 import com.sun.javafx.sg.prism.*; 39 import com.sun.javafx.text.TextRun; 40 import com.sun.prism.*; 41 import com.sun.prism.paint.Color; 42 import com.sun.prism.paint.Gradient; 43 import com.sun.prism.paint.ImagePattern; 44 import com.sun.prism.paint.Paint; 45 import com.sun.scenario.effect.*; 46 import com.sun.scenario.effect.impl.prism.PrDrawable; 47 import com.sun.scenario.effect.impl.prism.PrEffectHelper; 48 import com.sun.scenario.effect.impl.prism.PrFilterContext; 49 import com.sun.webkit.graphics.*; 50 51 import java.nio.ByteBuffer; 52 import java.nio.ByteOrder; 53 import java.security.AccessController; 54 import java.security.PrivilegedAction; 55 import java.util.ArrayList; 56 import java.util.List; 57 import java.util.logging.Level; 58 import java.util.logging.Logger; 59 60 import static com.sun.scenario.effect.Blend.Mode.*; 61 import com.sun.scenario.effect.impl.Renderer; 62 import com.sun.scenario.effect.impl.prism.PrRenderer; 63 64 class WCGraphicsPrismContext extends WCGraphicsContext { 65 66 public enum Type { 67 /** 68 * Base context associated with the topmost page buffer. 69 * Created and disposed during a single render pass. 70 */ 71 PRIMARY, 72 73 /** 74 * A context associated with a dedicated buffer representing 75 * a separate render target like canvas, buffered image etc. 76 * Its life cycle is not limited to a single render pass. 77 */ 78 DEDICATED 79 } 80 81 private final static Logger log = 82 Logger.getLogger(WCGraphicsPrismContext.class.getName()); 83 private final static boolean DEBUG_DRAW_CLIP_SHAPE = Boolean.valueOf( 84 AccessController.doPrivileged((PrivilegedAction<String>) () -> 85 System.getProperty("com.sun.webkit.debugDrawClipShape", "false"))); 86 87 Graphics baseGraphics; 88 private BaseTransform baseTransform; 89 90 private final List<ContextState> states = new ArrayList<ContextState>(); 91 92 private ContextState state = new ContextState(); 93 94 // Cache for getPlatformGraphics 95 private Graphics cachedGraphics = null; 96 97 private int fontSmoothingType; 98 private boolean isRootLayerValid = false; 99 100 WCGraphicsPrismContext(Graphics g) { 101 state.setClip(g.getClipRect()); 102 state.setAlpha(g.getExtraAlpha()); 103 baseGraphics = g; 104 initBaseTransform(g.getTransformNoClone()); 105 } 106 107 WCGraphicsPrismContext() { 108 } 109 110 public Type type() { 111 return Type.PRIMARY; 112 } 113 114 final void initBaseTransform(BaseTransform t) { 115 baseTransform = new Affine3D(t); 116 state.setTransform((Affine3D)baseTransform); 117 } 118 119 private void resetCachedGraphics() { 120 cachedGraphics = null; 121 } 122 123 @Override 124 public Object getPlatformGraphics() { 125 return getGraphics(false); 126 } 127 128 Graphics getGraphics(boolean checkClip) { 129 if (cachedGraphics == null) { 130 Layer l = state.getLayerNoClone(); 131 cachedGraphics = (l != null) 132 ? l.getGraphics() 133 : baseGraphics; 134 135 state.apply(cachedGraphics); 136 137 if (log.isLoggable(Level.FINE)) { 138 log.fine("getPlatformGraphics for " + this + " : " + 139 cachedGraphics); 140 } 141 } 142 143 Rectangle clip = cachedGraphics.getClipRectNoClone(); 144 return (checkClip && clip!=null && clip.isEmpty()) 145 ? null 146 : cachedGraphics; 147 } 148 149 public void saveState() 150 { 151 state.markAsRestorePoint(); 152 saveStateInternal(); 153 } 154 155 private void saveStateInternal() 156 { 157 states.add(state); 158 state = state.clone(); 159 } 160 161 private void startNewLayer(Layer layer) { 162 saveStateInternal(); 163 164 // layer has the same bounds as clip, so we have to translate 165 Rectangle clip = state.getClipNoClone(); 166 167 //left-side (post-) translate. 168 //NB! an order of transforms is essential! 169 Affine3D newTr = new Affine3D(BaseTransform.getTranslateInstance( 170 -clip.x, 171 -clip.y)); 172 newTr.concatenate(state.getTransformNoClone()); 173 174 //move clip to (0, 0) - start of texture 175 clip.x = 0; 176 clip.y = 0; 177 //no-clone - no-set! 178 179 Graphics g = getGraphics(true); 180 if (g != null && g != baseGraphics) { 181 layer.init(g); 182 } 183 184 state.setTransform(newTr); 185 state.setLayer(layer); 186 187 resetCachedGraphics(); 188 } 189 190 private void renderLayer(final Layer layer) { 191 WCTransform cur = getTransform(); 192 193 //translate to (layer.getX(), layer.getY()) 194 setTransform(new WCTransform( 195 1.0, 0.0, 196 0.0, 1.0, 197 layer.getX(), layer.getY())); 198 199 // composite drawing delegated to the layer rendering 200 Graphics g = getGraphics(true); 201 if (g != null) { 202 layer.render(g); 203 } 204 205 //restore transform 206 setTransform(cur); 207 } 208 209 private void restoreStateInternal() { 210 int size = states.size(); 211 if (size == 0) { 212 assert false: "Unbalanced restoreState"; 213 return; 214 } 215 216 Layer layer = state.getLayerNoClone(); 217 state = states.remove(size - 1); 218 if (layer != state.getLayerNoClone()) { 219 renderLayer(layer); 220 layer.dispose(); 221 if (log.isLoggable(Level.FINE)) { 222 log.fine("Popped layer " + layer); 223 } 224 } else { 225 resetCachedGraphics(); 226 } 227 } 228 229 public void restoreState() 230 { 231 log.fine("restoring state"); 232 do { 233 restoreStateInternal(); 234 } while ( !state.isRestorePoint() ); 235 } 236 237 /** 238 * Renders all layers to the underlaying Graphics, but preserves the 239 * current state and the states stack 240 */ 241 private void flushAllLayers() { 242 if (state == null) { 243 // context disposed 244 return; 245 } 246 247 if (isRootLayerValid) { 248 log.fine("FlushAllLayers: root layer is valid, skipping"); 249 return; 250 } 251 252 if (log.isLoggable(Level.FINE)) { 253 log.fine("FlushAllLayers"); 254 } 255 256 ContextState currentState = state; 257 258 for (int i = states.size() - 1; i >=0; i--) { 259 Layer layer = state.getLayerNoClone(); 260 state = states.get(i); 261 if (layer != state.getLayerNoClone()) { 262 renderLayer(layer); 263 } else { 264 resetCachedGraphics(); 265 } 266 } 267 268 Layer layer = state.getLayerNoClone(); 269 if (layer != null) { 270 renderLayer(layer); 271 } 272 273 state = currentState; 274 isRootLayerValid = true; 275 } 276 277 278 public void dispose() { 279 if (!states.isEmpty()) { 280 log.fine("Unbalanced saveState/restoreState"); 281 } 282 for (ContextState state: states) { 283 if (state.getLayerNoClone() != null) { 284 state.getLayerNoClone().dispose(); 285 } 286 } 287 states.clear(); 288 289 if (state != null && state.getLayerNoClone() != null) { 290 state.getLayerNoClone().dispose(); 291 } 292 state = null; 293 } 294 295 296 public void setClip(WCPath path, boolean isOut) { 297 Affine3D tr = new Affine3D(state.getTransformNoClone()); 298 path.transform( 299 tr.getMxx(), tr.getMyx(), 300 tr.getMxy(), tr.getMyy(), 301 tr.getMxt(), tr.getMyt()); 302 //path now is in node coordinates, as well as clip 303 304 if (!isOut) { 305 WCRectangle pathBounds = path.getBounds(); 306 307 // path bounds could be fractional so 'inclusive' rounding 308 // is used for determining clip rectangle 309 int pixelX = (int) Math.floor(pathBounds.getX()); 310 int pixelY = (int) Math.floor(pathBounds.getY()); 311 int pixelW = (int) Math.ceil(pathBounds.getMaxX()) - pixelX; 312 int pixelH = (int) Math.ceil(pathBounds.getMaxY()) - pixelY; 313 314 state.clip(new Rectangle(pixelX, pixelY, pixelW, pixelH)); 315 } 316 317 Rectangle clip = state.getClipNoClone(); 318 319 if (isOut) { 320 path.addRect(clip.x, clip.y, clip.width, clip.height); 321 //Out clip path is always EVENODD. 322 } 323 324 path.translate(-clip.x, -clip.y); 325 326 Layer layer = new ClipLayer( 327 getGraphics(false), clip, path, type() == Type.DEDICATED); 328 329 startNewLayer(layer); 330 331 if (log.isLoggable(Level.FINE)) { 332 log.fine("setClip(WCPath " + path.getID() + ")"); 333 log.fine("Pushed layer " + layer); 334 } 335 } 336 337 private Rectangle transformClip(Rectangle localClip) { 338 if (localClip==null) { 339 return null; 340 } 341 342 float[] points = new float[] { 343 localClip.x, localClip.y, 344 localClip.x + localClip.width, localClip.y, 345 localClip.x, localClip.y + localClip.height, 346 localClip.x + localClip.width, localClip.y + localClip.height}; 347 state.getTransformNoClone().transform(points, 0, points, 0, 4); 348 float minX = Math.min( 349 points[0], Math.min( 350 points[2], Math.min( 351 points[4], points[6]))); 352 float maxX = Math.max( 353 points[0], Math.max( 354 points[2], Math.max( 355 points[4], points[6]))); 356 float minY = Math.min( 357 points[1], Math.min( 358 points[3], Math.min( 359 points[5], points[7]))); 360 float maxY = Math.max( 361 points[1], Math.max( 362 points[3], Math.max( 363 points[5], points[7]))); 364 return new Rectangle(new RectBounds(minX, minY, maxX, maxY)); 365 366 /* #1 loose rotate 367 state.getTransformNoClone().transform(localClip, localClip); 368 */ 369 /* #2 problem with negative coordinates 370 RectBounds rb = TransformedShape.transformedShape( 371 new RoundRectangle2D(localClip.x, localClip.y, localClip.width, localClip.height, 0, 0), 372 state.getTransformNoClone()).getBounds(); 373 return rb.isEmpty() 374 ? null 375 : new Rectangle(rb); 376 */ 377 } 378 379 private void setClip(Rectangle shape) { 380 Affine3D tr = state.getTransformNoClone(); 381 if (tr.getMxy() == 0 && tr.getMxz() == 0 382 && tr.getMyx() == 0 && tr.getMyz() == 0 383 && tr.getMzx() == 0 && tr.getMzy() == 0) { 384 //There is no rotation here: scale + translation. 385 //Fast & easy! 386 state.clip(transformClip(shape)); 387 if (log.isLoggable(Level.FINE)) { 388 log.log(Level.FINE, "setClip({0})", shape); 389 } 390 if (DEBUG_DRAW_CLIP_SHAPE) { 391 //Draw clip shape 392 Rectangle rc = state.getClipNoClone(); 393 if (rc != null && rc.width >= 2 && rc.height >= 2) { 394 WCTransform cur = getTransform(); 395 //translate to (layer.getX(), layer.getY()) 396 setTransform(new WCTransform( 397 1.0, 0.0, 398 0.0, 1.0, 399 0.0, 0.0)); 400 401 Graphics g2d = getGraphics(true); 402 if (g2d != null) { 403 float fbase = (float)Math.random(); 404 g2d.setPaint(new Color( 405 fbase, 406 1f - fbase, 407 0.5f, 408 0.1f)); 409 g2d.setStroke(new BasicStroke()); 410 g2d.fillRect(rc.x, rc.y, rc.width, rc.height); 411 412 g2d.setPaint(new Color( 413 1f - fbase, 414 fbase, 415 0.5f, 416 1f)); 417 g2d.drawRect(rc.x, rc.y, rc.width, rc.height); 418 } 419 //restore transform 420 setTransform(cur); 421 state.clip(new Rectangle(rc.x+1, rc.y+1, rc.width-2, rc.height-2)); 422 } 423 } 424 if (cachedGraphics != null) { 425 cachedGraphics.setClipRect(state.getClipNoClone()); 426 } 427 } else { 428 //twisted axis set 429 WCPath path = new WCPathImpl(); 430 path.addRect(shape.x, shape.y, shape.width, shape.height); 431 setClip(path, false); 432 } 433 } 434 435 public void setClip(int cx, int cy, int cw, int ch) { 436 setClip(new Rectangle(cx, cy, cw, ch)); 437 } 438 439 public void setClip(WCRectangle c) { 440 setClip(new Rectangle((int)c.getX(), (int)c.getY(), 441 (int)c.getWidth(), (int)c.getHeight())); 442 } 443 444 public WCRectangle getClip() { 445 Rectangle r = state.getClipNoClone(); 446 return r == null ? null : new WCRectangle(r.x, r.y, r.width, r.height); 447 } 448 449 protected Rectangle getClipRectNoClone() { 450 return state.getClipNoClone(); 451 } 452 453 protected Affine3D getTransformNoClone() { 454 return state.getTransformNoClone(); 455 } 456 457 public void translate(float x, float y) { 458 if (log.isLoggable(Level.FINE)) { 459 log.log(Level.FINE, "translate({0},{1})", new Object[] {x, y}); 460 } 461 state.translate(x, y); 462 if (cachedGraphics != null) { 463 cachedGraphics.translate(x, y); 464 } 465 } 466 467 public void scale(float sx, float sy) { 468 if (log.isLoggable(Level.FINE)) { 469 log.fine("scale(" + sx + " " + sy + ")"); 470 } 471 state.scale(sx, sy); 472 if (cachedGraphics != null) { 473 cachedGraphics.scale(sx, sy); 474 } 475 } 476 477 public void rotate(float radians) { 478 if (log.isLoggable(Level.FINE)) { 479 log.fine("rotate(" + radians + ")"); 480 } 481 state.rotate(radians); 482 if (cachedGraphics != null) { 483 cachedGraphics.setTransform(state.getTransformNoClone()); 484 } 485 } 486 487 // overriden in WCBufferedContext 488 protected boolean shouldRenderRect(float x, float y, float w, float h, 489 DropShadow shadow, BasicStroke stroke) 490 { 491 return true; 492 } 493 494 // overriden in WCBufferedContext 495 protected boolean shouldRenderShape(Shape shape, DropShadow shadow, BasicStroke stroke) { 496 return true; 497 } 498 499 // overriden in WCBufferedContext 500 protected boolean shouldCalculateIntersection() { 501 return false; 502 } 503 504 @Override 505 public void fillRect(final float x, final float y, final float w, final float h, final Integer rgba) { 506 if (log.isLoggable(Level.FINE)) { 507 String format = (rgba != null) 508 ? "fillRect(%f, %f, %f, %f, 0x%x)" 509 : "fillRect(%f, %f, %f, %f, null)"; 510 log.fine(String.format(format, x, y, w, h, rgba)); 511 } 512 if (!shouldRenderRect(x, y, w, h, state.getShadowNoClone(), null)) { 513 return; 514 } 515 new Composite() { 516 @Override void doPaint(Graphics g) { 517 Paint paint = (rgba != null) ? createColor(rgba) : state.getPaintNoClone(); 518 DropShadow shadow = state.getShadowNoClone(); 519 if (shadow != null) { 520 final NGRectangle node = new NGRectangle(); 521 node.updateRectangle(x, y, w, h, 0, 0); 522 render(g, shadow, paint, null, node); 523 } else { 524 g.setPaint(paint); 525 g.fillRect(x, y, w, h); 526 } 527 } 528 }.paint(); 529 } 530 531 @Override 532 public void fillRoundedRect(final float x, final float y, final float w, final float h, 533 final float topLeftW, final float topLeftH, final float topRightW, final float topRightH, 534 final float bottomLeftW, final float bottomLeftH, final float bottomRightW, final float bottomRightH, 535 final int rgba) 536 { 537 if (log.isLoggable(Level.FINE)) { 538 log.fine(String.format("fillRoundedRect(%f, %f, %f, %f, " 539 + "%f, %f, %f, %f, %f, %f, %f, %f, 0x%x)", 540 x, y, w, h, topLeftW, topLeftH, topRightW, topRightH, 541 bottomLeftW, bottomLeftH, bottomRightW, bottomRightH, rgba)); 542 } 543 if (!shouldRenderRect(x, y, w, h, state.getShadowNoClone(), null)) { 544 return; 545 } 546 new Composite() { 547 @Override void doPaint(Graphics g) { 548 // Prism only supports single arcWidth/Height. 549 // We work around by calculating average width and height here 550 551 float arcW = (topLeftW + topRightW + bottomLeftW + bottomRightW) / 2; 552 float arcH = (topLeftH + topRightH + bottomLeftH + bottomRightH) / 2; 553 554 Paint paint = createColor(rgba); 555 DropShadow shadow = state.getShadowNoClone(); 556 if (shadow != null) { 557 final NGRectangle node = new NGRectangle(); 558 node.updateRectangle(x, y, w, h, arcW, arcH); 559 render(g, shadow, paint, null, node); 560 } else { 561 g.setPaint(paint); 562 g.fillRoundRect(x, y, w, h, arcW, arcH); 563 } 564 } 565 }.paint(); 566 } 567 568 @Override 569 public void clearRect(final float x, final float y, final float w, final float h) { 570 if (log.isLoggable(Level.FINE)) { 571 log.fine(String.format("clearRect(%f, %f, %f, %f)", x, y, w, h)); 572 } 573 if (shouldCalculateIntersection()) { 574 // No intersection is applicable for clearRect. 575 return; 576 } 577 new Composite() { 578 @Override void doPaint(Graphics g) { 579 g.clearQuad(x, y, x + w, y + h); 580 } 581 }.paint(); 582 } 583 584 @Override 585 public void setFillColor(int rgba) { 586 if (log.isLoggable(Level.FINE)) { 587 log.log(Level.FINE, String.format("setFillColor(0x%x)", rgba)); 588 } 589 state.setPaint(createColor(rgba)); 590 } 591 592 @Override 593 public void setFillGradient(WCGradient gradient) { 594 if (log.isLoggable(Level.FINE)) { 595 log.fine("setFillGradient(" + gradient + ")"); 596 } 597 state.setPaint((Gradient) gradient.getPlatformGradient()); 598 } 599 600 @Override 601 public void setTextMode(boolean fill, boolean stroke, boolean clip) { 602 if (log.isLoggable(Level.FINE)) { 603 log.fine("setTextMode(fill:" + fill + ",stroke:" + stroke + ",clip:" + clip + ")"); 604 } 605 state.setTextMode(fill, stroke, clip); 606 } 607 608 @Override 609 public void setFontSmoothingType(int fontSmoothingType) { 610 this.fontSmoothingType = fontSmoothingType; 611 } 612 613 @Override 614 public int getFontSmoothingType() { 615 return fontSmoothingType; 616 } 617 618 @Override 619 public void setStrokeStyle(int style) { 620 if (log.isLoggable(Level.FINE)) { 621 log.log(Level.FINE, "setStrokeStyle({0})", style); 622 } 623 state.getStrokeNoClone().setStyle(style); 624 } 625 626 @Override 627 public void setStrokeColor(int rgba) { 628 if (log.isLoggable(Level.FINE)) { 629 log.log(Level.FINE, String.format("setStrokeColor(0x%x)", rgba)); 630 } 631 state.getStrokeNoClone().setPaint(createColor(rgba)); 632 } 633 634 @Override 635 public void setStrokeWidth(float width) { 636 if (log.isLoggable(Level.FINE)) { 637 log.log(Level.FINE, "setStrokeWidth({0})", new Object[] { width }); 638 } 639 state.getStrokeNoClone().setThickness(width); 640 } 641 642 @Override 643 public void setStrokeGradient(WCGradient gradient) { 644 if (log.isLoggable(Level.FINE)) { 645 log.fine("setStrokeGradient(" + gradient + ")"); 646 } 647 state.getStrokeNoClone().setPaint((Gradient) gradient.getPlatformGradient()); 648 } 649 650 @Override 651 public void setLineDash(float offset, float... sizes) { 652 if (log.isLoggable(Level.FINE)) { 653 StringBuilder s = new StringBuilder("["); 654 for (int i=0; i < sizes.length; i++) { 655 s.append(sizes[i]).append(','); 656 } 657 s.append(']'); 658 log.log(Level.FINE, "setLineDash({0},{1}", new Object[] {offset, s}); 659 } 660 state.getStrokeNoClone().setDashOffset(offset); 661 if (sizes != null) { 662 boolean allZero = true; 663 for (int i = 0; i < sizes.length; i++) { 664 if (sizes[i] != 0) { 665 allZero = false; 666 break; 667 } 668 } 669 if (allZero) { 670 sizes = null; 671 } 672 } 673 state.getStrokeNoClone().setDashSizes(sizes); 674 } 675 676 @Override 677 public void setLineCap(int lineCap) { 678 if (log.isLoggable(Level.FINE)) { 679 log.fine("setLineCap(" + lineCap + ")"); 680 } 681 state.getStrokeNoClone().setLineCap(lineCap); 682 } 683 684 @Override 685 public void setLineJoin(int lineJoin) { 686 if (log.isLoggable(Level.FINE)) { 687 log.fine("setLineJoin(" + lineJoin + ")"); 688 } 689 state.getStrokeNoClone().setLineJoin(lineJoin); 690 } 691 692 @Override 693 public void setMiterLimit(float miterLimit) { 694 if (log.isLoggable(Level.FINE)) { 695 log.fine("setMiterLimit(" + miterLimit + ")"); 696 } 697 state.getStrokeNoClone().setMiterLimit(miterLimit); 698 } 699 700 @Override 701 public void setShadow(float dx, float dy, float blur, int rgba) { 702 if (log.isLoggable(Level.FINE)) { 703 String format = "setShadow(%f, %f, %f, 0x%x)"; 704 log.fine(String.format(format, dx, dy, blur, rgba)); 705 } 706 state.setShadow(createShadow(dx, dy, blur, rgba)); 707 } 708 709 @Override 710 public void drawPolygon(final WCPath path, final boolean shouldAntialias) { 711 if (log.isLoggable(Level.FINE)) { 712 log.log(Level.FINE, "drawPolygon({0})", 713 new Object[] {shouldAntialias}); 714 } 715 if (!shouldRenderShape(((WCPathImpl)path).getPlatformPath(), null, 716 state.getStrokeNoClone().getPlatformStroke())) 717 { 718 return; 719 } 720 new Composite() { 721 @Override void doPaint(Graphics g) { 722 Path2D p2d = (Path2D) path.getPlatformPath(); 723 g.setPaint(state.getPaintNoClone()); 724 g.fill(p2d); 725 if (state.getStrokeNoClone().apply(g)) { 726 g.draw(p2d); 727 } 728 } 729 }.paint(); 730 } 731 732 @Override 733 public void drawLine(final int x0, final int y0, final int x1, final int y1) { 734 if (log.isLoggable(Level.FINE)) { 735 log.log(Level.FINE, "drawLine({0}, {1}, {2}, {3})", 736 new Object[] {x0, y0, x1, y1}); 737 } 738 Line2D line = new Line2D(x0, y0, x1, y1); 739 if (!shouldRenderShape(line, null, state.getStrokeNoClone().getPlatformStroke())) { 740 return; 741 } 742 new Composite() { 743 @Override void doPaint(Graphics g) { 744 if (state.getStrokeNoClone().apply(g)) { 745 g.drawLine(x0, y0, x1, y1); 746 } 747 } 748 }.paint(); 749 } 750 751 @Override 752 public void drawPattern( 753 final WCImage texture, 754 final WCRectangle srcRect, 755 final WCTransform patternTransform, 756 final WCPoint phase, 757 final WCRectangle destRect) 758 { 759 if (log.isLoggable(Level.FINE)) { 760 log.log(Level.FINE, "drawPattern({0}, {1}, {2}, {3})", 761 new Object[] {destRect.getIntX(), destRect.getIntY(), 762 destRect.getIntWidth(), 763 destRect.getIntHeight()}); 764 } 765 if (!shouldRenderRect(destRect.getX(), destRect.getY(), 766 destRect.getWidth(), destRect.getHeight(), null, null)) 767 { 768 return; 769 } 770 if (texture != null) { 771 new Composite() { 772 @Override void doPaint(Graphics g) { 773 // The handling of pattern transform is modeled after the WebKit 774 // ImageCG.cpp's Image::drawPattern() 775 float adjustedX = phase.getX() 776 + srcRect.getX() * (float) patternTransform.getMatrix()[0]; 777 float adjustedY = phase.getY() 778 + srcRect.getY() * (float) patternTransform.getMatrix()[3]; 779 float scaledTileWidth = 780 srcRect.getWidth() * (float) patternTransform.getMatrix()[0]; 781 float scaledTileHeight = 782 srcRect.getHeight() * (float) patternTransform.getMatrix()[3]; 783 784 Image img = ((PrismImage)texture).getImage(); 785 786 // Create subImage only if srcRect doesn't fit the texture bounds. See RT-20193. 787 if (!srcRect.contains(new WCRectangle(0, 0, texture.getWidth(), texture.getHeight()))) { 788 789 img = img.createSubImage(srcRect.getIntX(), 790 srcRect.getIntY(), 791 (int)Math.ceil(srcRect.getWidth()), 792 (int)Math.ceil(srcRect.getHeight())); 793 } 794 g.setPaint(new ImagePattern( 795 img, 796 adjustedX, adjustedY, 797 scaledTileWidth, scaledTileHeight, 798 false, false)); 799 800 g.fillRect(destRect.getX(), destRect.getY(), 801 destRect.getWidth(), destRect.getHeight()); 802 } 803 }.paint(); 804 } 805 } 806 807 @Override 808 public void drawImage(final WCImage img, 809 final float dstx, final float dsty, final float dstw, final float dsth, 810 final float srcx, final float srcy, final float srcw, final float srch) 811 { 812 if (log.isLoggable(Level.FINE)){ 813 log.log(Level.FINE, "drawImage(img, dst({0},{1},{2},{3}), " + 814 "src({4},{5},{6},{7}))", 815 new Object[] {dstx, dsty, dstw, dsth, 816 srcx, srcy, srcw, srch}); 817 } 818 if (!shouldRenderRect(dstx, dsty, dstw, dsth, state.getShadowNoClone(), null)) { 819 return; 820 } 821 if (img instanceof PrismImage) { 822 new Composite() { 823 @Override void doPaint(Graphics g) { 824 PrismImage pi = (PrismImage) img; 825 DropShadow shadow = state.getShadowNoClone(); 826 if (shadow != null) { 827 NGImageView node = new NGImageView(); 828 node.setImage(pi.getImage()); 829 node.setX(dstx); 830 node.setY(dsty); 831 node.setViewport(srcx, srcy, srcw, srch, dstw, dsth); 832 node.setContentBounds(new RectBounds(dstx, dsty, dstx + dstw, dsty + dsth)); 833 render(g, shadow, null, null, node); 834 } else { 835 pi.draw(g, 836 (int) dstx, (int) dsty, 837 (int) (dstx + dstw), (int) (dsty + dsth), 838 (int) srcx, (int) srcy, 839 (int) (srcx + srcw), (int) (srcy + srch)); 840 } 841 } 842 }.paint(); 843 } 844 } 845 846 @Override 847 public void drawBitmapImage(final ByteBuffer image, final int x, final int y, final int w, final int h) { 848 if (!shouldRenderRect(x, y, w, h, null, null)) { 849 return; 850 } 851 new Composite() { 852 @Override void doPaint(Graphics g) { 853 image.order(ByteOrder.nativeOrder()); 854 Image img = Image.fromByteBgraPreData(image, w, h); 855 ResourceFactory rf = g.getResourceFactory(); 856 Texture txt = rf.createTexture(img, Texture.Usage.STATIC, Texture.WrapMode.REPEAT); 857 g.drawTexture(txt, x, y, x + w, y + h, 0, 0, w, h); 858 txt.dispose(); 859 } 860 }.paint(); 861 } 862 863 @Override 864 public void drawIcon(WCIcon icon, int x, int y) { 865 if (log.isLoggable(Level.FINE)) { 866 log.log(Level.FINE, "UNIMPLEMENTED drawIcon ({0}, {1})", 867 new Object[] {x, y}); 868 } 869 } 870 871 @Override 872 public void drawRect(final int x, final int y, final int w, final int h) { 873 if (log.isLoggable(Level.FINE)) { 874 log.log(Level.FINE, "drawRect({0}, {1}, {2}, {3})", 875 new Object[]{x, y, w, h}); 876 } 877 if (!shouldRenderRect(x, y, w, h, 878 null, state.getStrokeNoClone().getPlatformStroke())) 879 { 880 return; 881 } 882 new Composite() { 883 @Override void doPaint(Graphics g) { 884 Paint c = state.getPaintNoClone(); 885 if (c != null && c.isOpaque()) { 886 g.setPaint(c); 887 g.fillRect(x, y, w, h); 888 } 889 890 if (state.getStrokeNoClone().apply(g)) { 891 g.drawRect(x, y, w, h); 892 } 893 } 894 }.paint(); 895 } 896 897 @Override 898 public void drawString(final WCFont f, final int[] glyphs, 899 final float[] advances, final float x, final float y) 900 { 901 if (log.isLoggable(Level.FINE)) { 902 log.fine(String.format( 903 "Drawing %d glyphs @(%.1f, %.1f)", 904 glyphs.length, x, y)); 905 } 906 PGFont font = (PGFont)f.getPlatformFont(); 907 TextRun gl = TextUtilities.createGlyphList(glyphs, advances, x, y); 908 909 DropShadow shadow = state.getShadowNoClone(); 910 BasicStroke stroke = state.isTextStroke() 911 ? state.getStrokeNoClone().getPlatformStroke() 912 : null; 913 914 final FontStrike strike = font.getStrike(getTransformNoClone(), getFontSmoothingType()); 915 if (shouldCalculateIntersection()) { 916 Metrics m = strike.getMetrics(); 917 gl.setMetrics(m.getAscent(), m.getDescent(), m.getLineGap()); 918 if (!shouldRenderRect(x, y, gl.getWidth(), gl.getHeight(), shadow, stroke)) { 919 return; 920 } 921 } 922 new Composite() { 923 @Override void doPaint(Graphics g) { 924 Paint paint = state.isTextFill() 925 ? state.getPaintNoClone() 926 : null; 927 if (shadow != null) { 928 final NGText span = new NGText(); 929 span.setGlyphs(new GlyphList[] {gl}); 930 span.setFont(font); 931 span.setFontSmoothingType(fontSmoothingType); 932 render(g, shadow, paint, stroke, span); 933 } else { 934 if (paint != null) { 935 g.setPaint(paint); 936 g.drawString(gl, strike, x, y, null, 0, 0); 937 } 938 if (stroke != null) { 939 paint = state.getStrokeNoClone().getPaint(); 940 if (paint != null) { 941 g.setPaint(paint); 942 g.setStroke(stroke); 943 g.draw(strike.getOutline(gl, BaseTransform.getTranslateInstance(x, y))); 944 } 945 } 946 } 947 } 948 }.paint(); 949 } 950 951 @Override public void drawString(WCFont f, String str, boolean rtl, 952 int from, int to, float x, float y) 953 { 954 if (log.isLoggable(Level.FINE)) { 955 log.fine(String.format( 956 "str='%s' (length=%d), from=%d, to=%d, rtl=%b, @(%.1f, %.1f)", 957 str, str.length(), from, to, rtl, x, y)); 958 } 959 TextLayout layout = TextUtilities.createLayout( 960 str.substring(from, to), f.getPlatformFont()); 961 int count = 0; 962 GlyphList[] runs = layout.getRuns(); 963 for (GlyphList run: runs) { 964 count += run.getGlyphCount(); 965 } 966 967 int[] glyphs = new int[count]; 968 float[] adv = new float[count]; 969 count = 0; 970 for (GlyphList run: layout.getRuns()) { 971 int gc = run.getGlyphCount(); 972 for (int i = 0; i < gc; i++) { 973 glyphs[count] = run.getGlyphCode(i); 974 adv[count] = run.getPosX(i + 1) - run.getPosX(i); 975 count++; 976 } 977 } 978 979 // adjust x coordinate (see RT-29908) 980 if (rtl) { 981 x += (TextUtilities.getLayoutWidth(str.substring(from), f.getPlatformFont()) - 982 layout.getBounds().getWidth()); 983 } else { 984 x += TextUtilities.getLayoutWidth(str.substring(0, from), f.getPlatformFont()); 985 } 986 drawString(f, glyphs, adv, x, y); 987 } 988 989 @Override 990 public void setComposite(int composite) { 991 log.log(Level.FINE, "setComposite({0})", composite); 992 state.setCompositeOperation(composite); 993 } 994 995 @Override 996 public void drawEllipse(final int x, final int y, final int w, final int h) { 997 if (log.isLoggable(Level.FINE)) { 998 log.log(Level.FINE, "drawEllipse({0}, {1}, {2}, {3})", 999 new Object[] { x, y, w, h}); 1000 } 1001 if (!shouldRenderRect(x, y, w, h, 1002 null, state.getStrokeNoClone().getPlatformStroke())) 1003 { 1004 return; 1005 } 1006 new Composite() { 1007 @Override void doPaint(Graphics g) { 1008 g.setPaint(state.getPaintNoClone()); 1009 g.fillEllipse(x, y, w, h); 1010 if (state.getStrokeNoClone().apply(g)) { 1011 g.drawEllipse(x, y, w, h); 1012 } 1013 } 1014 }.paint(); 1015 } 1016 1017 private final static BasicStroke focusRingStroke = 1018 new BasicStroke(1.1f, BasicStroke.CAP_BUTT, 1019 BasicStroke.JOIN_ROUND, 0.0f, 1020 new float[] {1.0f}, 0.0f); 1021 1022 @Override 1023 public void drawFocusRing(final int x, final int y, final int w, final int h, final int rgba) { 1024 if (log.isLoggable(Level.FINE)) { 1025 log.log(Level.FINE, 1026 String.format("drawFocusRing: %d, %d, %d, %d, 0x%x", 1027 x, y, w, h, rgba)); 1028 } 1029 if (!shouldRenderRect(x, y, w, h, null, focusRingStroke)) { 1030 return; 1031 } 1032 new Composite() { 1033 @Override void doPaint(Graphics g) { 1034 g.setPaint(createColor(rgba)); 1035 BasicStroke stroke = g.getStroke(); 1036 g.setStroke(focusRingStroke); 1037 g.drawRoundRect(x, y, w, h, 4, 4); 1038 g.setStroke(stroke); 1039 } 1040 }.paint(); 1041 } 1042 1043 public void setAlpha(float alpha) { 1044 log.log(Level.FINE, "setAlpha({0})", alpha); 1045 1046 state.setAlpha(alpha); 1047 1048 if (null != cachedGraphics) { 1049 cachedGraphics.setExtraAlpha(state.getAlpha()); 1050 } 1051 } 1052 1053 public float getAlpha() { 1054 return state.getAlpha(); 1055 } 1056 1057 @Override public void beginTransparencyLayer(float opacity) { 1058 TransparencyLayer layer = new TransparencyLayer( 1059 getGraphics(false), state.getClipNoClone(), opacity); 1060 1061 if (log.isLoggable(Level.FINE)) { 1062 log.fine(String.format("beginTransparencyLayer(%s)", layer)); 1063 } 1064 1065 //[saveStateIntertal] will work as [saveState] 1066 state.markAsRestorePoint(); 1067 1068 startNewLayer(layer); 1069 } 1070 1071 @Override public void endTransparencyLayer() { 1072 if (log.isLoggable(Level.FINE)) { 1073 log.fine(String.format("endTransparencyLayer(%s)", state.getLayerNoClone())); 1074 } 1075 1076 //pair to [startNewLayer] that works as [saveState] call 1077 restoreState(); 1078 } 1079 1080 @Override 1081 public void drawWidget(final RenderTheme theme, final Ref widget, final int x, final int y) { 1082 WCSize s = theme.getWidgetSize(widget); 1083 if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) { 1084 return; 1085 } 1086 new Composite() { 1087 @Override void doPaint(Graphics g) { 1088 theme.drawWidget(WCGraphicsPrismContext.this, widget, x, y); 1089 } 1090 }.paint(); 1091 } 1092 1093 @Override 1094 public void drawScrollbar(final ScrollBarTheme theme, final Ref widget, int x, int y, 1095 int pressedPart, int hoveredPart) 1096 { 1097 if (log.isLoggable(Level.FINE)) { 1098 log.fine(String.format("drawScrollbar(%s, %s, x = %d, y = %d)", theme, widget, x, y)); 1099 } 1100 1101 WCSize s = theme.getWidgetSize(widget); 1102 if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) { 1103 return; 1104 } 1105 new Composite() { 1106 @Override void doPaint(Graphics g) { 1107 theme.paint(WCGraphicsPrismContext.this, widget, x, y, pressedPart, hoveredPart); 1108 } 1109 }.paint(); 1110 } 1111 1112 private static Rectangle intersect(Rectangle what, Rectangle with) { 1113 if (what == null) { 1114 return with; 1115 } 1116 RectBounds b = what.toRectBounds(); 1117 b.intersectWith(with); 1118 what.setBounds(b); 1119 return what; 1120 } 1121 1122 static Color createColor(int rgba) { 1123 float a = (0xFF & (rgba >> 24)) / 255.0f; 1124 float r = (0xFF & (rgba >> 16)) / 255.0f; 1125 float g = (0xFF & (rgba >> 8)) / 255.0f; 1126 float b = (0xFF & (rgba)) / 255.0f; 1127 return new Color(r, g, b, a); 1128 } 1129 1130 private static Color4f createColor4f(int rgba) { 1131 float a = (0xFF & (rgba >> 24)) / 255.0f; 1132 float r = (0xFF & (rgba >> 16)) / 255.0f; 1133 float g = (0xFF & (rgba >> 8)) / 255.0f; 1134 float b = (0xFF & (rgba)) / 255.0f; 1135 return new Color4f(r, g, b, a); 1136 } 1137 1138 private DropShadow createShadow(float dx, float dy, float blur, int rgba) { 1139 if (dx == 0f && dy == 0f && blur == 0f) { 1140 return null; 1141 } 1142 DropShadow shadow = new DropShadow(); 1143 shadow.setOffsetX((int) dx); 1144 shadow.setOffsetY((int) dy); 1145 shadow.setRadius((blur < 0f) ? 0f : (blur > 127f) ? 127f : blur); 1146 shadow.setColor(createColor4f(rgba)); 1147 return shadow; 1148 } 1149 1150 private void render(Graphics g, Effect effect, Paint paint, BasicStroke stroke, NGNode node) { 1151 if (node instanceof NGShape) { 1152 NGShape shape = (NGShape) node; 1153 Shape realShape = shape.getShape(); 1154 Paint strokePaint = state.getStrokeNoClone().getPaint(); 1155 if ((stroke != null) && (strokePaint != null)) { 1156 realShape = stroke.createStrokedShape(realShape); 1157 shape.setDrawStroke(stroke); 1158 shape.setDrawPaint(strokePaint); 1159 shape.setMode((paint == null) ? NGShape.Mode.STROKE : NGShape.Mode.STROKE_FILL); 1160 } else { 1161 shape.setMode((paint == null) ? NGShape.Mode.EMPTY : NGShape.Mode.FILL); 1162 } 1163 shape.setFillPaint(paint); 1164 shape.setContentBounds(realShape.getBounds()); 1165 } 1166 boolean culling = g.hasPreCullingBits(); 1167 g.setHasPreCullingBits(false); 1168 node.setEffect(effect); 1169 node.render(g); 1170 g.setHasPreCullingBits(culling); 1171 } 1172 1173 private static final class ContextState { 1174 private final WCStrokeImpl stroke = new WCStrokeImpl(); 1175 private Rectangle clip; 1176 private Paint paint; 1177 private float alpha; 1178 1179 private boolean textFill = true; 1180 private boolean textStroke = false; 1181 private boolean textClip = false; 1182 private boolean restorePoint = false; 1183 1184 private DropShadow shadow; 1185 private Affine3D xform; 1186 private Layer layer; 1187 private int compositeOperation; 1188 1189 private ContextState() { 1190 clip = null; 1191 paint = Color.BLACK; 1192 stroke.setPaint(Color.BLACK); 1193 alpha = 1.0f; 1194 xform = new Affine3D(); 1195 compositeOperation = COMPOSITE_SOURCE_OVER; 1196 } 1197 1198 private ContextState(ContextState state) { 1199 stroke.copyFrom(state.getStrokeNoClone()); 1200 setPaint(state.getPaintNoClone()); 1201 clip = state.getClipNoClone(); 1202 if (clip != null) { 1203 clip = new Rectangle(clip); 1204 } 1205 xform = new Affine3D(state.getTransformNoClone()); 1206 setShadow(state.getShadowNoClone()); 1207 setLayer(state.getLayerNoClone()); 1208 setAlpha(state.getAlpha()); 1209 setTextMode(state.isTextFill(), state.isTextStroke(), state.isTextClip()); 1210 setCompositeOperation(state.getCompositeOperation()); 1211 } 1212 1213 @Override 1214 protected ContextState clone() { 1215 return new ContextState(this); 1216 } 1217 1218 private void apply(Graphics g) { 1219 //TODO: Verify if we need to apply more properties from state 1220 g.setTransform(getTransformNoClone()); 1221 g.setClipRect(getClipNoClone()); 1222 g.setExtraAlpha(getAlpha()); 1223 } 1224 1225 private int getCompositeOperation() { 1226 return compositeOperation; 1227 } 1228 1229 private void setCompositeOperation(int compositeOperation) { 1230 this.compositeOperation = compositeOperation; 1231 } 1232 1233 private WCStrokeImpl getStrokeNoClone() { 1234 return stroke; 1235 } 1236 1237 private Paint getPaintNoClone() { 1238 return paint; 1239 } 1240 1241 private void setPaint(Paint paint) { 1242 this.paint = paint; 1243 } 1244 1245 private Rectangle getClipNoClone() { 1246 return clip; 1247 } 1248 1249 private Layer getLayerNoClone() { 1250 return layer; 1251 } 1252 1253 private void setLayer(Layer layer) { 1254 this.layer = layer; 1255 } 1256 1257 private void setClip(Rectangle area) { 1258 clip = area; 1259 } 1260 1261 private void clip(Rectangle area) { 1262 if (null == clip) { 1263 clip = area; 1264 } else { 1265 clip.intersectWith(area); 1266 } 1267 } 1268 1269 private void setAlpha(float alpha) { 1270 this.alpha = alpha; 1271 } 1272 1273 private float getAlpha() { 1274 return alpha; 1275 } 1276 1277 private void setTextMode(boolean fill, boolean stroke, boolean clip) { 1278 textFill = fill; 1279 textStroke = stroke; 1280 textClip = clip; 1281 } 1282 1283 private boolean isTextFill() { 1284 return textFill; 1285 } 1286 1287 private boolean isTextStroke() { 1288 return textStroke; 1289 } 1290 1291 private boolean isTextClip() { 1292 return textClip; 1293 } 1294 1295 private void markAsRestorePoint() { 1296 restorePoint = true; 1297 } 1298 1299 private boolean isRestorePoint() { 1300 return restorePoint; 1301 } 1302 1303 private void setShadow(DropShadow shadow) { 1304 this.shadow = shadow; 1305 } 1306 1307 private DropShadow getShadowNoClone() { 1308 return shadow; 1309 } 1310 1311 private Affine3D getTransformNoClone() { 1312 return xform; 1313 } 1314 1315 private void setTransform(final Affine3D at) { 1316 this.xform.setTransform(at); 1317 } 1318 1319 private void concatTransform(Affine3D at) { 1320 xform.concatenate(at); 1321 } 1322 1323 private void translate(double dx, double dy) { 1324 xform.translate(dx, dy); 1325 } 1326 1327 private void scale(double sx, double sy) { 1328 xform.scale(sx,sy); 1329 } 1330 1331 private void rotate(double radians) { 1332 xform.rotate(radians); 1333 } 1334 } 1335 1336 private abstract static class Layer { 1337 FilterContext fctx; 1338 PrDrawable buffer; 1339 Graphics graphics; 1340 final Rectangle bounds; 1341 boolean permanent; 1342 1343 Layer(Graphics g, Rectangle bounds, boolean permanent) { 1344 this.bounds = new Rectangle(bounds); 1345 this.permanent = permanent; 1346 1347 // avoid creating zero-size drawable, see also RT-21410 1348 int w = Math.max(bounds.width, 1); 1349 int h = Math.max(bounds.height, 1); 1350 fctx = getFilterContext(g); 1351 if (permanent) { 1352 ResourceFactory f = GraphicsPipeline.getDefaultResourceFactory(); 1353 RTTexture rtt = f.createRTTexture(w, h, Texture.WrapMode.CLAMP_NOT_NEEDED); 1354 rtt.makePermanent(); 1355 buffer = ((PrRenderer)Renderer.getRenderer(fctx)).createDrawable(rtt); 1356 } else { 1357 buffer = (PrDrawable) Effect.getCompatibleImage(fctx, w, h); 1358 } 1359 } 1360 1361 Graphics getGraphics() { 1362 if (graphics == null) { 1363 graphics = buffer.createGraphics(); 1364 } 1365 return graphics; 1366 } 1367 1368 abstract void init(Graphics g); 1369 1370 abstract void render(Graphics g); 1371 1372 private void dispose() { 1373 if (buffer != null) { 1374 if (permanent) { 1375 buffer.flush(); // releases the resource 1376 } else { 1377 Effect.releaseCompatibleImage(fctx, buffer); 1378 } 1379 fctx = null; 1380 buffer = null; 1381 } 1382 } 1383 1384 private double getX() { return (double) bounds.x; } 1385 private double getY() { return (double) bounds.y; } 1386 } 1387 1388 private final class TransparencyLayer extends Layer { 1389 private final float opacity; 1390 1391 private TransparencyLayer(Graphics g, Rectangle bounds, float opacity) { 1392 super(g, bounds, false); 1393 this.opacity = opacity; 1394 } 1395 1396 @Override void init(Graphics g) { 1397 state.setCompositeOperation(COMPOSITE_SOURCE_OVER); 1398 } 1399 1400 @Override void render(Graphics g) { 1401 new Composite() { 1402 @Override void doPaint(Graphics g) { 1403 float op = g.getExtraAlpha(); 1404 g.setExtraAlpha(opacity); 1405 Affine3D tx = new Affine3D(g.getTransformNoClone()); 1406 g.setTransform(BaseTransform.IDENTITY_TRANSFORM); 1407 g.drawTexture(buffer.getTextureObject(), 1408 bounds.x, bounds.y, bounds.width, bounds.height); 1409 g.setTransform(tx); 1410 g.setExtraAlpha(op); 1411 } 1412 }.paint(g); 1413 } 1414 1415 @Override public String toString() { 1416 return String.format("TransparencyLayer[%d,%d + %dx%d, opacity %.2f]", 1417 bounds.x, bounds.y, bounds.width, bounds.height, opacity); 1418 } 1419 } 1420 1421 private static final class ClipLayer extends Layer { 1422 private final WCPath normalizedToClipPath; 1423 private boolean srcover; 1424 1425 private ClipLayer(Graphics g, Rectangle bounds, WCPath normalizedToClipPath, 1426 boolean permanent) 1427 { 1428 super(g, bounds, permanent); 1429 this.normalizedToClipPath = normalizedToClipPath; 1430 srcover = true; 1431 } 1432 1433 @Override void init(Graphics g) { 1434 RTTexture texture = null; 1435 ReadbackGraphics readbackGraphics = null; 1436 try { 1437 readbackGraphics = (ReadbackGraphics) g; 1438 texture = readbackGraphics.readBack(bounds); 1439 getGraphics().drawTexture(texture, 0, 0, bounds.width, bounds.height); 1440 } finally { 1441 if (readbackGraphics != null && texture != null) { 1442 readbackGraphics.releaseReadBackBuffer(texture); 1443 } 1444 } 1445 srcover = false; 1446 } 1447 1448 @Override void render(Graphics g) { 1449 Path2D p2d = ((WCPathImpl)normalizedToClipPath).getPlatformPath(); 1450 1451 // render normalizedToClipPath to a drawable 1452 PrDrawable bufferImg = (PrDrawable) Effect.getCompatibleImage( 1453 fctx, bounds.width, bounds.height); 1454 Graphics bufferGraphics = bufferImg.createGraphics(); 1455 1456 bufferGraphics.setPaint(Color.BLACK); 1457 bufferGraphics.fill(p2d); 1458 1459 // blend buffer and clipImg onto |g| 1460 if (g instanceof MaskTextureGraphics && ! (g instanceof PrinterGraphics)) { 1461 MaskTextureGraphics mg = (MaskTextureGraphics) g; 1462 if (srcover) { 1463 mg.drawPixelsMasked(buffer.getTextureObject(), 1464 bufferImg.getTextureObject(), 1465 bounds.x, bounds.y, bounds.width, bounds.height, 1466 0, 0, 0, 0); 1467 } else { 1468 mg.maskInterpolatePixels(buffer.getTextureObject(), 1469 bufferImg.getTextureObject(), 1470 bounds.x, bounds.y, bounds.width, bounds.height, 1471 0, 0, 0, 0); 1472 } 1473 } else { 1474 Blend blend = new Blend(Blend.Mode.SRC_IN, 1475 new PassThrough(bufferImg, bounds.width, bounds.height), 1476 new PassThrough(buffer, bounds.width, bounds.height)); 1477 Affine3D tx = new Affine3D(g.getTransformNoClone()); 1478 g.setTransform(BaseTransform.IDENTITY_TRANSFORM); 1479 PrEffectHelper.render(blend, g, bounds.x, bounds.y, null); 1480 g.setTransform(tx); 1481 } 1482 1483 Effect.releaseCompatibleImage(fctx, bufferImg); 1484 } 1485 1486 @Override public String toString() { 1487 return String.format("ClipLayer[%d,%d + %dx%d, path %s]", 1488 bounds.x, bounds.y, bounds.width, bounds.height, 1489 normalizedToClipPath); 1490 } 1491 } 1492 1493 private abstract class Composite { 1494 abstract void doPaint(Graphics g); 1495 1496 void paint() { 1497 paint(getGraphics(true)); 1498 } 1499 1500 void paint(Graphics g) { 1501 if (g != null) { 1502 CompositeMode oldCompositeMode = g.getCompositeMode(); 1503 switch (state.getCompositeOperation()) { 1504 // decode operations that don't require Blend first 1505 case COMPOSITE_COPY: 1506 g.setCompositeMode(CompositeMode.SRC); 1507 doPaint(g); 1508 g.setCompositeMode(oldCompositeMode); 1509 break; 1510 case COMPOSITE_SOURCE_OVER: 1511 g.setCompositeMode(CompositeMode.SRC_OVER); 1512 doPaint(g); 1513 g.setCompositeMode(oldCompositeMode); 1514 break; 1515 default: 1516 // other operations require usage of Blend 1517 blend(g); 1518 break; 1519 } 1520 isRootLayerValid = false; 1521 } 1522 } 1523 1524 private void blend(Graphics g) { 1525 FilterContext fctx = getFilterContext(g); 1526 PrDrawable dstImg = null; 1527 PrDrawable srcImg = null; 1528 ReadbackGraphics readBackGraphics = null; 1529 RTTexture texture = null; 1530 Rectangle clip = state.getClipNoClone(); 1531 WCImage image = getImage(); 1532 try { 1533 if (image != null && image instanceof PrismImage) { 1534 // blending on canvas 1535 dstImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height); 1536 Graphics dstG = dstImg.createGraphics(); 1537 ((PrismImage) image).draw(dstG, 1538 0, 0, clip.width, clip.height, 1539 clip.x, clip.y, clip.width, clip.height); 1540 } else { 1541 // blending on page 1542 readBackGraphics = (ReadbackGraphics) g; 1543 texture = readBackGraphics.readBack(clip); 1544 dstImg = PrDrawable.create(fctx, texture); 1545 } 1546 1547 srcImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height); 1548 Graphics srcG = srcImg.createGraphics(); 1549 state.apply(srcG); 1550 doPaint(srcG); 1551 1552 g.clear(); 1553 PrEffectHelper.render(createEffect(dstImg, srcImg, clip.width, clip.height), g, 0, 0, null); 1554 1555 } finally { 1556 if (srcImg != null) { 1557 Effect.releaseCompatibleImage(fctx, srcImg); 1558 } 1559 if (dstImg != null) { 1560 if (readBackGraphics != null && texture != null) { 1561 readBackGraphics.releaseReadBackBuffer(texture); 1562 } else { 1563 Effect.releaseCompatibleImage(fctx, dstImg); 1564 } 1565 } 1566 } 1567 } 1568 1569 // provides some syntax sugar for createEffect() 1570 private Effect createBlend(Blend.Mode mode, 1571 PrDrawable dstImg, 1572 PrDrawable srcImg, 1573 int width, 1574 int height) 1575 { 1576 return new Blend( 1577 mode, 1578 new PassThrough(dstImg, width, height), 1579 new PassThrough(srcImg, width, height)); 1580 } 1581 1582 private Effect createEffect(PrDrawable dstImg, 1583 PrDrawable srcImg, 1584 int width, 1585 int height) 1586 { 1587 switch (state.getCompositeOperation()) { 1588 case COMPOSITE_CLEAR: // same as xor 1589 case COMPOSITE_XOR: 1590 return new Blend( 1591 SRC_OVER, 1592 createBlend(SRC_OUT, dstImg, srcImg, width, height), 1593 createBlend(SRC_OUT, srcImg, dstImg, width, height) 1594 ); 1595 case COMPOSITE_SOURCE_IN: 1596 return createBlend(SRC_IN, dstImg, srcImg, width, height); 1597 case COMPOSITE_SOURCE_OUT: 1598 return createBlend(SRC_OUT, dstImg, srcImg, width, height); 1599 case COMPOSITE_SOURCE_ATOP: 1600 return createBlend(SRC_ATOP, dstImg, srcImg, width, height); 1601 case COMPOSITE_DESTINATION_OVER: 1602 return createBlend(SRC_OVER, srcImg, dstImg, width, height); 1603 case COMPOSITE_DESTINATION_IN: 1604 return createBlend(SRC_IN, srcImg, dstImg, width, height); 1605 case COMPOSITE_DESTINATION_OUT: 1606 return createBlend(SRC_OUT, srcImg, dstImg, width, height); 1607 case COMPOSITE_DESTINATION_ATOP: 1608 return createBlend(SRC_ATOP, srcImg, dstImg, width, height); 1609 case COMPOSITE_HIGHLIGHT: 1610 return createBlend(ADD, dstImg, srcImg, width, height); 1611 default: 1612 return createBlend(SRC_OVER, dstImg, srcImg, width, height); 1613 } 1614 } 1615 } 1616 1617 private static final class PassThrough extends Effect { 1618 private final PrDrawable img; 1619 private final int width; 1620 private final int height; 1621 1622 private PassThrough(PrDrawable img, int width, int height) { 1623 this.img = img; 1624 this.width = width; 1625 this.height = height; 1626 } 1627 1628 @Override public ImageData filter( 1629 FilterContext fctx, 1630 BaseTransform transform, 1631 Rectangle outputClip, 1632 Object renderHelper, 1633 Effect defaultInput) { 1634 // We have an unpaired lock() here, because unlocking is done 1635 // internally by ImageData. See RT-33625 for details. 1636 img.lock(); 1637 ImageData imgData = new ImageData(fctx, img, new Rectangle( 1638 (int) transform.getMxt(), 1639 (int) transform.getMyt(), 1640 width, height)); 1641 imgData.setReusable(true); 1642 return imgData; 1643 } 1644 1645 @Override public RectBounds getBounds( 1646 BaseTransform transform, 1647 Effect defaultInput) { 1648 return null; 1649 } 1650 1651 @Override public AccelType getAccelType(FilterContext fctx) { 1652 return AccelType.INTRINSIC; 1653 } 1654 1655 @Override 1656 public boolean reducesOpaquePixels() { 1657 return false; 1658 } 1659 1660 @Override 1661 public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) { 1662 return null; 1663 } 1664 } 1665 1666 private static FilterContext getFilterContext(Graphics g) { 1667 Screen screen = g.getAssociatedScreen(); 1668 if (screen == null) { 1669 ResourceFactory factory = g.getResourceFactory(); 1670 return PrFilterContext.getPrinterContext(factory); 1671 } else { 1672 return PrFilterContext.getInstance(screen); 1673 } 1674 } 1675 1676 @Override 1677 public void strokeArc(final int x, final int y, final int w, final int h, 1678 final int startAngle, final int angleSpan) 1679 { 1680 if (log.isLoggable(Level.FINE)) { 1681 log.fine(String.format("strokeArc(%d, %d, %d, %d, %d, %d)", 1682 x, y, w, h, startAngle, angleSpan)); 1683 } 1684 Arc2D arc = new Arc2D(x, y, w, h, startAngle, angleSpan, Arc2D.OPEN); 1685 if (state.getStrokeNoClone().isApplicable() && 1686 !shouldRenderShape(arc, null, state.getStrokeNoClone().getPlatformStroke())) 1687 { 1688 return; 1689 } 1690 new Composite() { 1691 @Override void doPaint(Graphics g) { 1692 if (state.getStrokeNoClone().apply(g)) { 1693 g.draw(arc); 1694 } 1695 } 1696 }.paint(); 1697 } 1698 1699 @Override 1700 public WCImage getImage() { 1701 return null; 1702 } 1703 1704 @Override 1705 public void strokeRect(final float x, final float y, final float w, final float h, 1706 final float lineWidth) { 1707 if (log.isLoggable(Level.FINE)) { 1708 log.fine(String.format("strokeRect_FFFFF(%f, %f, %f, %f, %f)", 1709 x, y, w, h, lineWidth)); 1710 } 1711 BasicStroke stroke = new BasicStroke( 1712 lineWidth, 1713 BasicStroke.CAP_BUTT, 1714 BasicStroke.JOIN_MITER, 1715 Math.max(1.0f, lineWidth), 1716 state.getStrokeNoClone().getDashSizes(), 1717 state.getStrokeNoClone().getDashOffset()); 1718 1719 if (!shouldRenderRect(x, y, w, h, null, stroke)) { 1720 return; 1721 } 1722 new Composite() { 1723 @Override void doPaint(Graphics g) { 1724 g.setStroke(stroke); 1725 Paint paint = state.getStrokeNoClone().getPaint(); 1726 if (paint == null) { 1727 paint = state.getPaintNoClone(); 1728 } 1729 g.setPaint(paint); 1730 g.drawRect(x, y, w, h); 1731 } 1732 }.paint(); 1733 } 1734 1735 @Override 1736 public void strokePath(final WCPath path) { 1737 log.fine("strokePath"); 1738 if (path != null) { 1739 final BasicStroke stroke = state.getStrokeNoClone().getPlatformStroke(); 1740 final DropShadow shadow = state.getShadowNoClone(); 1741 final Path2D p2d = (Path2D)path.getPlatformPath(); 1742 1743 if ((stroke == null && shadow == null) || 1744 !shouldRenderShape(p2d, shadow, stroke)) 1745 { 1746 return; 1747 } 1748 new Composite() { 1749 @Override void doPaint(Graphics g) { 1750 if (shadow != null) { 1751 final NGPath node = new NGPath(); 1752 node.updateWithPath2d(p2d); 1753 render(g, shadow, null, stroke, node); 1754 } else if (stroke != null) { 1755 Paint paint = state.getStrokeNoClone().getPaint(); 1756 if (paint == null) { 1757 paint = state.getPaintNoClone(); 1758 } 1759 g.setPaint(paint); 1760 g.setStroke(stroke); 1761 g.draw(p2d); 1762 } 1763 } 1764 }.paint(); 1765 } 1766 } 1767 1768 @Override 1769 public void fillPath(final WCPath path) { 1770 log.fine("fillPath"); 1771 if (path != null) { 1772 if (!shouldRenderShape(((WCPathImpl)path).getPlatformPath(), 1773 state.getShadowNoClone(), null)) 1774 { 1775 return; 1776 } 1777 new Composite() { 1778 @Override void doPaint(Graphics g) { 1779 Path2D p2d = (Path2D) path.getPlatformPath(); 1780 Paint paint = state.getPaintNoClone(); 1781 DropShadow shadow = state.getShadowNoClone(); 1782 if (shadow != null) { 1783 final NGPath node = new NGPath(); 1784 node.updateWithPath2d(p2d); 1785 render(g, shadow, paint, null, node); 1786 } else { 1787 g.setPaint(paint); 1788 g.fill(p2d); 1789 } 1790 } 1791 }.paint(); 1792 } 1793 } 1794 1795 public void setTransform(WCTransform tm) { 1796 double m[] = tm.getMatrix(); 1797 Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5])); 1798 if (state.getLayerNoClone() == null) { 1799 at.preConcatenate(baseTransform); 1800 } 1801 state.setTransform(at); 1802 resetCachedGraphics(); 1803 } 1804 1805 public WCTransform getTransform() { 1806 Affine3D xf = state.getTransformNoClone(); 1807 return new WCTransform(xf.getMxx(), xf.getMyx(), 1808 xf.getMxy(), xf.getMyy(), 1809 xf.getMxt(), xf.getMyt()); 1810 } 1811 1812 public void concatTransform(WCTransform tm) { 1813 double m[] = tm.getMatrix(); 1814 Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5])); 1815 state.concatTransform(at); 1816 resetCachedGraphics(); 1817 } 1818 1819 @Override 1820 public void flush() { 1821 flushAllLayers(); 1822 } 1823 1824 @Override 1825 public WCGradient createLinearGradient(WCPoint p1, WCPoint p2) { 1826 return new WCLinearGradient(p1, p2); 1827 } 1828 1829 @Override 1830 public WCGradient createRadialGradient(WCPoint p1, float r1, WCPoint p2, float r2) { 1831 return new WCRadialGradient(p1, r1, p2, r2); 1832 } 1833 }