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