1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package 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.logging.PlatformLogger; 37 import com.sun.javafx.logging.PlatformLogger.Level; 38 import com.sun.javafx.scene.text.GlyphList; 39 import com.sun.javafx.scene.text.TextLayout; 40 import com.sun.javafx.sg.prism.*; 41 import com.sun.javafx.text.TextRun; 42 import com.sun.prism.*; 43 import com.sun.prism.paint.Color; 44 import com.sun.prism.paint.Gradient; 45 import com.sun.prism.paint.ImagePattern; 46 import com.sun.prism.paint.Paint; 47 import com.sun.scenario.effect.*; 48 import com.sun.scenario.effect.impl.prism.PrDrawable; 49 import com.sun.scenario.effect.impl.prism.PrEffectHelper; 50 import com.sun.scenario.effect.impl.prism.PrFilterContext; 51 import com.sun.webkit.graphics.*; 52 53 import java.nio.ByteBuffer; 54 import java.nio.ByteOrder; 55 import java.security.AccessController; 56 import java.security.PrivilegedAction; 57 import java.util.ArrayList; 58 import java.util.List; 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 PlatformLogger log = 82 PlatformLogger.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.fine(String.format("drawFocusRing: %d, %d, %d, %d, 0x%x", x, y, w, h, rgba)); 1026 } 1027 if (!shouldRenderRect(x, y, w, h, null, focusRingStroke)) { 1028 return; 1029 } 1030 new Composite() { 1031 @Override void doPaint(Graphics g) { 1032 g.setPaint(createColor(rgba)); 1033 BasicStroke stroke = g.getStroke(); 1034 g.setStroke(focusRingStroke); 1035 g.drawRoundRect(x, y, w, h, 4, 4); 1036 g.setStroke(stroke); 1037 } 1038 }.paint(); 1039 } 1040 1041 public void setAlpha(float alpha) { 1042 log.fine("setAlpha({0})", alpha); 1043 1044 state.setAlpha(alpha); 1045 1046 if (null != cachedGraphics) { 1047 cachedGraphics.setExtraAlpha(state.getAlpha()); 1048 } 1049 } 1050 1051 public float getAlpha() { 1052 return state.getAlpha(); 1053 } 1054 1055 @Override public void beginTransparencyLayer(float opacity) { 1056 TransparencyLayer layer = new TransparencyLayer( 1057 getGraphics(false), state.getClipNoClone(), opacity); 1058 1059 if (log.isLoggable(Level.FINE)) { 1060 log.fine(String.format("beginTransparencyLayer(%s)", layer)); 1061 } 1062 1063 //[saveStateIntertal] will work as [saveState] 1064 state.markAsRestorePoint(); 1065 1066 startNewLayer(layer); 1067 } 1068 1069 @Override public void endTransparencyLayer() { 1070 if (log.isLoggable(Level.FINE)) { 1071 log.fine(String.format("endTransparencyLayer(%s)", state.getLayerNoClone())); 1072 } 1073 1074 //pair to [startNewLayer] that works as [saveState] call 1075 restoreState(); 1076 } 1077 1078 @Override 1079 public void drawWidget(final RenderTheme theme, final Ref widget, final int x, final int y) { 1080 WCSize s = theme.getWidgetSize(widget); 1081 if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) { 1082 return; 1083 } 1084 new Composite() { 1085 @Override void doPaint(Graphics g) { 1086 theme.drawWidget(WCGraphicsPrismContext.this, widget, x, y); 1087 } 1088 }.paint(); 1089 } 1090 1091 @Override 1092 public void drawScrollbar(final ScrollBarTheme theme, final Ref widget, int x, int y, 1093 int pressedPart, int hoveredPart) 1094 { 1095 if (log.isLoggable(Level.FINE)) { 1096 log.fine(String.format("drawScrollbar(%s, %s, x = %d, y = %d)", theme, widget, x, y)); 1097 } 1098 1099 WCSize s = theme.getWidgetSize(widget); 1100 if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) { 1101 return; 1102 } 1103 new Composite() { 1104 @Override void doPaint(Graphics g) { 1105 theme.paint(WCGraphicsPrismContext.this, widget, x, y, pressedPart, hoveredPart); 1106 } 1107 }.paint(); 1108 } 1109 1110 private static Rectangle intersect(Rectangle what, Rectangle with) { 1111 if (what == null) { 1112 return with; 1113 } 1114 RectBounds b = what.toRectBounds(); 1115 b.intersectWith(with); 1116 what.setBounds(b); 1117 return what; 1118 } 1119 1120 static Color createColor(int rgba) { 1121 float a = (0xFF & (rgba >> 24)) / 255.0f; 1122 float r = (0xFF & (rgba >> 16)) / 255.0f; 1123 float g = (0xFF & (rgba >> 8)) / 255.0f; 1124 float b = (0xFF & (rgba)) / 255.0f; 1125 return new Color(r, g, b, a); 1126 } 1127 1128 private static Color4f createColor4f(int rgba) { 1129 float a = (0xFF & (rgba >> 24)) / 255.0f; 1130 float r = (0xFF & (rgba >> 16)) / 255.0f; 1131 float g = (0xFF & (rgba >> 8)) / 255.0f; 1132 float b = (0xFF & (rgba)) / 255.0f; 1133 return new Color4f(r, g, b, a); 1134 } 1135 1136 private DropShadow createShadow(float dx, float dy, float blur, int rgba) { 1137 if (dx == 0f && dy == 0f && blur == 0f) { 1138 return null; 1139 } 1140 DropShadow shadow = new DropShadow(); 1141 shadow.setOffsetX((int) dx); 1142 shadow.setOffsetY((int) dy); 1143 shadow.setRadius((blur < 0f) ? 0f : (blur > 127f) ? 127f : blur); 1144 shadow.setColor(createColor4f(rgba)); 1145 return shadow; 1146 } 1147 1148 private void render(Graphics g, Effect effect, Paint paint, BasicStroke stroke, NGNode node) { 1149 if (node instanceof NGShape) { 1150 NGShape shape = (NGShape) node; 1151 Shape realShape = shape.getShape(); 1152 Paint strokePaint = state.getStrokeNoClone().getPaint(); 1153 if ((stroke != null) && (strokePaint != null)) { 1154 realShape = stroke.createStrokedShape(realShape); 1155 shape.setDrawStroke(stroke); 1156 shape.setDrawPaint(strokePaint); 1157 shape.setMode((paint == null) ? NGShape.Mode.STROKE : NGShape.Mode.STROKE_FILL); 1158 } else { 1159 shape.setMode((paint == null) ? NGShape.Mode.EMPTY : NGShape.Mode.FILL); 1160 } 1161 shape.setFillPaint(paint); 1162 shape.setContentBounds(realShape.getBounds()); 1163 } 1164 boolean culling = g.hasPreCullingBits(); 1165 g.setHasPreCullingBits(false); 1166 node.setEffect(effect); 1167 node.render(g); 1168 g.setHasPreCullingBits(culling); 1169 } 1170 1171 private static final class ContextState { 1172 private final WCStrokeImpl stroke = new WCStrokeImpl(); 1173 private Rectangle clip; 1174 private Paint paint; 1175 private float alpha; 1176 1177 private boolean textFill = true; 1178 private boolean textStroke = false; 1179 private boolean textClip = false; 1180 private boolean restorePoint = false; 1181 1182 private DropShadow shadow; 1183 private Affine3D xform; 1184 private Layer layer; 1185 private int compositeOperation; 1186 1187 private ContextState() { 1188 clip = null; 1189 paint = Color.BLACK; 1190 stroke.setPaint(Color.BLACK); 1191 alpha = 1.0f; 1192 xform = new Affine3D(); 1193 compositeOperation = COMPOSITE_SOURCE_OVER; 1194 } 1195 1196 private ContextState(ContextState state) { 1197 stroke.copyFrom(state.getStrokeNoClone()); 1198 setPaint(state.getPaintNoClone()); 1199 clip = state.getClipNoClone(); 1200 if (clip != null) { 1201 clip = new Rectangle(clip); 1202 } 1203 xform = new Affine3D(state.getTransformNoClone()); 1204 setShadow(state.getShadowNoClone()); 1205 setLayer(state.getLayerNoClone()); 1206 setAlpha(state.getAlpha()); 1207 setTextMode(state.isTextFill(), state.isTextStroke(), state.isTextClip()); 1208 setCompositeOperation(state.getCompositeOperation()); 1209 } 1210 1211 @Override 1212 protected ContextState clone() { 1213 return new ContextState(this); 1214 } 1215 1216 private void apply(Graphics g) { 1217 //TODO: Verify if we need to apply more properties from state 1218 g.setTransform(getTransformNoClone()); 1219 g.setClipRect(getClipNoClone()); 1220 g.setExtraAlpha(getAlpha()); 1221 } 1222 1223 private int getCompositeOperation() { 1224 return compositeOperation; 1225 } 1226 1227 private void setCompositeOperation(int compositeOperation) { 1228 this.compositeOperation = compositeOperation; 1229 } 1230 1231 private WCStrokeImpl getStrokeNoClone() { 1232 return stroke; 1233 } 1234 1235 private Paint getPaintNoClone() { 1236 return paint; 1237 } 1238 1239 private void setPaint(Paint paint) { 1240 this.paint = paint; 1241 } 1242 1243 private Rectangle getClipNoClone() { 1244 return clip; 1245 } 1246 1247 private Layer getLayerNoClone() { 1248 return layer; 1249 } 1250 1251 private void setLayer(Layer layer) { 1252 this.layer = layer; 1253 } 1254 1255 private void setClip(Rectangle area) { 1256 clip = area; 1257 } 1258 1259 private void clip(Rectangle area) { 1260 if (null == clip) { 1261 clip = area; 1262 } else { 1263 clip.intersectWith(area); 1264 } 1265 } 1266 1267 private void setAlpha(float alpha) { 1268 this.alpha = alpha; 1269 } 1270 1271 private float getAlpha() { 1272 return alpha; 1273 } 1274 1275 private void setTextMode(boolean fill, boolean stroke, boolean clip) { 1276 textFill = fill; 1277 textStroke = stroke; 1278 textClip = clip; 1279 } 1280 1281 private boolean isTextFill() { 1282 return textFill; 1283 } 1284 1285 private boolean isTextStroke() { 1286 return textStroke; 1287 } 1288 1289 private boolean isTextClip() { 1290 return textClip; 1291 } 1292 1293 private void markAsRestorePoint() { 1294 restorePoint = true; 1295 } 1296 1297 private boolean isRestorePoint() { 1298 return restorePoint; 1299 } 1300 1301 private void setShadow(DropShadow shadow) { 1302 this.shadow = shadow; 1303 } 1304 1305 private DropShadow getShadowNoClone() { 1306 return shadow; 1307 } 1308 1309 private Affine3D getTransformNoClone() { 1310 return xform; 1311 } 1312 1313 private void setTransform(final Affine3D at) { 1314 this.xform.setTransform(at); 1315 } 1316 1317 private void concatTransform(Affine3D at) { 1318 xform.concatenate(at); 1319 } 1320 1321 private void translate(double dx, double dy) { 1322 xform.translate(dx, dy); 1323 } 1324 1325 private void scale(double sx, double sy) { 1326 xform.scale(sx,sy); 1327 } 1328 1329 private void rotate(double radians) { 1330 xform.rotate(radians); 1331 } 1332 } 1333 1334 private abstract static class Layer { 1335 FilterContext fctx; 1336 PrDrawable buffer; 1337 Graphics graphics; 1338 final Rectangle bounds; 1339 boolean permanent; 1340 1341 Layer(Graphics g, Rectangle bounds, boolean permanent) { 1342 this.bounds = new Rectangle(bounds); 1343 this.permanent = permanent; 1344 1345 // avoid creating zero-size drawable, see also RT-21410 1346 int w = Math.max(bounds.width, 1); 1347 int h = Math.max(bounds.height, 1); 1348 fctx = getFilterContext(g); 1349 if (permanent) { 1350 ResourceFactory f = GraphicsPipeline.getDefaultResourceFactory(); 1351 RTTexture rtt = f.createRTTexture(w, h, Texture.WrapMode.CLAMP_NOT_NEEDED); 1352 rtt.makePermanent(); 1353 buffer = ((PrRenderer)Renderer.getRenderer(fctx)).createDrawable(rtt); 1354 } else { 1355 buffer = (PrDrawable) Effect.getCompatibleImage(fctx, w, h); 1356 } 1357 } 1358 1359 Graphics getGraphics() { 1360 if (graphics == null) { 1361 graphics = buffer.createGraphics(); 1362 } 1363 return graphics; 1364 } 1365 1366 abstract void init(Graphics g); 1367 1368 abstract void render(Graphics g); 1369 1370 private void dispose() { 1371 if (buffer != null) { 1372 if (permanent) { 1373 buffer.flush(); // releases the resource 1374 } else { 1375 Effect.releaseCompatibleImage(fctx, buffer); 1376 } 1377 fctx = null; 1378 buffer = null; 1379 } 1380 } 1381 1382 private double getX() { return (double) bounds.x; } 1383 private double getY() { return (double) bounds.y; } 1384 } 1385 1386 private final class TransparencyLayer extends Layer { 1387 private final float opacity; 1388 1389 private TransparencyLayer(Graphics g, Rectangle bounds, float opacity) { 1390 super(g, bounds, false); 1391 this.opacity = opacity; 1392 } 1393 1394 @Override void init(Graphics g) { 1395 state.setCompositeOperation(COMPOSITE_SOURCE_OVER); 1396 } 1397 1398 @Override void render(Graphics g) { 1399 new Composite() { 1400 @Override void doPaint(Graphics g) { 1401 float op = g.getExtraAlpha(); 1402 g.setExtraAlpha(opacity); 1403 Affine3D tx = new Affine3D(g.getTransformNoClone()); 1404 g.setTransform(BaseTransform.IDENTITY_TRANSFORM); 1405 g.drawTexture(buffer.getTextureObject(), 1406 bounds.x, bounds.y, bounds.width, bounds.height); 1407 g.setTransform(tx); 1408 g.setExtraAlpha(op); 1409 } 1410 }.paint(g); 1411 } 1412 1413 @Override public String toString() { 1414 return String.format("TransparencyLayer[%d,%d + %dx%d, opacity %.2f]", 1415 bounds.x, bounds.y, bounds.width, bounds.height, opacity); 1416 } 1417 } 1418 1419 private static final class ClipLayer extends Layer { 1420 private final WCPath normalizedToClipPath; 1421 private boolean srcover; 1422 1423 private ClipLayer(Graphics g, Rectangle bounds, WCPath normalizedToClipPath, 1424 boolean permanent) 1425 { 1426 super(g, bounds, permanent); 1427 this.normalizedToClipPath = normalizedToClipPath; 1428 srcover = true; 1429 } 1430 1431 @Override void init(Graphics g) { 1432 RTTexture texture = null; 1433 ReadbackGraphics readbackGraphics = null; 1434 try { 1435 readbackGraphics = (ReadbackGraphics) g; 1436 texture = readbackGraphics.readBack(bounds); 1437 getGraphics().drawTexture(texture, 0, 0, bounds.width, bounds.height); 1438 } finally { 1439 if (readbackGraphics != null && texture != null) { 1440 readbackGraphics.releaseReadBackBuffer(texture); 1441 } 1442 } 1443 srcover = false; 1444 } 1445 1446 @Override void render(Graphics g) { 1447 Path2D p2d = ((WCPathImpl)normalizedToClipPath).getPlatformPath(); 1448 1449 // render normalizedToClipPath to a drawable 1450 PrDrawable bufferImg = (PrDrawable) Effect.getCompatibleImage( 1451 fctx, bounds.width, bounds.height); 1452 Graphics bufferGraphics = bufferImg.createGraphics(); 1453 1454 bufferGraphics.setPaint(Color.BLACK); 1455 bufferGraphics.fill(p2d); 1456 1457 // blend buffer and clipImg onto |g| 1458 if (g instanceof MaskTextureGraphics && ! (g instanceof PrinterGraphics)) { 1459 MaskTextureGraphics mg = (MaskTextureGraphics) g; 1460 if (srcover) { 1461 mg.drawPixelsMasked(buffer.getTextureObject(), 1462 bufferImg.getTextureObject(), 1463 bounds.x, bounds.y, bounds.width, bounds.height, 1464 0, 0, 0, 0); 1465 } else { 1466 mg.maskInterpolatePixels(buffer.getTextureObject(), 1467 bufferImg.getTextureObject(), 1468 bounds.x, bounds.y, bounds.width, bounds.height, 1469 0, 0, 0, 0); 1470 } 1471 } else { 1472 Blend blend = new Blend(Blend.Mode.SRC_IN, 1473 new PassThrough(bufferImg, bounds.width, bounds.height), 1474 new PassThrough(buffer, bounds.width, bounds.height)); 1475 Affine3D tx = new Affine3D(g.getTransformNoClone()); 1476 g.setTransform(BaseTransform.IDENTITY_TRANSFORM); 1477 PrEffectHelper.render(blend, g, bounds.x, bounds.y, null); 1478 g.setTransform(tx); 1479 } 1480 1481 Effect.releaseCompatibleImage(fctx, bufferImg); 1482 } 1483 1484 @Override public String toString() { 1485 return String.format("ClipLayer[%d,%d + %dx%d, path %s]", 1486 bounds.x, bounds.y, bounds.width, bounds.height, 1487 normalizedToClipPath); 1488 } 1489 } 1490 1491 private abstract class Composite { 1492 abstract void doPaint(Graphics g); 1493 1494 void paint() { 1495 paint(getGraphics(true)); 1496 } 1497 1498 void paint(Graphics g) { 1499 if (g != null) { 1500 CompositeMode oldCompositeMode = g.getCompositeMode(); 1501 switch (state.getCompositeOperation()) { 1502 // decode operations that don't require Blend first 1503 case COMPOSITE_COPY: 1504 g.setCompositeMode(CompositeMode.SRC); 1505 doPaint(g); 1506 g.setCompositeMode(oldCompositeMode); 1507 break; 1508 case COMPOSITE_SOURCE_OVER: 1509 g.setCompositeMode(CompositeMode.SRC_OVER); 1510 doPaint(g); 1511 g.setCompositeMode(oldCompositeMode); 1512 break; 1513 default: 1514 // other operations require usage of Blend 1515 blend(g); 1516 break; 1517 } 1518 isRootLayerValid = false; 1519 } 1520 } 1521 1522 private void blend(Graphics g) { 1523 FilterContext fctx = getFilterContext(g); 1524 PrDrawable dstImg = null; 1525 PrDrawable srcImg = null; 1526 ReadbackGraphics readBackGraphics = null; 1527 RTTexture texture = null; 1528 Rectangle clip = state.getClipNoClone(); 1529 WCImage image = getImage(); 1530 try { 1531 if (image != null && image instanceof PrismImage) { 1532 // blending on canvas 1533 dstImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height); 1534 Graphics dstG = dstImg.createGraphics(); 1535 ((PrismImage) image).draw(dstG, 1536 0, 0, clip.width, clip.height, 1537 clip.x, clip.y, clip.width, clip.height); 1538 } else { 1539 // blending on page 1540 readBackGraphics = (ReadbackGraphics) g; 1541 texture = readBackGraphics.readBack(clip); 1542 dstImg = PrDrawable.create(fctx, texture); 1543 } 1544 1545 srcImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height); 1546 Graphics srcG = srcImg.createGraphics(); 1547 state.apply(srcG); 1548 doPaint(srcG); 1549 1550 g.clear(); 1551 PrEffectHelper.render(createEffect(dstImg, srcImg, clip.width, clip.height), g, 0, 0, null); 1552 1553 } finally { 1554 if (srcImg != null) { 1555 Effect.releaseCompatibleImage(fctx, srcImg); 1556 } 1557 if (dstImg != null) { 1558 if (readBackGraphics != null && texture != null) { 1559 readBackGraphics.releaseReadBackBuffer(texture); 1560 } else { 1561 Effect.releaseCompatibleImage(fctx, dstImg); 1562 } 1563 } 1564 } 1565 } 1566 1567 // provides some syntax sugar for createEffect() 1568 private Effect createBlend(Blend.Mode mode, 1569 PrDrawable dstImg, 1570 PrDrawable srcImg, 1571 int width, 1572 int height) 1573 { 1574 return new Blend( 1575 mode, 1576 new PassThrough(dstImg, width, height), 1577 new PassThrough(srcImg, width, height)); 1578 } 1579 1580 private Effect createEffect(PrDrawable dstImg, 1581 PrDrawable srcImg, 1582 int width, 1583 int height) 1584 { 1585 switch (state.getCompositeOperation()) { 1586 case COMPOSITE_CLEAR: // same as xor 1587 case COMPOSITE_XOR: 1588 return new Blend( 1589 SRC_OVER, 1590 createBlend(SRC_OUT, dstImg, srcImg, width, height), 1591 createBlend(SRC_OUT, srcImg, dstImg, width, height) 1592 ); 1593 case COMPOSITE_SOURCE_IN: 1594 return createBlend(SRC_IN, dstImg, srcImg, width, height); 1595 case COMPOSITE_SOURCE_OUT: 1596 return createBlend(SRC_OUT, dstImg, srcImg, width, height); 1597 case COMPOSITE_SOURCE_ATOP: 1598 return createBlend(SRC_ATOP, dstImg, srcImg, width, height); 1599 case COMPOSITE_DESTINATION_OVER: 1600 return createBlend(SRC_OVER, srcImg, dstImg, width, height); 1601 case COMPOSITE_DESTINATION_IN: 1602 return createBlend(SRC_IN, srcImg, dstImg, width, height); 1603 case COMPOSITE_DESTINATION_OUT: 1604 return createBlend(SRC_OUT, srcImg, dstImg, width, height); 1605 case COMPOSITE_DESTINATION_ATOP: 1606 return createBlend(SRC_ATOP, srcImg, dstImg, width, height); 1607 case COMPOSITE_HIGHLIGHT: 1608 return createBlend(ADD, dstImg, srcImg, width, height); 1609 default: 1610 return createBlend(SRC_OVER, dstImg, srcImg, width, height); 1611 } 1612 } 1613 } 1614 1615 private static final class PassThrough extends Effect { 1616 private final PrDrawable img; 1617 private final int width; 1618 private final int height; 1619 1620 private PassThrough(PrDrawable img, int width, int height) { 1621 this.img = img; 1622 this.width = width; 1623 this.height = height; 1624 } 1625 1626 @Override public ImageData filter( 1627 FilterContext fctx, 1628 BaseTransform transform, 1629 Rectangle outputClip, 1630 Object renderHelper, 1631 Effect defaultInput) { 1632 // We have an unpaired lock() here, because unlocking is done 1633 // internally by ImageData. See RT-33625 for details. 1634 img.lock(); 1635 ImageData imgData = new ImageData(fctx, img, new Rectangle( 1636 (int) transform.getMxt(), 1637 (int) transform.getMyt(), 1638 width, height)); 1639 imgData.setReusable(true); 1640 return imgData; 1641 } 1642 1643 @Override public RectBounds getBounds( 1644 BaseTransform transform, 1645 Effect defaultInput) { 1646 return null; 1647 } 1648 1649 @Override public AccelType getAccelType(FilterContext fctx) { 1650 return AccelType.INTRINSIC; 1651 } 1652 1653 @Override 1654 public boolean reducesOpaquePixels() { 1655 return false; 1656 } 1657 1658 @Override 1659 public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) { 1660 return null; 1661 } 1662 } 1663 1664 private static FilterContext getFilterContext(Graphics g) { 1665 Screen screen = g.getAssociatedScreen(); 1666 if (screen == null) { 1667 ResourceFactory factory = g.getResourceFactory(); 1668 return PrFilterContext.getPrinterContext(factory); 1669 } else { 1670 return PrFilterContext.getInstance(screen); 1671 } 1672 } 1673 1674 @Override 1675 public void strokeArc(final int x, final int y, final int w, final int h, 1676 final int startAngle, final int angleSpan) 1677 { 1678 if (log.isLoggable(Level.FINE)) { 1679 log.fine(String.format("strokeArc(%d, %d, %d, %d, %d, %d)", 1680 x, y, w, h, startAngle, angleSpan)); 1681 } 1682 Arc2D arc = new Arc2D(x, y, w, h, startAngle, angleSpan, Arc2D.OPEN); 1683 if (state.getStrokeNoClone().isApplicable() && 1684 !shouldRenderShape(arc, null, state.getStrokeNoClone().getPlatformStroke())) 1685 { 1686 return; 1687 } 1688 new Composite() { 1689 @Override void doPaint(Graphics g) { 1690 if (state.getStrokeNoClone().apply(g)) { 1691 g.draw(arc); 1692 } 1693 } 1694 }.paint(); 1695 } 1696 1697 @Override 1698 public WCImage getImage() { 1699 return null; 1700 } 1701 1702 @Override 1703 public void strokeRect(final float x, final float y, final float w, final float h, 1704 final float lineWidth) { 1705 if (log.isLoggable(Level.FINE)) { 1706 log.fine(String.format("strokeRect_FFFFF(%f, %f, %f, %f, %f)", 1707 x, y, w, h, lineWidth)); 1708 } 1709 BasicStroke stroke = new BasicStroke( 1710 lineWidth, 1711 BasicStroke.CAP_BUTT, 1712 BasicStroke.JOIN_MITER, 1713 Math.max(1.0f, lineWidth), 1714 state.getStrokeNoClone().getDashSizes(), 1715 state.getStrokeNoClone().getDashOffset()); 1716 1717 if (!shouldRenderRect(x, y, w, h, null, stroke)) { 1718 return; 1719 } 1720 new Composite() { 1721 @Override void doPaint(Graphics g) { 1722 g.setStroke(stroke); 1723 Paint paint = state.getStrokeNoClone().getPaint(); 1724 if (paint == null) { 1725 paint = state.getPaintNoClone(); 1726 } 1727 g.setPaint(paint); 1728 g.drawRect(x, y, w, h); 1729 } 1730 }.paint(); 1731 } 1732 1733 @Override 1734 public void strokePath(final WCPath path) { 1735 log.fine("strokePath"); 1736 if (path != null) { 1737 final BasicStroke stroke = state.getStrokeNoClone().getPlatformStroke(); 1738 final DropShadow shadow = state.getShadowNoClone(); 1739 final Path2D p2d = (Path2D)path.getPlatformPath(); 1740 1741 if ((stroke == null && shadow == null) || 1742 !shouldRenderShape(p2d, shadow, stroke)) 1743 { 1744 return; 1745 } 1746 new Composite() { 1747 @Override void doPaint(Graphics g) { 1748 if (shadow != null) { 1749 final NGPath node = new NGPath(); 1750 node.updateWithPath2d(p2d); 1751 render(g, shadow, null, stroke, node); 1752 } else if (stroke != null) { 1753 Paint paint = state.getStrokeNoClone().getPaint(); 1754 if (paint == null) { 1755 paint = state.getPaintNoClone(); 1756 } 1757 g.setPaint(paint); 1758 g.setStroke(stroke); 1759 g.draw(p2d); 1760 } 1761 } 1762 }.paint(); 1763 } 1764 } 1765 1766 @Override 1767 public void fillPath(final WCPath path) { 1768 log.fine("fillPath"); 1769 if (path != null) { 1770 if (!shouldRenderShape(((WCPathImpl)path).getPlatformPath(), 1771 state.getShadowNoClone(), null)) 1772 { 1773 return; 1774 } 1775 new Composite() { 1776 @Override void doPaint(Graphics g) { 1777 Path2D p2d = (Path2D) path.getPlatformPath(); 1778 Paint paint = state.getPaintNoClone(); 1779 DropShadow shadow = state.getShadowNoClone(); 1780 if (shadow != null) { 1781 final NGPath node = new NGPath(); 1782 node.updateWithPath2d(p2d); 1783 render(g, shadow, paint, null, node); 1784 } else { 1785 g.setPaint(paint); 1786 g.fill(p2d); 1787 } 1788 } 1789 }.paint(); 1790 } 1791 } 1792 1793 public void setTransform(WCTransform tm) { 1794 double m[] = tm.getMatrix(); 1795 Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5])); 1796 if (state.getLayerNoClone() == null) { 1797 at.preConcatenate(baseTransform); 1798 } 1799 state.setTransform(at); 1800 resetCachedGraphics(); 1801 } 1802 1803 public WCTransform getTransform() { 1804 Affine3D xf = state.getTransformNoClone(); 1805 return new WCTransform(xf.getMxx(), xf.getMyx(), 1806 xf.getMxy(), xf.getMyy(), 1807 xf.getMxt(), xf.getMyt()); 1808 } 1809 1810 public void concatTransform(WCTransform tm) { 1811 double m[] = tm.getMatrix(); 1812 Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5])); 1813 state.concatTransform(at); 1814 resetCachedGraphics(); 1815 } 1816 1817 @Override 1818 public void flush() { 1819 flushAllLayers(); 1820 } 1821 1822 @Override 1823 public WCGradient createLinearGradient(WCPoint p1, WCPoint p2) { 1824 return new WCLinearGradient(p1, p2); 1825 } 1826 1827 @Override 1828 public WCGradient createRadialGradient(WCPoint p1, float r1, WCPoint p2, float r2) { 1829 return new WCRadialGradient(p1, r1, p2, r2); 1830 } 1831 }