1 /* 2 * Copyright (c) 2012, 2014, 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.sg.prism; 27 28 import javafx.geometry.VPos; 29 import javafx.scene.text.Font; 30 import java.nio.IntBuffer; 31 import java.util.concurrent.ExecutionException; 32 import java.util.concurrent.FutureTask; 33 import java.util.LinkedList; 34 import com.sun.javafx.font.PGFont; 35 import com.sun.javafx.geom.Arc2D; 36 import com.sun.javafx.geom.BaseBounds; 37 import com.sun.javafx.geom.DirtyRegionContainer; 38 import com.sun.javafx.geom.DirtyRegionPool; 39 import com.sun.javafx.geom.Path2D; 40 import com.sun.javafx.geom.PathIterator; 41 import com.sun.javafx.geom.RectBounds; 42 import com.sun.javafx.geom.Rectangle; 43 import com.sun.javafx.geom.RoundRectangle2D; 44 import com.sun.javafx.geom.Shape; 45 import com.sun.javafx.geom.transform.Affine2D; 46 import com.sun.javafx.geom.transform.BaseTransform; 47 import com.sun.javafx.geom.transform.NoninvertibleTransformException; 48 import com.sun.javafx.text.PrismTextLayout; 49 import com.sun.javafx.tk.RenderJob; 50 import com.sun.javafx.tk.Toolkit; 51 import com.sun.prism.BasicStroke; 52 import com.sun.prism.CompositeMode; 53 import com.sun.prism.Graphics; 54 import com.sun.prism.GraphicsPipeline; 55 import com.sun.prism.Image; 56 import com.sun.prism.MaskTextureGraphics; 57 import com.sun.prism.PrinterGraphics; 58 import com.sun.prism.RTTexture; 59 import com.sun.prism.ResourceFactory; 60 import com.sun.prism.Texture; 61 import com.sun.prism.Texture.WrapMode; 62 import com.sun.prism.paint.Color; 63 import com.sun.prism.paint.Paint; 64 import com.sun.scenario.effect.Blend; 65 import com.sun.scenario.effect.Blend.Mode; 66 import com.sun.scenario.effect.Effect; 67 import com.sun.scenario.effect.FilterContext; 68 import com.sun.scenario.effect.Filterable; 69 import com.sun.scenario.effect.ImageData; 70 import com.sun.scenario.effect.impl.prism.PrDrawable; 71 import com.sun.scenario.effect.impl.prism.PrFilterContext; 72 import com.sun.scenario.effect.impl.prism.PrTexture; 73 74 /** 75 */ 76 public class NGCanvas extends NGNode { 77 public static final byte ATTR_BASE = 0; 78 public static final byte GLOBAL_ALPHA = ATTR_BASE + 0; 79 public static final byte COMP_MODE = ATTR_BASE + 1; 80 public static final byte FILL_PAINT = ATTR_BASE + 2; 81 public static final byte STROKE_PAINT = ATTR_BASE + 3; 82 public static final byte LINE_WIDTH = ATTR_BASE + 4; 83 public static final byte LINE_CAP = ATTR_BASE + 5; 84 public static final byte LINE_JOIN = ATTR_BASE + 6; 85 public static final byte MITER_LIMIT = ATTR_BASE + 7; 86 public static final byte FONT = ATTR_BASE + 8; 87 public static final byte TEXT_ALIGN = ATTR_BASE + 9; 88 public static final byte TEXT_BASELINE = ATTR_BASE + 10; 89 public static final byte TRANSFORM = ATTR_BASE + 11; 90 public static final byte EFFECT = ATTR_BASE + 12; 91 public static final byte PUSH_CLIP = ATTR_BASE + 13; 92 public static final byte POP_CLIP = ATTR_BASE + 14; 93 public static final byte ARC_TYPE = ATTR_BASE + 15; 94 public static final byte FILL_RULE = ATTR_BASE + 16; 95 96 public static final byte OP_BASE = 20; 97 public static final byte FILL_RECT = OP_BASE + 0; 98 public static final byte STROKE_RECT = OP_BASE + 1; 99 public static final byte CLEAR_RECT = OP_BASE + 2; 100 public static final byte STROKE_LINE = OP_BASE + 3; 101 public static final byte FILL_OVAL = OP_BASE + 4; 102 public static final byte STROKE_OVAL = OP_BASE + 5; 103 public static final byte FILL_ROUND_RECT = OP_BASE + 6; 104 public static final byte STROKE_ROUND_RECT = OP_BASE + 7; 105 public static final byte FILL_ARC = OP_BASE + 8; 106 public static final byte STROKE_ARC = OP_BASE + 9; 107 public static final byte FILL_TEXT = OP_BASE + 10; 108 public static final byte STROKE_TEXT = OP_BASE + 11; 109 110 public static final byte PATH_BASE = 40; 111 public static final byte PATHSTART = PATH_BASE + 0; 112 public static final byte MOVETO = PATH_BASE + 1; 113 public static final byte LINETO = PATH_BASE + 2; 114 public static final byte QUADTO = PATH_BASE + 3; 115 public static final byte CUBICTO = PATH_BASE + 4; 116 public static final byte CLOSEPATH = PATH_BASE + 5; 117 public static final byte PATHEND = PATH_BASE + 6; 118 public static final byte FILL_PATH = PATH_BASE + 7; 119 public static final byte STROKE_PATH = PATH_BASE + 8; 120 121 public static final byte IMG_BASE = 50; 122 public static final byte DRAW_IMAGE = IMG_BASE + 0; 123 public static final byte DRAW_SUBIMAGE = IMG_BASE + 1; 124 public static final byte PUT_ARGB = IMG_BASE + 2; 125 public static final byte PUT_ARGBPRE_BUF = IMG_BASE + 3; 126 127 public static final byte FX_BASE = 60; 128 public static final byte FX_APPLY_EFFECT = FX_BASE + 0; 129 130 public static final byte UTIL_BASE = 70; 131 public static final byte RESET = UTIL_BASE + 0; 132 public static final byte SET_DIMS = UTIL_BASE + 1; 133 134 public static final byte CAP_BUTT = 0; 135 public static final byte CAP_ROUND = 1; 136 public static final byte CAP_SQUARE = 2; 137 138 public static final byte JOIN_MITER = 0; 139 public static final byte JOIN_ROUND = 1; 140 public static final byte JOIN_BEVEL = 2; 141 142 public static final byte ARC_OPEN = 0; 143 public static final byte ARC_CHORD = 1; 144 public static final byte ARC_PIE = 2; 145 146 public static final byte ALIGN_LEFT = 0; 147 public static final byte ALIGN_CENTER = 1; 148 public static final byte ALIGN_RIGHT = 2; 149 public static final byte ALIGN_JUSTIFY = 3; 150 151 public static final byte BASE_TOP = 0; 152 public static final byte BASE_MIDDLE = 1; 153 public static final byte BASE_ALPHABETIC = 2; 154 public static final byte BASE_BOTTOM = 3; 155 156 public static final byte FILL_RULE_NON_ZERO = 0; 157 public static final byte FILL_RULE_EVEN_ODD = 1; 158 159 static enum InitType { 160 CLEAR, 161 FILL_WHITE, 162 PRESERVE_UPPER_LEFT 163 } 164 165 static class RenderBuf { 166 final InitType init_type; 167 RTTexture tex; 168 Graphics g; 169 EffectInput input; 170 private PixelData savedPixelData = null; 171 172 public RenderBuf(InitType init_type) { 173 this.init_type = init_type; 174 } 175 176 public void dispose() { 177 if (tex != null) tex.dispose(); 178 179 tex = null; 180 g = null; 181 input = null; 182 } 183 184 public boolean validate(Graphics resg, int tw, int th) { 185 int cw, ch; 186 boolean create; 187 if (tex == null) { 188 cw = ch = 0; 189 create = true; 190 } else { 191 cw = tex.getContentWidth(); 192 ch = tex.getContentHeight(); 193 tex.lock(); 194 create = tex.isSurfaceLost() || cw < tw || ch < th; 195 } 196 if (create) { 197 RTTexture oldtex = tex; 198 ResourceFactory factory = (resg == null) 199 ? GraphicsPipeline.getDefaultResourceFactory() 200 : resg.getResourceFactory(); 201 RTTexture newtex = 202 factory.createRTTexture(tw, th, WrapMode.CLAMP_TO_ZERO); 203 this.tex = newtex; 204 this.g = newtex.createGraphics(); 205 this.input = new EffectInput(newtex); 206 if (oldtex != null) { 207 if (init_type == InitType.PRESERVE_UPPER_LEFT) { 208 g.setCompositeMode(CompositeMode.SRC); 209 if (oldtex.isSurfaceLost()) { 210 if (savedPixelData != null) { 211 savedPixelData.restore(g, cw, ch); 212 } 213 } else { 214 g.drawTexture(oldtex, 0, 0, cw, ch); 215 } 216 g.setCompositeMode(CompositeMode.SRC_OVER); 217 } 218 oldtex.unlock(); 219 oldtex.dispose(); 220 } 221 if (init_type == InitType.FILL_WHITE) { 222 g.clear(Color.WHITE); 223 } 224 return true; 225 } else { 226 if (this.g == null) { 227 this.g = tex.createGraphics(); 228 if (this.g == null) { 229 tex.dispose(); 230 ResourceFactory factory = (resg == null) 231 ? GraphicsPipeline.getDefaultResourceFactory() 232 : resg.getResourceFactory(); 233 tex = factory.createRTTexture(tw, th, WrapMode.CLAMP_TO_ZERO); 234 this.g = tex.createGraphics(); 235 this.input = new EffectInput(tex); 236 if (savedPixelData != null) { 237 g.setCompositeMode(CompositeMode.SRC); 238 savedPixelData.restore(g, tw, th); 239 g.setCompositeMode(CompositeMode.SRC_OVER); 240 } else if (init_type == InitType.FILL_WHITE) { 241 g.clear(Color.WHITE); 242 } 243 return true; 244 } 245 } 246 } 247 if (init_type == InitType.CLEAR) { 248 g.clear(); 249 } 250 return false; 251 } 252 253 private void save(int tw, int th) { 254 if (tex.isVolatile()) { 255 if (savedPixelData == null) { 256 savedPixelData = new PixelData(tw, th); 257 } 258 savedPixelData.save(tex); 259 } 260 } 261 } 262 263 // Saved pixel data used to preserve the image that backs the canvas if the 264 // RTT is volatile. 265 private static class PixelData { 266 private IntBuffer pixels = null; 267 private boolean validPixels = false; 268 private int cw, ch; 269 270 private PixelData(int cw, int ch) { 271 this.cw = cw; 272 this.ch = ch; 273 pixels = IntBuffer.allocate(cw*ch); 274 } 275 276 private void save(RTTexture tex) { 277 int tw = tex.getContentWidth(); 278 int th = tex.getContentHeight(); 279 if (cw < tw || ch < th) { 280 cw = tw; 281 ch = th; 282 pixels = IntBuffer.allocate(cw*ch); 283 } 284 pixels.rewind(); 285 tex.readPixels(pixels); 286 validPixels = true; 287 } 288 289 private void restore(Graphics g, int tw, int th) { 290 if (validPixels) { 291 Image img = Image.fromIntArgbPreData(pixels, tw, th); 292 ResourceFactory factory = g.getResourceFactory(); 293 Texture tempTex = 294 factory.createTexture(img, 295 Texture.Usage.DEFAULT, 296 Texture.WrapMode.CLAMP_TO_EDGE); 297 g.drawTexture(tempTex, 0, 0, tw, th); 298 tempTex.dispose(); 299 } 300 } 301 } 302 303 private static Blend BLENDER = new MyBlend(Mode.SRC_OVER, null, null); 304 305 private GrowableDataBuffer thebuf; 306 307 private int tw, th; 308 private int cw, ch; 309 private RenderBuf cv; 310 private RenderBuf temp; 311 private RenderBuf clip; 312 313 private float globalAlpha; 314 private Blend.Mode blendmode; 315 private Paint fillPaint, strokePaint; 316 private float linewidth; 317 private int linecap, linejoin; 318 private float miterlimit; 319 private BasicStroke stroke; 320 private Path2D path; 321 private NGText ngtext; 322 private PrismTextLayout textLayout; 323 private PGFont pgfont; 324 private int align; 325 private int baseline; 326 private Affine2D transform; 327 private Affine2D inverseTransform; 328 private boolean inversedirty; 329 private LinkedList<Path2D> clipStack; 330 private int clipsRendered; 331 private boolean clipIsRect; 332 private Rectangle clipRect; 333 private Effect effect; 334 private int arctype; 335 336 static float TEMP_COORDS[] = new float[6]; 337 private static Arc2D TEMP_ARC = new Arc2D(); 338 private static RectBounds TEMP_RECTBOUNDS = new RectBounds(); 339 340 public NGCanvas() { 341 cv = new RenderBuf(InitType.PRESERVE_UPPER_LEFT); 342 temp = new RenderBuf(InitType.CLEAR); 343 clip = new RenderBuf(InitType.FILL_WHITE); 344 345 path = new Path2D(); 346 ngtext = new NGText(); 347 textLayout = new PrismTextLayout(); 348 transform = new Affine2D(); 349 clipStack = new LinkedList<Path2D>(); 350 initAttributes(); 351 } 352 353 private void initAttributes() { 354 globalAlpha = 1.0f; 355 blendmode = Mode.SRC_OVER; 356 fillPaint = Color.BLACK; 357 strokePaint = Color.BLACK; 358 linewidth = 1.0f; 359 linecap = BasicStroke.CAP_SQUARE; 360 linejoin = BasicStroke.JOIN_MITER; 361 miterlimit = 10f; 362 stroke = null; 363 path.setWindingRule(Path2D.WIND_NON_ZERO); 364 // ngtext stores no state between render operations 365 // textLayout stores no state between render operations 366 pgfont = (PGFont) Font.getDefault().impl_getNativeFont(); 367 align = ALIGN_LEFT; 368 baseline = VPos.BASELINE.ordinal(); 369 transform.setToScale(highestPixelScale, highestPixelScale); 370 clipStack.clear(); 371 resetClip(false); 372 } 373 374 static final Affine2D TEMP_PATH_TX = new Affine2D(); 375 static final int numCoords[] = { 2, 2, 4, 6, 0 }; 376 Shape untransformedPath = new Shape() { 377 378 @Override 379 public RectBounds getBounds() { 380 if (transform.isTranslateOrIdentity()) { 381 RectBounds rb = path.getBounds(); 382 if (transform.isIdentity()) { 383 return rb; 384 } else { 385 float tx = (float) transform.getMxt(); 386 float ty = (float) transform.getMyt(); 387 return new RectBounds(rb.getMinX() - tx, rb.getMinY() - ty, 388 rb.getMaxX() - tx, rb.getMaxY() - ty); 389 } 390 } 391 // We could use Shape.accumulate, but that method optimizes the 392 // bounds for curves and the optimized code above will simply ask 393 // the path for its bounds - which in this case of a Path2D would 394 // simply accumulate all of the coordinates in the buffer. So, 395 // we write a simpler accumulator loop here to be consistent with 396 // the optimized case above. 397 float x0 = Float.POSITIVE_INFINITY; 398 float y0 = Float.POSITIVE_INFINITY; 399 float x1 = Float.NEGATIVE_INFINITY; 400 float y1 = Float.NEGATIVE_INFINITY; 401 PathIterator pi = path.getPathIterator(getInverseTransform()); 402 while (!pi.isDone()) { 403 int ncoords = numCoords[pi.currentSegment(TEMP_COORDS)]; 404 for (int i = 0; i < ncoords; i += 2) { 405 if (x0 > TEMP_COORDS[i+0]) x0 = TEMP_COORDS[i+0]; 406 if (x1 < TEMP_COORDS[i+0]) x1 = TEMP_COORDS[i+0]; 407 if (y0 > TEMP_COORDS[i+1]) y0 = TEMP_COORDS[i+1]; 408 if (y1 < TEMP_COORDS[i+1]) y1 = TEMP_COORDS[i+1]; 409 } 410 pi.next(); 411 } 412 return new RectBounds(x0, y0, x1, y1); 413 } 414 415 @Override 416 public boolean contains(float x, float y) { 417 TEMP_COORDS[0] = x; 418 TEMP_COORDS[1] = y; 419 transform.transform(TEMP_COORDS, 0, TEMP_COORDS, 0, 1); 420 x = TEMP_COORDS[0]; 421 y = TEMP_COORDS[1]; 422 return path.contains(x, y); 423 } 424 425 @Override 426 public boolean intersects(float x, float y, float w, float h) { 427 if (transform.isTranslateOrIdentity()) { 428 x += transform.getMxt(); 429 y += transform.getMyt(); 430 return path.intersects(x, y, w, h); 431 } 432 PathIterator pi = path.getPathIterator(getInverseTransform()); 433 int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h); 434 // int mask = (windingRule == WIND_NON_ZERO ? -1 : 2); 435 // return (crossings == Shape.RECT_INTERSECTS || 436 // (crossings & mask) != 0); 437 // with wind == NON_ZERO, then mask == -1 and 438 // since REC_INTERSECTS != 0, we simplify to: 439 return (crossings != 0); 440 } 441 442 @Override 443 public boolean contains(float x, float y, float w, float h) { 444 if (transform.isTranslateOrIdentity()) { 445 x += transform.getMxt(); 446 y += transform.getMyt(); 447 return path.contains(x, y, w, h); 448 } 449 PathIterator pi = path.getPathIterator(getInverseTransform()); 450 int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h); 451 // int mask = (windingRule == WIND_NON_ZERO ? -1 : 2); 452 // return (crossings != Shape.RECT_INTERSECTS && 453 // (crossings & mask) != 0); 454 // with wind == NON_ZERO, then mask == -1 we simplify to: 455 return (crossings != Shape.RECT_INTERSECTS && crossings != 0); 456 } 457 458 public BaseTransform getCombinedTransform(BaseTransform tx) { 459 if (transform.isIdentity()) return tx; 460 if (transform.equals(tx)) return null; 461 Affine2D inv = getInverseTransform(); 462 if (tx == null || tx.isIdentity()) return inv; 463 TEMP_PATH_TX.setTransform(tx); 464 TEMP_PATH_TX.concatenate(inv); 465 return TEMP_PATH_TX; 466 } 467 468 @Override 469 public PathIterator getPathIterator(BaseTransform tx) { 470 return path.getPathIterator(getCombinedTransform(tx)); 471 } 472 473 @Override 474 public PathIterator getPathIterator(BaseTransform tx, float flatness) { 475 return path.getPathIterator(getCombinedTransform(tx), flatness); 476 } 477 478 @Override 479 public Shape copy() { 480 throw new UnsupportedOperationException("Not supported yet."); 481 } 482 }; 483 484 private Affine2D getInverseTransform() { 485 if (inverseTransform == null) { 486 inverseTransform = new Affine2D(); 487 inversedirty = true; 488 } 489 if (inversedirty) { 490 inverseTransform.setTransform(transform); 491 try { 492 inverseTransform.invert(); 493 } catch (NoninvertibleTransformException e) { 494 inverseTransform.setToScale(0, 0); 495 } 496 inversedirty = false; 497 } 498 return inverseTransform; 499 } 500 501 @Override 502 protected boolean hasOverlappingContents() { 503 return true; 504 } 505 506 private static void shapebounds(Shape shape, RectBounds bounds, 507 BaseTransform transform) 508 { 509 TEMP_COORDS[0] = TEMP_COORDS[1] = Float.POSITIVE_INFINITY; 510 TEMP_COORDS[2] = TEMP_COORDS[3] = Float.NEGATIVE_INFINITY; 511 Shape.accumulate(TEMP_COORDS, shape, transform); 512 bounds.setBounds(TEMP_COORDS[0], TEMP_COORDS[1], 513 TEMP_COORDS[2], TEMP_COORDS[3]); 514 } 515 516 private static void strokebounds(BasicStroke stroke, Shape shape, 517 RectBounds bounds, BaseTransform transform) 518 { 519 TEMP_COORDS[0] = TEMP_COORDS[1] = Float.POSITIVE_INFINITY; 520 TEMP_COORDS[2] = TEMP_COORDS[3] = Float.NEGATIVE_INFINITY; 521 stroke.accumulateShapeBounds(TEMP_COORDS, shape, transform); 522 bounds.setBounds(TEMP_COORDS[0], TEMP_COORDS[1], 523 TEMP_COORDS[2], TEMP_COORDS[3]); 524 } 525 526 private static void runOnRenderThread(final Runnable r) { 527 // We really need a standard mechanism to detect the render thread ! 528 if (Thread.currentThread().getName().startsWith("QuantumRenderer")) { 529 r.run(); 530 } else { 531 FutureTask<Void> f = new FutureTask<Void>(r, null); 532 Toolkit.getToolkit().addRenderJob(new RenderJob(f)); 533 try { 534 // block until job is complete 535 f.get(); 536 } catch (ExecutionException ex) { 537 throw new AssertionError(ex); 538 } catch (InterruptedException ex) { 539 // ignore; recovery is impossible 540 } 541 } 542 } 543 544 private boolean printedCanvas(Graphics g) { 545 final RTTexture localTex = cv.tex; 546 if (!(g instanceof PrinterGraphics) || localTex == null) { 547 return false; 548 } 549 ResourceFactory factory = g.getResourceFactory(); 550 boolean isCompatTex = factory.isCompatibleTexture(localTex); 551 if (isCompatTex) { 552 return false; 553 } 554 555 final int tw = localTex.getContentWidth(); 556 final int th = localTex.getContentHeight(); 557 final RTTexture tmpTex = 558 factory.createRTTexture(tw, th, WrapMode.CLAMP_TO_ZERO); 559 final Graphics texg = tmpTex.createGraphics(); 560 texg.setCompositeMode(CompositeMode.SRC); 561 if (cv.savedPixelData == null) { 562 final PixelData pd = new PixelData(cw, ch); 563 runOnRenderThread(() -> { 564 pd.save(localTex); 565 pd.restore(texg, tw, th); 566 }); 567 } else { 568 cv.savedPixelData.restore(texg, tw, th); 569 } 570 g.drawTexture(tmpTex, 0, 0, tw, th); 571 tmpTex.unlock(); 572 tmpTex.dispose(); 573 return true; 574 } 575 576 @Override 577 protected void renderContent(Graphics g) { 578 if (printedCanvas(g)) return; 579 initCanvas(g); 580 if (cv.tex != null) { 581 if (thebuf != null) { 582 renderStream(thebuf); 583 GrowableDataBuffer.returnBuffer(thebuf); 584 thebuf = null; 585 } 586 float dw = tw / highestPixelScale; 587 float dh = th / highestPixelScale; 588 g.drawTexture(cv.tex, 589 0, 0, dw, dh, 590 0, 0, tw, th); 591 // Must save the pixels every frame if RTT is volatile. 592 cv.save(tw, th); 593 } 594 this.temp.g = this.clip.g = this.cv.g = null; 595 } 596 597 @Override 598 public void renderForcedContent(Graphics gOptional) { 599 if (thebuf != null) { 600 initCanvas(gOptional); 601 if (cv.tex != null) { 602 renderStream(thebuf); 603 GrowableDataBuffer.returnBuffer(thebuf); 604 thebuf = null; 605 cv.save(tw, th); 606 } 607 this.temp.g = this.clip.g = this.cv.g = null; 608 } 609 } 610 611 private void initCanvas(Graphics g) { 612 if (tw <= 0 || th <= 0) { 613 cv.dispose(); 614 return; 615 } 616 if (cv.validate(g, tw, th)) { 617 // If the texture was recreated then we add a permanent 618 // "useful" and extra "lock" status to it. 619 cv.tex.contentsUseful(); 620 cv.tex.makePermanent(); 621 cv.tex.lock(); 622 } 623 } 624 625 private void clearCanvas(int x, int y, int w, int h) { 626 cv.g.setCompositeMode(CompositeMode.CLEAR); 627 cv.g.setTransform(BaseTransform.IDENTITY_TRANSFORM); 628 cv.g.fillQuad(x, y, x+w, y+h); 629 cv.g.setCompositeMode(CompositeMode.SRC_OVER); 630 } 631 632 private void resetClip(boolean andDispose) { 633 if (andDispose) clip.dispose(); 634 clipsRendered = 0; 635 clipIsRect = true; 636 clipRect = null; 637 } 638 639 private static final float CLIPRECT_TOLERANCE = 1.0f / 256.0f; 640 private static final Rectangle TEMP_RECT = new Rectangle(); 641 private boolean initClip() { 642 boolean clipValidated; 643 if (clipIsRect) { 644 clipValidated = false; 645 } else { 646 clipValidated = true; 647 if (clip.validate(cv.g, tw, th)) { 648 clip.tex.contentsUseful(); 649 // Reset, but do not dispose - we just validated (and cleared) it... 650 resetClip(false); 651 } 652 } 653 int clipSize = clipStack.size(); 654 while (clipsRendered < clipSize) { 655 Path2D clippath = clipStack.get(clipsRendered++); 656 if (clipIsRect) { 657 if (clippath.checkAndGetIntRect(TEMP_RECT, CLIPRECT_TOLERANCE)) { 658 if (clipRect == null) { 659 clipRect = new Rectangle(TEMP_RECT); 660 } else { 661 clipRect.intersectWith(TEMP_RECT); 662 } 663 continue; 664 } 665 clipIsRect = false; 666 if (!clipValidated) { 667 clipValidated = true; 668 if (clip.validate(cv.g, tw, th)) { 669 clip.tex.contentsUseful(); 670 // No need to reset, this is our first fill. 671 } 672 } 673 if (clipRect != null) { 674 renderClip(new RoundRectangle2D(clipRect.x, clipRect.y, 675 clipRect.width, clipRect.height, 676 0, 0)); 677 } 678 } 679 shapebounds(clippath, TEMP_RECTBOUNDS, BaseTransform.IDENTITY_TRANSFORM); 680 TEMP_RECT.setBounds(TEMP_RECTBOUNDS); 681 if (clipRect == null) { 682 clipRect = new Rectangle(TEMP_RECT); 683 } else { 684 clipRect.intersectWith(TEMP_RECT); 685 } 686 renderClip(clippath); 687 } 688 if (clipValidated && clipIsRect) { 689 clip.tex.unlock(); 690 } 691 return !clipIsRect; 692 } 693 694 private void renderClip(Shape clippath) { 695 temp.validate(cv.g, tw, th); 696 temp.g.setPaint(Color.WHITE); 697 temp.g.setTransform(BaseTransform.IDENTITY_TRANSFORM); 698 temp.g.fill(clippath); 699 blendAthruBintoC(temp, Mode.SRC_IN, clip, null, CompositeMode.SRC, clip); 700 temp.tex.unlock(); 701 } 702 703 private Rectangle applyEffectOnAintoC(Effect definput, 704 Effect effect, 705 BaseTransform transform, 706 Rectangle outputClip, 707 CompositeMode comp, 708 RenderBuf destbuf) 709 { 710 FilterContext fctx = 711 PrFilterContext.getInstance(destbuf.tex.getAssociatedScreen()); 712 ImageData id = 713 effect.filter(fctx, transform, outputClip, null, definput); 714 Rectangle r = id.getUntransformedBounds(); 715 Filterable f = id.getUntransformedImage(); 716 Texture tex = ((PrTexture) f).getTextureObject(); 717 destbuf.g.setTransform(id.getTransform()); 718 destbuf.g.setCompositeMode(comp); 719 destbuf.g.drawTexture(tex, r.x, r.y, r.width, r.height); 720 destbuf.g.setTransform(BaseTransform.IDENTITY_TRANSFORM); 721 destbuf.g.setCompositeMode(CompositeMode.SRC_OVER); 722 Rectangle resultBounds = id.getTransformedBounds(outputClip); 723 id.unref(); 724 return resultBounds; 725 } 726 727 private void blendAthruBintoC(RenderBuf drawbuf, 728 Mode mode, 729 RenderBuf clipbuf, 730 RectBounds bounds, 731 CompositeMode comp, 732 RenderBuf destbuf) 733 { 734 BLENDER.setTopInput(drawbuf.input); 735 BLENDER.setBottomInput(clipbuf.input); 736 BLENDER.setMode(mode); 737 Rectangle blendclip; 738 if (bounds != null) { 739 blendclip = new Rectangle(bounds); 740 } else { 741 blendclip = null; 742 } 743 applyEffectOnAintoC(null, BLENDER, 744 BaseTransform.IDENTITY_TRANSFORM, blendclip, 745 comp, destbuf); 746 } 747 748 private void setupFill(Graphics gr) { 749 gr.setPaint(fillPaint); 750 } 751 752 private BasicStroke getStroke() { 753 if (stroke == null) { 754 stroke = new BasicStroke(linewidth, linecap, linejoin, 755 miterlimit); 756 } 757 return stroke; 758 } 759 760 private void setupStroke(Graphics gr) { 761 gr.setStroke(getStroke()); 762 gr.setPaint(strokePaint); 763 } 764 765 private static final int prcaps[] = { 766 BasicStroke.CAP_BUTT, 767 BasicStroke.CAP_ROUND, 768 BasicStroke.CAP_SQUARE, 769 }; 770 private static final int prjoins[] = { 771 BasicStroke.JOIN_MITER, 772 BasicStroke.JOIN_ROUND, 773 BasicStroke.JOIN_BEVEL, 774 }; 775 private static final int prbases[] = { 776 VPos.TOP.ordinal(), 777 VPos.CENTER.ordinal(), 778 VPos.BASELINE.ordinal(), 779 VPos.BOTTOM.ordinal(), 780 }; 781 private static final Affine2D TEMP_TX = new Affine2D(); 782 private void renderStream(GrowableDataBuffer buf) { 783 while (buf.hasValues()) { 784 int token = buf.getByte(); 785 switch (token) { 786 case RESET: 787 initAttributes(); 788 // RESET is always followed by SET_DIMS 789 // Setting cwh = twh avoids unnecessary double clears 790 this.cw = this.tw; 791 this.ch = this.th; 792 clearCanvas(0, 0, this.tw, this.th); 793 break; 794 case SET_DIMS: 795 int neww = (int) Math.ceil(buf.getFloat() * highestPixelScale); 796 int newh = (int) Math.ceil(buf.getFloat() * highestPixelScale); 797 int clearx = Math.min(neww, this.cw); 798 int cleary = Math.min(newh, this.ch); 799 if (clearx < this.tw) { 800 // tw is set to the final width, we simulate all of 801 // the intermediate changes in size by making sure 802 // that all pixels outside of any size change are 803 // cleared at the stream point where they happened 804 clearCanvas(clearx, 0, this.tw-clearx, this.th); 805 } 806 if (cleary < this.th) { 807 // th is set to the final width, we simulate all of 808 // the intermediate changes in size by making sure 809 // that all pixels outside of any size change are 810 // cleared at the stream point where they happened 811 clearCanvas(0, cleary, this.tw, this.th-cleary); 812 } 813 this.cw = neww; 814 this.ch = newh; 815 break; 816 case PATHSTART: 817 path.reset(); 818 break; 819 case MOVETO: 820 path.moveTo(buf.getFloat(), buf.getFloat()); 821 break; 822 case LINETO: 823 path.lineTo(buf.getFloat(), buf.getFloat()); 824 break; 825 case QUADTO: 826 path.quadTo(buf.getFloat(), buf.getFloat(), 827 buf.getFloat(), buf.getFloat()); 828 break; 829 case CUBICTO: 830 path.curveTo(buf.getFloat(), buf.getFloat(), 831 buf.getFloat(), buf.getFloat(), 832 buf.getFloat(), buf.getFloat()); 833 break; 834 case CLOSEPATH: 835 path.closePath(); 836 break; 837 case PATHEND: 838 if (highestPixelScale != 1.0f) { 839 TEMP_TX.setToScale(highestPixelScale, highestPixelScale); 840 path.transform(TEMP_TX); 841 } 842 break; 843 case PUSH_CLIP: 844 { 845 Path2D clippath = (Path2D) buf.getObject(); 846 if (highestPixelScale != 1.0f) { 847 TEMP_TX.setToScale(highestPixelScale, highestPixelScale); 848 clippath.transform(TEMP_TX); 849 } 850 clipStack.addLast(clippath); 851 break; 852 } 853 case POP_CLIP: 854 // Let it be recreated when next needed 855 resetClip(true); 856 clipStack.removeLast(); 857 break; 858 case ARC_TYPE: 859 { 860 byte type = buf.getByte(); 861 switch (type) { 862 case ARC_OPEN: arctype = Arc2D.OPEN; break; 863 case ARC_CHORD: arctype = Arc2D.CHORD; break; 864 case ARC_PIE: arctype = Arc2D.PIE; break; 865 } 866 break; 867 } 868 case PUT_ARGB: 869 { 870 float dx1 = buf.getInt(); 871 float dy1 = buf.getInt(); 872 int argb = buf.getInt(); 873 Graphics gr = cv.g; 874 gr.setExtraAlpha(1.0f); 875 gr.setCompositeMode(CompositeMode.SRC); 876 gr.setTransform(BaseTransform.IDENTITY_TRANSFORM); 877 dx1 *= highestPixelScale; 878 dy1 *= highestPixelScale; 879 float a = ((argb) >>> 24) / 255.0f; 880 float r = (((argb) >> 16) & 0xff) / 255.0f; 881 float g = (((argb) >> 8) & 0xff) / 255.0f; 882 float b = (((argb) ) & 0xff) / 255.0f; 883 gr.setPaint(new Color(r, g, b, a)); 884 // Note that we cannot use fillRect here because SRC 885 // mode does not interact well with antialiasing. 886 // fillQuad does hard edges which matches the concept 887 // of setting adjacent abutting, non-overlapping "pixels" 888 gr.fillQuad(dx1, dy1, dx1+highestPixelScale, dy1+highestPixelScale); 889 gr.setCompositeMode(CompositeMode.SRC_OVER); 890 break; 891 } 892 case PUT_ARGBPRE_BUF: 893 { 894 float dx1 = buf.getInt(); 895 float dy1 = buf.getInt(); 896 int w = buf.getInt(); 897 int h = buf.getInt(); 898 byte[] data = (byte[]) buf.getObject(); 899 Image img = Image.fromByteBgraPreData(data, w, h); 900 Graphics gr = cv.g; 901 ResourceFactory factory = gr.getResourceFactory(); 902 Texture tex = 903 factory.getCachedTexture(img, Texture.WrapMode.CLAMP_TO_EDGE); 904 gr.setTransform(BaseTransform.IDENTITY_TRANSFORM); 905 gr.setCompositeMode(CompositeMode.SRC); 906 float dx2 = dx1 + w; 907 float dy2 = dy1 + h; 908 dx1 *= highestPixelScale; 909 dy1 *= highestPixelScale; 910 dx2 *= highestPixelScale; 911 dy2 *= highestPixelScale; 912 gr.drawTexture(tex, 913 dx1, dy1, dx2, dy2, 914 0, 0, w, h); 915 tex.contentsNotUseful(); 916 tex.unlock(); 917 gr.setCompositeMode(CompositeMode.SRC_OVER); 918 break; 919 } 920 case TRANSFORM: 921 { 922 double mxx = buf.getDouble() * highestPixelScale; 923 double mxy = buf.getDouble() * highestPixelScale; 924 double mxt = buf.getDouble() * highestPixelScale; 925 double myx = buf.getDouble() * highestPixelScale; 926 double myy = buf.getDouble() * highestPixelScale; 927 double myt = buf.getDouble() * highestPixelScale; 928 transform.setTransform(mxx, myx, mxy, myy, mxt, myt); 929 inversedirty = true; 930 break; 931 } 932 case GLOBAL_ALPHA: 933 globalAlpha = buf.getFloat(); 934 break; 935 case FILL_RULE: 936 if (buf.getByte() == FILL_RULE_NON_ZERO) { 937 path.setWindingRule(Path2D.WIND_NON_ZERO); 938 } else { 939 path.setWindingRule(Path2D.WIND_EVEN_ODD); 940 } 941 break; 942 case COMP_MODE: 943 blendmode = (Blend.Mode)buf.getObject(); 944 break; 945 case FILL_PAINT: 946 fillPaint = (Paint) buf.getObject(); 947 break; 948 case STROKE_PAINT: 949 strokePaint = (Paint) buf.getObject(); 950 break; 951 case LINE_WIDTH: 952 linewidth = buf.getFloat(); 953 stroke = null; 954 break; 955 case LINE_CAP: 956 linecap = prcaps[buf.getUByte()]; 957 stroke = null; 958 break; 959 case LINE_JOIN: 960 linejoin = prjoins[buf.getUByte()]; 961 stroke = null; 962 break; 963 case MITER_LIMIT: 964 miterlimit = buf.getFloat(); 965 stroke = null; 966 break; 967 case FONT: 968 { 969 pgfont = (PGFont) buf.getObject(); 970 break; 971 } 972 case TEXT_ALIGN: 973 align = buf.getUByte(); 974 break; 975 case TEXT_BASELINE: 976 baseline = prbases[buf.getUByte()]; 977 break; 978 case FX_APPLY_EFFECT: 979 { 980 Effect e = (Effect) buf.getObject(); 981 RenderBuf dest = clipStack.isEmpty() ? cv : temp; 982 BaseTransform tx; 983 if (highestPixelScale != 1.0f) { 984 TEMP_TX.setToScale(highestPixelScale, highestPixelScale); 985 tx = TEMP_TX; 986 cv.input.setPixelScale(highestPixelScale); 987 } else { 988 tx = BaseTransform.IDENTITY_TRANSFORM; 989 } 990 applyEffectOnAintoC(cv.input, e, 991 tx, null, 992 CompositeMode.SRC, dest); 993 cv.input.setPixelScale(1.0f); 994 if (dest != cv) { 995 blendAthruBintoC(dest, Mode.SRC_IN, clip, 996 null, CompositeMode.SRC, cv); 997 } 998 break; 999 } 1000 case EFFECT: 1001 effect = (Effect) buf.getObject(); 1002 break; 1003 case FILL_PATH: 1004 case STROKE_PATH: 1005 case STROKE_LINE: 1006 case FILL_RECT: 1007 case CLEAR_RECT: 1008 case STROKE_RECT: 1009 case FILL_OVAL: 1010 case STROKE_OVAL: 1011 case FILL_ROUND_RECT: 1012 case STROKE_ROUND_RECT: 1013 case FILL_ARC: 1014 case STROKE_ARC: 1015 case DRAW_IMAGE: 1016 case DRAW_SUBIMAGE: 1017 case FILL_TEXT: 1018 case STROKE_TEXT: 1019 { 1020 RenderBuf dest; 1021 boolean tempvalidated; 1022 boolean clipvalidated = initClip(); 1023 if (clipvalidated) { 1024 temp.validate(cv.g, tw, th); 1025 tempvalidated = true; 1026 dest = temp; 1027 } else if (blendmode != Blend.Mode.SRC_OVER) { 1028 temp.validate(cv.g, tw, th); 1029 tempvalidated = true; 1030 dest = temp; 1031 } else { 1032 tempvalidated = false; 1033 dest = cv; 1034 } 1035 if (effect != null) { 1036 buf.save(); 1037 handleRenderOp(token, buf, null, TEMP_RECTBOUNDS); 1038 RenderInput ri = 1039 new RenderInput(token, buf, transform, TEMP_RECTBOUNDS); 1040 // If we are rendering to cv then we need the results of 1041 // the effect to be applied "SRC_OVER" onto the canvas. 1042 // If we are rendering to temp then either SRC or SRC_OVER 1043 // would work since we know it would have been freshly 1044 // erased above, but using the more common SRC_OVER may save 1045 // having to update the hardware blend equations. 1046 Rectangle resultBounds = 1047 applyEffectOnAintoC(ri, effect, 1048 transform, clipRect, 1049 CompositeMode.SRC_OVER, dest); 1050 if (dest != cv) { 1051 TEMP_RECTBOUNDS.setBounds(resultBounds.x, resultBounds.y, 1052 resultBounds.x + resultBounds.width, 1053 resultBounds.y + resultBounds.height); 1054 } 1055 } else { 1056 Graphics g = dest.g; 1057 g.setExtraAlpha(globalAlpha); 1058 g.setTransform(transform); 1059 g.setClipRect(clipRect); 1060 // If we are not rendering directly to the canvas then 1061 // we need to save the bounds for the later stages. 1062 RectBounds optSaveBounds = 1063 (dest != cv) ? TEMP_RECTBOUNDS : null; 1064 handleRenderOp(token, buf, g, optSaveBounds); 1065 g.setClipRect(null); 1066 } 1067 if (clipvalidated) { 1068 CompositeMode compmode; 1069 if (blendmode == Blend.Mode.SRC_OVER) { 1070 // For the SRC_OVER case we can point the clip 1071 // operation directly to the screen with the Prism 1072 // SRC_OVER composite mode. 1073 dest = cv; 1074 compmode = CompositeMode.SRC_OVER; 1075 } else { 1076 // Here we are blending the rendered pixels that 1077 // were output to the temp buffer above against the 1078 // pixels of the canvas and we need to put them 1079 // back into the temp buffer. We must use SRC 1080 // mode here so that the erased (or reduced) pixels 1081 // actually get reduced to their new alpha. 1082 // assert: dest == temp; 1083 compmode = CompositeMode.SRC; 1084 } 1085 if (clipRect != null) { 1086 TEMP_RECTBOUNDS.intersectWith(clipRect); 1087 } 1088 if (!TEMP_RECTBOUNDS.isEmpty()) { 1089 if (dest == cv && cv.g instanceof MaskTextureGraphics) { 1090 MaskTextureGraphics mtg = (MaskTextureGraphics) cv.g; 1091 int dx = (int) Math.floor(TEMP_RECTBOUNDS.getMinX()); 1092 int dy = (int) Math.floor(TEMP_RECTBOUNDS.getMinY()); 1093 int dw = (int) Math.ceil(TEMP_RECTBOUNDS.getMaxX()) - dx; 1094 int dh = (int) Math.ceil(TEMP_RECTBOUNDS.getMaxY()) - dy; 1095 mtg.drawPixelsMasked(temp.tex, clip.tex, 1096 dx, dy, dw, dh, 1097 dx, dy, dx, dy); 1098 } else { 1099 blendAthruBintoC(temp, Mode.SRC_IN, clip, 1100 TEMP_RECTBOUNDS, compmode, dest); 1101 } 1102 } 1103 } 1104 if (blendmode != Blend.Mode.SRC_OVER) { 1105 // We always use SRC mode here because the results of 1106 // the blend operation are final and must replace 1107 // the associated pixel in the canvas with no further 1108 // blending math. 1109 if (clipRect != null) { 1110 TEMP_RECTBOUNDS.intersectWith(clipRect); 1111 } 1112 blendAthruBintoC(temp, blendmode, cv, 1113 TEMP_RECTBOUNDS, CompositeMode.SRC, cv); 1114 } 1115 if (clipvalidated) { 1116 clip.tex.unlock(); 1117 } 1118 if (tempvalidated) { 1119 temp.tex.unlock(); 1120 } 1121 break; 1122 } 1123 default: 1124 throw new InternalError("Unrecognized PGCanvas token: "+token); 1125 } 1126 } 1127 } 1128 1129 /** 1130 * Calculate bounds and/or render one single rendering operation. 1131 * All of the data for the rendering operation should be consumed 1132 * so that the buffer is left at the next token in the stream. 1133 * 1134 * @param token the stream token for the rendering op 1135 * @param buf the GrowableDataBuffer to get rendering info from 1136 * @param gr the Graphics to render to, if not null 1137 * @param bounds the RectBounds to accumulate bounds into, if not null 1138 */ 1139 public void handleRenderOp(int token, GrowableDataBuffer buf, 1140 Graphics gr, RectBounds bounds) 1141 { 1142 boolean strokeBounds = false; 1143 boolean transformBounds = false; 1144 switch (token) { 1145 case FILL_PATH: 1146 { 1147 if (bounds != null) { 1148 shapebounds(path, bounds, BaseTransform.IDENTITY_TRANSFORM); 1149 } 1150 if (gr != null) { 1151 setupFill(gr); 1152 gr.fill(untransformedPath); 1153 } 1154 break; 1155 } 1156 case STROKE_PATH: 1157 { 1158 if (bounds != null) { 1159 strokebounds(getStroke(), untransformedPath, bounds, transform); 1160 } 1161 if (gr != null) { 1162 setupStroke(gr); 1163 gr.draw(untransformedPath); 1164 } 1165 break; 1166 } 1167 case STROKE_LINE: 1168 { 1169 float x1 = buf.getFloat(); 1170 float y1 = buf.getFloat(); 1171 float x2 = buf.getFloat(); 1172 float y2 = buf.getFloat(); 1173 if (bounds != null) { 1174 bounds.setBoundsAndSort(x1, y1, x2, y2); 1175 strokeBounds = true; 1176 transformBounds = true; 1177 } 1178 if (gr != null) { 1179 setupStroke(gr); 1180 gr.drawLine(x1, y1, x2, y2); 1181 } 1182 break; 1183 } 1184 case STROKE_RECT: 1185 case STROKE_OVAL: 1186 strokeBounds = true; 1187 case FILL_RECT: 1188 case CLEAR_RECT: 1189 case FILL_OVAL: 1190 { 1191 float x = buf.getFloat(); 1192 float y = buf.getFloat(); 1193 float w = buf.getFloat(); 1194 float h = buf.getFloat(); 1195 if (bounds != null) { 1196 bounds.setBounds(x, y, x+w, y+h); 1197 transformBounds = true; 1198 } 1199 if (gr != null) { 1200 switch (token) { 1201 case FILL_RECT: 1202 setupFill(gr); 1203 gr.fillRect(x, y, w, h); 1204 break; 1205 case FILL_OVAL: 1206 setupFill(gr); 1207 gr.fillEllipse(x, y, w, h); 1208 break; 1209 case STROKE_RECT: 1210 setupStroke(gr); 1211 gr.drawRect(x, y, w, h); 1212 break; 1213 case STROKE_OVAL: 1214 setupStroke(gr); 1215 gr.drawEllipse(x, y, w, h); 1216 break; 1217 case CLEAR_RECT: 1218 gr.setCompositeMode(CompositeMode.CLEAR); 1219 gr.fillRect(x, y, w, h); 1220 gr.setCompositeMode(CompositeMode.SRC_OVER); 1221 break; 1222 } 1223 } 1224 break; 1225 } 1226 case STROKE_ROUND_RECT: 1227 strokeBounds = true; 1228 case FILL_ROUND_RECT: 1229 { 1230 float x = buf.getFloat(); 1231 float y = buf.getFloat(); 1232 float w = buf.getFloat(); 1233 float h = buf.getFloat(); 1234 float aw = buf.getFloat(); 1235 float ah = buf.getFloat(); 1236 if (bounds != null) { 1237 bounds.setBounds(x, y, x+w, y+h); 1238 transformBounds = true; 1239 } 1240 if (gr != null) { 1241 if (token == FILL_ROUND_RECT) { 1242 setupFill(gr); 1243 gr.fillRoundRect(x, y, w, h, aw, ah); 1244 } else { 1245 setupStroke(gr); 1246 gr.drawRoundRect(x, y, w, h, aw, ah); 1247 } 1248 } 1249 break; 1250 } 1251 case FILL_ARC: 1252 case STROKE_ARC: 1253 { 1254 float x = buf.getFloat(); 1255 float y = buf.getFloat(); 1256 float w = buf.getFloat(); 1257 float h = buf.getFloat(); 1258 float as = buf.getFloat(); 1259 float ae = buf.getFloat(); 1260 TEMP_ARC.setArc(x, y, w, h, as, ae, arctype); 1261 if (token == FILL_ARC) { 1262 if (bounds != null) { 1263 shapebounds(TEMP_ARC, bounds, transform); 1264 } 1265 if (gr != null) { 1266 setupFill(gr); 1267 gr.fill(TEMP_ARC); 1268 } 1269 } else { 1270 if (bounds != null) { 1271 strokebounds(getStroke(), TEMP_ARC, bounds, transform); 1272 } 1273 if (gr != null) { 1274 setupStroke(gr); 1275 gr.draw(TEMP_ARC); 1276 } 1277 } 1278 break; 1279 } 1280 case DRAW_IMAGE: 1281 case DRAW_SUBIMAGE: 1282 { 1283 float dx = buf.getFloat(); 1284 float dy = buf.getFloat(); 1285 float dw = buf.getFloat(); 1286 float dh = buf.getFloat(); 1287 Image img = (Image) buf.getObject(); 1288 float sx, sy, sw, sh; 1289 if (token == DRAW_IMAGE) { 1290 sx = sy = 0f; 1291 sw = img.getWidth(); 1292 sh = img.getHeight(); 1293 } else { 1294 sx = buf.getFloat(); 1295 sy = buf.getFloat(); 1296 sw = buf.getFloat(); 1297 sh = buf.getFloat(); 1298 float ps = img.getPixelScale(); 1299 if (ps != 1.0f) { 1300 sx *= ps; 1301 sy *= ps; 1302 sw *= ps; 1303 sh *= ps; 1304 } 1305 } 1306 if (bounds != null) { 1307 bounds.setBounds(dx, dy, dx+dw, dy+dh); 1308 transformBounds = true; 1309 } 1310 if (gr != null) { 1311 ResourceFactory factory = gr.getResourceFactory(); 1312 Texture tex = 1313 factory.getCachedTexture(img, Texture.WrapMode.CLAMP_TO_EDGE); 1314 gr.drawTexture(tex, 1315 dx, dy, dx+dw, dy+dh, 1316 sx, sy, sx+sw, sy+sh); 1317 tex.unlock(); 1318 } 1319 break; 1320 } 1321 case FILL_TEXT: 1322 case STROKE_TEXT: 1323 { 1324 float x = buf.getFloat(); 1325 float y = buf.getFloat(); 1326 float maxWidth = buf.getFloat(); 1327 boolean rtl = buf.getBoolean(); 1328 String string = (String) buf.getObject(); 1329 int dir = rtl ? PrismTextLayout.DIRECTION_RTL : 1330 PrismTextLayout.DIRECTION_LTR; 1331 1332 textLayout.setContent(string, pgfont); 1333 textLayout.setAlignment(align); 1334 textLayout.setDirection(dir); 1335 float xAlign = 0, yAlign = 0; 1336 BaseBounds layoutBounds = textLayout.getBounds(); 1337 float layoutWidth = layoutBounds.getWidth(); 1338 float layoutHeight = layoutBounds.getHeight(); 1339 switch (align) { 1340 case ALIGN_RIGHT: xAlign = layoutWidth; break; 1341 case ALIGN_CENTER: xAlign = layoutWidth / 2; break; 1342 } 1343 switch (baseline) { 1344 case BASE_ALPHABETIC: yAlign = -layoutBounds.getMinY(); break; 1345 case BASE_MIDDLE: yAlign = layoutHeight / 2; break; 1346 case BASE_BOTTOM: yAlign = layoutHeight; break; 1347 } 1348 float scaleX = 1; 1349 float layoutX = 0; 1350 float layoutY = y - yAlign; 1351 if (maxWidth > 0.0 && layoutWidth > maxWidth) { 1352 float sx = maxWidth / layoutWidth; 1353 if (rtl) { 1354 layoutX = -((x + maxWidth) / sx - xAlign); 1355 scaleX = -sx; 1356 } else { 1357 layoutX = x / sx - xAlign; 1358 scaleX = sx; 1359 } 1360 } else { 1361 if (rtl) { 1362 layoutX = -(x - xAlign + layoutWidth); 1363 scaleX = -1; 1364 } else { 1365 layoutX = x - xAlign; 1366 } 1367 } 1368 if (bounds != null) { 1369 computeTextLayoutBounds(bounds, transform, scaleX, layoutX, layoutY, token); 1370 } 1371 if (gr != null) { 1372 if (scaleX != 1) { 1373 gr.scale(scaleX, 1); 1374 } 1375 ngtext.setLayoutLocation(-layoutX, -layoutY); 1376 if (token == FILL_TEXT) { 1377 ngtext.setMode(NGShape.Mode.FILL); 1378 ngtext.setFillPaint(fillPaint); 1379 if (fillPaint.isProportional()) { 1380 RectBounds textBounds = new RectBounds(); 1381 computeTextLayoutBounds(textBounds, BaseTransform.IDENTITY_TRANSFORM, 1382 1, layoutX, layoutY, token); 1383 ngtext.setContentBounds(textBounds); 1384 } 1385 } else { 1386 if (strokePaint.isProportional()) { 1387 RectBounds textBounds = new RectBounds(); 1388 computeTextLayoutBounds(textBounds, BaseTransform.IDENTITY_TRANSFORM, 1389 1, layoutX, layoutY, token); 1390 ngtext.setContentBounds(textBounds); 1391 } 1392 ngtext.setMode(NGShape.Mode.STROKE); 1393 ngtext.setDrawStroke(getStroke()); 1394 ngtext.setDrawPaint(strokePaint); 1395 } 1396 ngtext.setFont(pgfont); 1397 ngtext.setGlyphs(textLayout.getRuns()); 1398 ngtext.renderContent(gr); 1399 } 1400 break; 1401 } 1402 default: 1403 throw new InternalError("Unrecognized PGCanvas rendering token: "+token); 1404 } 1405 if (bounds != null) { 1406 if (strokeBounds) { 1407 BasicStroke s = getStroke(); 1408 if (s.getType() != BasicStroke.TYPE_INNER) { 1409 float lw = s.getLineWidth(); 1410 if (s.getType() == BasicStroke.TYPE_CENTERED) { 1411 lw /= 2f; 1412 } 1413 bounds.grow(lw, lw); 1414 } 1415 } 1416 if (transformBounds) { 1417 txBounds(bounds, transform); 1418 } 1419 } 1420 } 1421 1422 void computeTextLayoutBounds(RectBounds bounds, BaseTransform transform, 1423 float scaleX, float layoutX, float layoutY, 1424 int token) 1425 { 1426 textLayout.getBounds(null, bounds); 1427 TEMP_TX.setTransform(transform); 1428 TEMP_TX.scale(scaleX, 1); 1429 TEMP_TX.translate(layoutX, layoutY); 1430 TEMP_TX.transform(bounds, bounds); 1431 if (token == STROKE_TEXT) { 1432 int flag = PrismTextLayout.TYPE_TEXT; 1433 Shape textShape = textLayout.getShape(flag, null); 1434 RectBounds shapeBounds = new RectBounds(); 1435 strokebounds(getStroke(), textShape, shapeBounds, TEMP_TX); 1436 bounds.unionWith(shapeBounds); 1437 } 1438 } 1439 1440 static void txBounds(RectBounds bounds, BaseTransform transform) { 1441 switch (transform.getType()) { 1442 case BaseTransform.TYPE_IDENTITY: 1443 break; 1444 case BaseTransform.TYPE_TRANSLATION: 1445 float tx = (float) transform.getMxt(); 1446 float ty = (float) transform.getMyt(); 1447 bounds.setBounds(bounds.getMinX() + tx, bounds.getMinY() + ty, 1448 bounds.getMaxX() + tx, bounds.getMaxY() + ty); 1449 break; 1450 default: 1451 BaseBounds txbounds = transform.transform(bounds, bounds); 1452 if (txbounds != bounds) { 1453 bounds.setBounds(txbounds.getMinX(), txbounds.getMinY(), 1454 txbounds.getMaxX(), txbounds.getMaxY()); 1455 } 1456 break; 1457 } 1458 } 1459 1460 static void inverseTxBounds(RectBounds bounds, BaseTransform transform) { 1461 switch (transform.getType()) { 1462 case BaseTransform.TYPE_IDENTITY: 1463 break; 1464 case BaseTransform.TYPE_TRANSLATION: 1465 float tx = (float) transform.getMxt(); 1466 float ty = (float) transform.getMyt(); 1467 bounds.setBounds(bounds.getMinX() - tx, bounds.getMinY() - ty, 1468 bounds.getMaxX() - tx, bounds.getMaxY() - ty); 1469 break; 1470 default: 1471 try { 1472 BaseBounds txbounds = transform.inverseTransform(bounds, bounds); 1473 if (txbounds != bounds) { 1474 bounds.setBounds(txbounds.getMinX(), txbounds.getMinY(), 1475 txbounds.getMaxX(), txbounds.getMaxY()); 1476 } 1477 } catch (NoninvertibleTransformException e) { 1478 bounds.makeEmpty(); 1479 } 1480 break; 1481 } 1482 } 1483 1484 public void updateBounds(float w, float h) { 1485 this.tw = (int) Math.ceil(w * highestPixelScale); 1486 this.th = (int) Math.ceil(h * highestPixelScale); 1487 geometryChanged(); 1488 } 1489 1490 // Returns true if we are falling behind in rendering (i.e. we 1491 // have unrendered data at the time of the synch. This tells 1492 // the FX layer that it should consider emitting a RESET if it 1493 // detects a full-canvas clear command even if it looks like it 1494 // is superfluous. 1495 public boolean updateRendering(GrowableDataBuffer buf) { 1496 if (buf.isEmpty()) { 1497 GrowableDataBuffer.returnBuffer(buf); 1498 return (this.thebuf != null); 1499 } 1500 boolean reset = (buf.peekByte(0) == RESET); 1501 GrowableDataBuffer retbuf; 1502 if (reset || this.thebuf == null) { 1503 retbuf = this.thebuf; 1504 this.thebuf = buf; 1505 } else { 1506 this.thebuf.append(buf); 1507 retbuf = buf; 1508 } 1509 geometryChanged(); 1510 if (retbuf != null) { 1511 GrowableDataBuffer.returnBuffer(retbuf); 1512 return true; 1513 } 1514 return false; 1515 } 1516 1517 class RenderInput extends Effect { 1518 float x, y, w, h; 1519 int token; 1520 GrowableDataBuffer buf; 1521 Affine2D savedBoundsTx = new Affine2D(); 1522 1523 public RenderInput(int token, GrowableDataBuffer buf, 1524 BaseTransform boundsTx, RectBounds rb) 1525 { 1526 this.token = token; 1527 this.buf = buf; 1528 savedBoundsTx.setTransform(boundsTx); 1529 this.x = rb.getMinX(); 1530 this.y = rb.getMinY(); 1531 this.w = rb.getWidth(); 1532 this.h = rb.getHeight(); 1533 } 1534 1535 @Override 1536 public ImageData filter(FilterContext fctx, BaseTransform transform, 1537 Rectangle outputClip, Object renderHelper, 1538 Effect defaultInput) 1539 { 1540 BaseBounds bounds = getBounds(transform, defaultInput); 1541 if (outputClip != null) { 1542 bounds.intersectWith(outputClip); 1543 } 1544 Rectangle r = new Rectangle(bounds); 1545 if (r.width < 1) r.width = 1; 1546 if (r.height < 1) r.height = 1; 1547 PrDrawable ret = (PrDrawable) Effect.getCompatibleImage(fctx, r.width, r.height); 1548 if (ret != null) { 1549 Graphics g = ret.createGraphics(); 1550 g.setExtraAlpha(globalAlpha); 1551 g.translate(-r.x, -r.y); 1552 if (transform != null) { 1553 g.transform(transform); 1554 } 1555 buf.restore(); 1556 handleRenderOp(token, buf, g, null); 1557 } 1558 return new ImageData(fctx, ret, r); 1559 } 1560 1561 @Override 1562 public AccelType getAccelType(FilterContext fctx) { 1563 throw new UnsupportedOperationException("Not supported yet."); 1564 } 1565 1566 @Override 1567 public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) { 1568 RectBounds ret = new RectBounds(x, y, x + w, y + h); 1569 if (!transform.equals(savedBoundsTx)) { 1570 inverseTxBounds(ret, savedBoundsTx); 1571 txBounds(ret, transform); 1572 } 1573 return ret; 1574 } 1575 1576 @Override 1577 public boolean reducesOpaquePixels() { 1578 return false; 1579 } 1580 1581 @Override 1582 public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) { 1583 return null; // Never called 1584 } 1585 1586 } 1587 1588 static class MyBlend extends Blend { 1589 public MyBlend(Mode mode, Effect bottomInput, Effect topInput) { 1590 super(mode, bottomInput, topInput); 1591 } 1592 1593 @Override 1594 public Rectangle getResultBounds(BaseTransform transform, 1595 Rectangle outputClip, 1596 ImageData... inputDatas) 1597 { 1598 // There is a bug in the ImageData class that means that the 1599 // outputClip will not be taken into account, so we override 1600 // here and apply it ourselves. 1601 Rectangle r = super.getResultBounds(transform, outputClip, inputDatas); 1602 r.intersectWith(outputClip); 1603 return r; 1604 } 1605 } 1606 1607 static class EffectInput extends Effect { 1608 RTTexture tex; 1609 float pixelscale; 1610 1611 EffectInput(RTTexture tex) { 1612 this.tex = tex; 1613 this.pixelscale = 1.0f; 1614 } 1615 1616 public void setPixelScale(float scale) { 1617 this.pixelscale = scale; 1618 } 1619 1620 @Override 1621 public ImageData filter(FilterContext fctx, BaseTransform transform, 1622 Rectangle outputClip, Object renderHelper, 1623 Effect defaultInput) 1624 { 1625 Filterable f = PrDrawable.create(fctx, tex); 1626 Rectangle r = new Rectangle(tex.getContentWidth(), tex.getContentHeight()); 1627 ImageData id = new ImageData(fctx, f, r); 1628 if (pixelscale != 1.0f || !transform.isIdentity()) { 1629 Affine2D a2d = new Affine2D(); 1630 a2d.scale(1.0f / pixelscale, 1.0f / pixelscale); 1631 a2d.concatenate(transform); 1632 id = id.transform(a2d); 1633 } 1634 return id; 1635 } 1636 1637 @Override 1638 public AccelType getAccelType(FilterContext fctx) { 1639 throw new UnsupportedOperationException("Not supported yet."); 1640 } 1641 1642 @Override 1643 public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) { 1644 Rectangle r = new Rectangle(tex.getContentWidth(), tex.getContentHeight()); 1645 return transformBounds(transform, new RectBounds(r)); 1646 } 1647 1648 @Override 1649 public boolean reducesOpaquePixels() { 1650 return false; 1651 } 1652 1653 @Override 1654 public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) { 1655 return null; // Never called 1656 } 1657 } 1658 }