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 gr.setCompositeMode(CompositeMode.SRC_OVER); 839 break; 840 } 841 case PUT_ARGBPRE_BUF: 842 { 843 float dx1 = buf.getInt(); 844 float dy1 = buf.getInt(); 845 int w = buf.getInt(); 846 int h = buf.getInt(); 847 byte[] data = (byte[]) buf.getObject(); 848 Image img = Image.fromByteBgraPreData(data, w, h); 849 Graphics gr = cv.g; 850 ResourceFactory factory = gr.getResourceFactory(); 851 Texture tex = 852 factory.getCachedTexture(img, Texture.WrapMode.CLAMP_TO_EDGE); 853 gr.setTransform(BaseTransform.IDENTITY_TRANSFORM); 854 gr.setCompositeMode(CompositeMode.SRC); 855 float dx2 = dx1 + w; 856 float dy2 = dy1 + h; 857 dx1 *= highestPixelScale; 858 dy1 *= highestPixelScale; 859 dx2 *= highestPixelScale; 860 dy2 *= highestPixelScale; 861 gr.drawTexture(tex, 862 dx1, dy1, dx2, dy2, 863 0, 0, w, h); 864 tex.contentsNotUseful(); 865 tex.unlock(); 866 gr.setCompositeMode(CompositeMode.SRC_OVER); 867 break; 868 } 869 case TRANSFORM: 870 { 871 double mxx = buf.getDouble() * highestPixelScale; 872 double mxy = buf.getDouble() * highestPixelScale; 873 double mxt = buf.getDouble() * highestPixelScale; 874 double myx = buf.getDouble() * highestPixelScale; 875 double myy = buf.getDouble() * highestPixelScale; 876 double myt = buf.getDouble() * highestPixelScale; 877 transform.setTransform(mxx, myx, mxy, myy, mxt, myt); 878 inversedirty = true; 879 break; 880 } 881 case GLOBAL_ALPHA: 882 globalAlpha = buf.getFloat(); 883 break; 884 case FILL_RULE: 885 if (buf.getByte() == FILL_RULE_NON_ZERO) { 886 path.setWindingRule(Path2D.WIND_NON_ZERO); 887 } else { 888 path.setWindingRule(Path2D.WIND_EVEN_ODD); 889 } 890 break; 891 case COMP_MODE: 892 blendmode = (Blend.Mode)buf.getObject(); 893 break; 894 case FILL_PAINT: 895 fillPaint = (Paint) buf.getObject(); 896 break; 897 case STROKE_PAINT: 898 strokePaint = (Paint) buf.getObject(); 899 break; 900 case LINE_WIDTH: 901 linewidth = buf.getFloat(); 902 stroke = null; 903 break; 904 case LINE_CAP: 905 linecap = prcaps[buf.getUByte()]; 906 stroke = null; 907 break; 908 case LINE_JOIN: 909 linejoin = prjoins[buf.getUByte()]; 910 stroke = null; 911 break; 912 case MITER_LIMIT: 913 miterlimit = buf.getFloat(); 914 stroke = null; 915 break; 916 case FONT: 917 { 918 pgfont = (PGFont) buf.getObject(); 919 break; 920 } 921 case TEXT_ALIGN: 922 align = buf.getUByte(); 923 break; 924 case TEXT_BASELINE: 925 baseline = prbases[buf.getUByte()]; 926 break; 927 case FX_APPLY_EFFECT: 928 { 929 Effect e = (Effect) buf.getObject(); 930 RenderBuf dest = clipStack.isEmpty() ? cv : temp; 931 BaseTransform tx; 932 if (highestPixelScale != 1.0f) { 933 TEMP_TX.setToScale(highestPixelScale, highestPixelScale); 934 tx = TEMP_TX; 935 cv.input.setPixelScale(highestPixelScale); 936 } else { 937 tx = BaseTransform.IDENTITY_TRANSFORM; 938 } 939 applyEffectOnAintoC(cv.input, e, 940 tx, null, 941 CompositeMode.SRC, dest); 942 cv.input.setPixelScale(1.0f); 943 if (dest != cv) { 944 blendAthruBintoC(dest, Mode.SRC_IN, clip, 945 null, CompositeMode.SRC, cv); 946 } 947 break; 948 } 949 case EFFECT: 950 effect = (Effect) buf.getObject(); 951 break; 952 case FILL_PATH: 953 case STROKE_PATH: 954 case STROKE_LINE: 955 case FILL_RECT: 956 case CLEAR_RECT: 957 case STROKE_RECT: 958 case FILL_OVAL: 959 case STROKE_OVAL: 960 case FILL_ROUND_RECT: 961 case STROKE_ROUND_RECT: 962 case FILL_ARC: 963 case STROKE_ARC: 964 case DRAW_IMAGE: 965 case DRAW_SUBIMAGE: 966 case FILL_TEXT: 967 case STROKE_TEXT: 968 { 969 RenderBuf dest; 970 boolean tempvalidated; 971 boolean clipvalidated; 972 if (!clipStack.isEmpty()) { 973 initClip(); 974 clipvalidated = true; 975 temp.validate(cv.g, tw, th); 976 tempvalidated = true; 977 dest = temp; 978 } else if (blendmode != Blend.Mode.SRC_OVER) { 979 clipvalidated = false; 980 temp.validate(cv.g, tw, th); 981 tempvalidated = true; 982 dest = temp; 983 } else { 984 clipvalidated = tempvalidated = false; 985 dest = cv; 986 } 987 if (effect != null) { 988 buf.save(); 989 handleRenderOp(token, buf, null, TEMP_RECTBOUNDS); 990 RenderInput ri = 991 new RenderInput(token, buf, transform, TEMP_RECTBOUNDS); 992 // If we are rendering to cv then we need the results of 993 // the effect to be applied "SRC_OVER" onto the canvas. 994 // If we are rendering to temp then either SRC or SRC_OVER 995 // would work since we know it would have been freshly 996 // erased above, but using the more common SRC_OVER may save 997 // having to update the hardware blend equations. 998 Rectangle resultBounds = 999 applyEffectOnAintoC(ri, effect, 1000 transform, null, 1001 CompositeMode.SRC_OVER, dest); 1002 if (dest != cv) { 1003 TEMP_RECTBOUNDS.setBounds(resultBounds.x, resultBounds.y, 1004 resultBounds.x + resultBounds.width, 1005 resultBounds.y + resultBounds.height); 1006 } 1007 } else { 1008 Graphics g = dest.g; 1009 g.setExtraAlpha(globalAlpha); 1010 g.setTransform(transform); 1011 // If we are not rendering directly to the canvas then 1012 // we need to save the bounds for the later stages. 1013 RectBounds optSaveBounds = 1014 (dest != cv) ? TEMP_RECTBOUNDS : null; 1015 handleRenderOp(token, buf, g, optSaveBounds); 1016 } 1017 if (!clipStack.isEmpty()) { 1018 CompositeMode compmode; 1019 if (blendmode == Blend.Mode.SRC_OVER) { 1020 // For the SRC_OVER case we can point the clip 1021 // operation directly to the screen with the Prism 1022 // SRC_OVER composite mode. 1023 dest = cv; 1024 compmode = CompositeMode.SRC_OVER; 1025 } else { 1026 // Here we are blending the rendered pixels that 1027 // were output to the temp buffer above against the 1028 // pixels of the canvas and we need to put them 1029 // back into the temp buffer. We must use SRC 1030 // mode here so that the erased (or reduced) pixels 1031 // actually get reduced to their new alpha. 1032 // assert: dest == temp; 1033 compmode = CompositeMode.SRC; 1034 } 1035 blendAthruBintoC(temp, Mode.SRC_IN, clip, 1036 TEMP_RECTBOUNDS, compmode, dest); 1037 } 1038 if (blendmode != Blend.Mode.SRC_OVER) { 1039 // We always use SRC mode here because the results of 1040 // the blend operation are final and must replace 1041 // the associated pixel in the canvas with no further 1042 // blending math. 1043 blendAthruBintoC(temp, blendmode, cv, 1044 TEMP_RECTBOUNDS, CompositeMode.SRC, cv); 1045 } 1046 if (clipvalidated) { 1047 clip.tex.unlock(); 1048 } 1049 if (tempvalidated) { 1050 temp.tex.unlock(); 1051 } 1052 break; 1053 } 1054 default: 1055 throw new InternalError("Unrecognized PGCanvas token: "+token); 1056 } 1057 } 1058 } 1059 1060 /** 1061 * Calculate bounds and/or render one single rendering operation. 1062 * All of the data for the rendering operation should be consumed 1063 * so that the buffer is left at the next token in the stream. 1064 * 1065 * @param token the stream token for the rendering op 1066 * @param buf the GrowableDataBuffer to get rendering info from 1067 * @param gr the Graphics to render to, if not null 1068 * @param bounds the RectBounds to accumulate bounds into, if not null 1069 */ 1070 public void handleRenderOp(int token, GrowableDataBuffer buf, 1071 Graphics gr, RectBounds bounds) 1072 { 1073 boolean strokeBounds = false; 1074 boolean transformBounds = false; 1075 switch (token) { 1076 case FILL_PATH: 1077 { 1078 if (bounds != null) { 1079 shapebounds(path, bounds, BaseTransform.IDENTITY_TRANSFORM); 1080 } 1081 if (gr != null) { 1082 setupFill(gr); 1083 gr.fill(untransformedPath); 1084 } 1085 break; 1086 } 1087 case STROKE_PATH: 1088 { 1089 if (bounds != null) { 1090 strokebounds(getStroke(), untransformedPath, bounds, transform); 1091 } 1092 if (gr != null) { 1093 setupStroke(gr); 1094 gr.draw(untransformedPath); 1095 } 1096 break; 1097 } 1098 case STROKE_LINE: 1099 { 1100 float x1 = buf.getFloat(); 1101 float y1 = buf.getFloat(); 1102 float x2 = buf.getFloat(); 1103 float y2 = buf.getFloat(); 1104 if (bounds != null) { 1105 bounds.setBoundsAndSort(x1, y1, x2, y2); 1106 strokeBounds = true; 1107 transformBounds = true; 1108 } 1109 if (gr != null) { 1110 setupStroke(gr); 1111 gr.drawLine(x1, y1, x2, y2); 1112 } 1113 break; 1114 } 1115 case STROKE_RECT: 1116 case STROKE_OVAL: 1117 strokeBounds = true; 1118 case FILL_RECT: 1119 case CLEAR_RECT: 1120 case FILL_OVAL: 1121 { 1122 float x = buf.getFloat(); 1123 float y = buf.getFloat(); 1124 float w = buf.getFloat(); 1125 float h = buf.getFloat(); 1126 if (bounds != null) { 1127 bounds.setBounds(x, y, x+w, y+h); 1128 transformBounds = true; 1129 } 1130 if (gr != null) { 1131 switch (token) { 1132 case FILL_RECT: 1133 setupFill(gr); 1134 gr.fillRect(x, y, w, h); 1135 break; 1136 case FILL_OVAL: 1137 setupFill(gr); 1138 gr.fillEllipse(x, y, w, h); 1139 break; 1140 case STROKE_RECT: 1141 setupStroke(gr); 1142 gr.drawRect(x, y, w, h); 1143 break; 1144 case STROKE_OVAL: 1145 setupStroke(gr); 1146 gr.drawEllipse(x, y, w, h); 1147 break; 1148 case CLEAR_RECT: 1149 gr.setPaint(Color.TRANSPARENT); 1150 gr.setCompositeMode(CompositeMode.SRC); 1151 gr.fillRect(x, y, w, h); 1152 gr.setCompositeMode(CompositeMode.SRC_OVER); 1153 break; 1154 } 1155 } 1156 break; 1157 } 1158 case STROKE_ROUND_RECT: 1159 strokeBounds = true; 1160 case FILL_ROUND_RECT: 1161 { 1162 float x = buf.getFloat(); 1163 float y = buf.getFloat(); 1164 float w = buf.getFloat(); 1165 float h = buf.getFloat(); 1166 float aw = buf.getFloat(); 1167 float ah = buf.getFloat(); 1168 if (bounds != null) { 1169 bounds.setBounds(x, y, x+w, y+h); 1170 transformBounds = true; 1171 } 1172 if (gr != null) { 1173 if (token == FILL_ROUND_RECT) { 1174 setupFill(gr); 1175 gr.fillRoundRect(x, y, w, h, aw, ah); 1176 } else { 1177 setupStroke(gr); 1178 gr.drawRoundRect(x, y, w, h, aw, ah); 1179 } 1180 } 1181 break; 1182 } 1183 case FILL_ARC: 1184 case STROKE_ARC: 1185 { 1186 float x = buf.getFloat(); 1187 float y = buf.getFloat(); 1188 float w = buf.getFloat(); 1189 float h = buf.getFloat(); 1190 float as = buf.getFloat(); 1191 float ae = buf.getFloat(); 1192 TEMP_ARC.setArc(x, y, w, h, as, ae, arctype); 1193 if (token == FILL_ARC) { 1194 if (bounds != null) { 1195 shapebounds(TEMP_ARC, bounds, transform); 1196 } 1197 if (gr != null) { 1198 setupFill(gr); 1199 gr.fill(TEMP_ARC); 1200 } 1201 } else { 1202 if (bounds != null) { 1203 strokebounds(getStroke(), TEMP_ARC, bounds, transform); 1204 } 1205 if (gr != null) { 1206 setupStroke(gr); 1207 gr.draw(TEMP_ARC); 1208 } 1209 } 1210 break; 1211 } 1212 case DRAW_IMAGE: 1213 case DRAW_SUBIMAGE: 1214 { 1215 float dx = buf.getFloat(); 1216 float dy = buf.getFloat(); 1217 float dw = buf.getFloat(); 1218 float dh = buf.getFloat(); 1219 Image img = (Image) buf.getObject(); 1220 float sx, sy, sw, sh; 1221 if (token == DRAW_IMAGE) { 1222 sx = sy = 0f; 1223 sw = img.getWidth(); 1224 sh = img.getHeight(); 1225 } else { 1226 sx = buf.getFloat(); 1227 sy = buf.getFloat(); 1228 sw = buf.getFloat(); 1229 sh = buf.getFloat(); 1230 float ps = img.getPixelScale(); 1231 if (ps != 1.0f) { 1232 sx *= ps; 1233 sy *= ps; 1234 sw *= ps; 1235 sh *= ps; 1236 } 1237 } 1238 if (bounds != null) { 1239 bounds.setBounds(dx, dy, dx+dw, dy+dh); 1240 transformBounds = true; 1241 } 1242 if (gr != null) { 1243 ResourceFactory factory = gr.getResourceFactory(); 1244 Texture tex = 1245 factory.getCachedTexture(img, Texture.WrapMode.CLAMP_TO_EDGE); 1246 gr.drawTexture(tex, 1247 dx, dy, dx+dw, dy+dh, 1248 sx, sy, sx+sw, sy+sh); 1249 tex.unlock(); 1250 } 1251 break; 1252 } 1253 case FILL_TEXT: 1254 case STROKE_TEXT: 1255 { 1256 float x = buf.getFloat(); 1257 float y = buf.getFloat(); 1258 float maxWidth = buf.getFloat(); 1259 boolean rtl = buf.getBoolean(); 1260 String string = (String) buf.getObject(); 1261 int dir = rtl ? PrismTextLayout.DIRECTION_RTL : 1262 PrismTextLayout.DIRECTION_LTR; 1263 1264 textLayout.setContent(string, pgfont); 1265 textLayout.setAlignment(align); 1266 textLayout.setDirection(dir); 1267 float xAlign = 0, yAlign = 0; 1268 BaseBounds layoutBounds = textLayout.getBounds(); 1269 float layoutWidth = layoutBounds.getWidth(); 1270 float layoutHeight = layoutBounds.getHeight(); 1271 switch (align) { 1272 case ALIGN_RIGHT: xAlign = layoutWidth; break; 1273 case ALIGN_CENTER: xAlign = layoutWidth / 2; break; 1274 } 1275 switch (baseline) { 1276 case BASE_ALPHABETIC: yAlign = -layoutBounds.getMinY(); break; 1277 case BASE_MIDDLE: yAlign = layoutHeight / 2; break; 1278 case BASE_BOTTOM: yAlign = layoutHeight; break; 1279 } 1280 float scaleX = 1; 1281 float layoutX = 0; 1282 float layoutY = y - yAlign; 1283 if (maxWidth > 0.0 && layoutWidth > maxWidth) { 1284 float sx = maxWidth / layoutWidth; 1285 if (rtl) { 1286 layoutX = -((x + maxWidth) / sx - xAlign); 1287 scaleX = -sx; 1288 } else { 1289 layoutX = x / sx - xAlign; 1290 scaleX = sx; 1291 } 1292 } else { 1293 if (rtl) { 1294 layoutX = -(x - xAlign + layoutWidth); 1295 scaleX = -1; 1296 } else { 1297 layoutX = x - xAlign; 1298 } 1299 } 1300 if (bounds != null) { 1301 computeTextLayoutBounds(bounds, transform, scaleX, layoutX, layoutY, token); 1302 } 1303 if (gr != null) { 1304 if (scaleX != 1) { 1305 gr.scale(scaleX, 1); 1306 } 1307 ngtext.setLayoutLocation(-layoutX, -layoutY); 1308 if (token == FILL_TEXT) { 1309 ngtext.setMode(NGShape.Mode.FILL); 1310 ngtext.setFillPaint(fillPaint); 1311 if (fillPaint.isProportional()) { 1312 RectBounds textBounds = new RectBounds(); 1313 computeTextLayoutBounds(textBounds, BaseTransform.IDENTITY_TRANSFORM, 1314 1, layoutX, layoutY, token); 1315 ngtext.setContentBounds(textBounds); 1316 } 1317 } else { 1318 if (strokePaint.isProportional()) { 1319 RectBounds textBounds = new RectBounds(); 1320 computeTextLayoutBounds(textBounds, BaseTransform.IDENTITY_TRANSFORM, 1321 1, layoutX, layoutY, token); 1322 ngtext.setContentBounds(textBounds); 1323 } 1324 ngtext.setMode(NGShape.Mode.STROKE); 1325 ngtext.setDrawStroke(getStroke()); 1326 ngtext.setDrawPaint(strokePaint); 1327 } 1328 ngtext.setFont(pgfont); 1329 ngtext.setGlyphs(textLayout.getRuns()); 1330 ngtext.renderContent(gr); 1331 } 1332 break; 1333 } 1334 default: 1335 throw new InternalError("Unrecognized PGCanvas rendering token: "+token); 1336 } 1337 if (bounds != null) { 1338 if (strokeBounds) { 1339 BasicStroke s = getStroke(); 1340 if (s.getType() != BasicStroke.TYPE_INNER) { 1341 float lw = s.getLineWidth(); 1342 if (s.getType() == BasicStroke.TYPE_CENTERED) { 1343 lw /= 2f; 1344 } 1345 bounds.grow(lw, lw); 1346 } 1347 } 1348 if (transformBounds) { 1349 txBounds(bounds, transform); 1350 } 1351 } 1352 } 1353 1354 void computeTextLayoutBounds(RectBounds bounds, BaseTransform transform, 1355 float scaleX, float layoutX, float layoutY, 1356 int token) 1357 { 1358 textLayout.getBounds(null, bounds); 1359 TEMP_TX.setTransform(transform); 1360 TEMP_TX.scale(scaleX, 1); 1361 TEMP_TX.translate(layoutX, layoutY); 1362 TEMP_TX.transform(bounds, bounds); 1363 if (token == STROKE_TEXT) { 1364 int flag = PrismTextLayout.TYPE_TEXT; 1365 Shape textShape = textLayout.getShape(flag, null); 1366 RectBounds shapeBounds = new RectBounds(); 1367 strokebounds(getStroke(), textShape, shapeBounds, TEMP_TX); 1368 bounds.unionWith(shapeBounds); 1369 } 1370 } 1371 1372 static void txBounds(RectBounds bounds, BaseTransform transform) { 1373 switch (transform.getType()) { 1374 case BaseTransform.TYPE_IDENTITY: 1375 break; 1376 case BaseTransform.TYPE_TRANSLATION: 1377 float tx = (float) transform.getMxt(); 1378 float ty = (float) transform.getMyt(); 1379 bounds.setBounds(bounds.getMinX() + tx, bounds.getMinY() + ty, 1380 bounds.getMaxX() + tx, bounds.getMaxY() + ty); 1381 break; 1382 default: 1383 BaseBounds txbounds = transform.transform(bounds, bounds); 1384 if (txbounds != bounds) { 1385 bounds.setBounds(txbounds.getMinX(), txbounds.getMinY(), 1386 txbounds.getMaxX(), txbounds.getMaxY()); 1387 } 1388 break; 1389 } 1390 } 1391 1392 static void inverseTxBounds(RectBounds bounds, BaseTransform transform) { 1393 switch (transform.getType()) { 1394 case BaseTransform.TYPE_IDENTITY: 1395 break; 1396 case BaseTransform.TYPE_TRANSLATION: 1397 float tx = (float) transform.getMxt(); 1398 float ty = (float) transform.getMyt(); 1399 bounds.setBounds(bounds.getMinX() - tx, bounds.getMinY() - ty, 1400 bounds.getMaxX() - tx, bounds.getMaxY() - ty); 1401 break; 1402 default: 1403 try { 1404 BaseBounds txbounds = transform.inverseTransform(bounds, bounds); 1405 if (txbounds != bounds) { 1406 bounds.setBounds(txbounds.getMinX(), txbounds.getMinY(), 1407 txbounds.getMaxX(), txbounds.getMaxY()); 1408 } 1409 } catch (NoninvertibleTransformException e) { 1410 bounds.makeEmpty(); 1411 } 1412 break; 1413 } 1414 } 1415 1416 public void updateBounds(float w, float h) { 1417 this.tw = (int) Math.ceil(w * highestPixelScale); 1418 this.th = (int) Math.ceil(h * highestPixelScale); 1419 geometryChanged(); 1420 } 1421 1422 // Returns true if we are falling behind in rendering (i.e. we 1423 // have unrendered data at the time of the synch. This tells 1424 // the FX layer that it should consider emitting a RESET if it 1425 // detects a full-canvas clear command even if it looks like it 1426 // is superfluous. 1427 public boolean updateRendering(GrowableDataBuffer buf) { 1428 if (buf.isEmpty()) { 1429 GrowableDataBuffer.returnBuffer(buf); 1430 return (this.thebuf != null); 1431 } 1432 boolean reset = (buf.peekByte(0) == RESET); 1433 GrowableDataBuffer retbuf; 1434 if (reset || this.thebuf == null) { 1435 retbuf = this.thebuf; 1436 this.thebuf = buf; 1437 } else { 1438 this.thebuf.append(buf); 1439 retbuf = buf; 1440 } 1441 geometryChanged(); 1442 if (retbuf != null) { 1443 GrowableDataBuffer.returnBuffer(retbuf); 1444 return true; 1445 } 1446 return false; 1447 } 1448 1449 class RenderInput extends Effect { 1450 float x, y, w, h; 1451 int token; 1452 GrowableDataBuffer buf; 1453 Affine2D savedBoundsTx = new Affine2D(); 1454 1455 public RenderInput(int token, GrowableDataBuffer buf, 1456 BaseTransform boundsTx, RectBounds rb) 1457 { 1458 this.token = token; 1459 this.buf = buf; 1460 savedBoundsTx.setTransform(boundsTx); 1461 this.x = rb.getMinX(); 1462 this.y = rb.getMinY(); 1463 this.w = rb.getWidth(); 1464 this.h = rb.getHeight(); 1465 } 1466 1467 @Override 1468 public ImageData filter(FilterContext fctx, BaseTransform transform, 1469 Rectangle outputClip, Object renderHelper, 1470 Effect defaultInput) 1471 { 1472 BaseBounds bounds = getBounds(transform, defaultInput); 1473 if (outputClip != null) { 1474 bounds.intersectWith(outputClip); 1475 } 1476 Rectangle r = new Rectangle(bounds); 1477 if (r.width < 1) r.width = 1; 1478 if (r.height < 1) r.height = 1; 1479 PrDrawable ret = (PrDrawable) Effect.getCompatibleImage(fctx, r.width, r.height); 1480 if (ret != null) { 1481 Graphics g = ret.createGraphics(); 1482 g.setExtraAlpha(globalAlpha); 1483 g.translate(-r.x, -r.y); 1484 if (transform != null) { 1485 g.transform(transform); 1486 } 1487 buf.restore(); 1488 handleRenderOp(token, buf, g, null); 1489 } 1490 return new ImageData(fctx, ret, r); 1491 } 1492 1493 @Override 1494 public AccelType getAccelType(FilterContext fctx) { 1495 throw new UnsupportedOperationException("Not supported yet."); 1496 } 1497 1498 @Override 1499 public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) { 1500 RectBounds ret = new RectBounds(x, y, x + w, y + h); 1501 if (!transform.equals(savedBoundsTx)) { 1502 inverseTxBounds(ret, savedBoundsTx); 1503 txBounds(ret, transform); 1504 } 1505 return ret; 1506 } 1507 1508 @Override 1509 public boolean reducesOpaquePixels() { 1510 return false; 1511 } 1512 1513 @Override 1514 public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) { 1515 return null; // Never called 1516 } 1517 1518 } 1519 1520 static class MyBlend extends Blend { 1521 public MyBlend(Mode mode, Effect bottomInput, Effect topInput) { 1522 super(mode, bottomInput, topInput); 1523 } 1524 1525 @Override 1526 public Rectangle getResultBounds(BaseTransform transform, 1527 Rectangle outputClip, 1528 ImageData... inputDatas) 1529 { 1530 // There is a bug in the ImageData class that means that the 1531 // outputClip will not be taken into account, so we override 1532 // here and apply it ourselves. 1533 Rectangle r = super.getResultBounds(transform, outputClip, inputDatas); 1534 r.intersectWith(outputClip); 1535 return r; 1536 } 1537 } 1538 1539 static class EffectInput extends Effect { 1540 RTTexture tex; 1541 float pixelscale; 1542 1543 EffectInput(RTTexture tex) { 1544 this.tex = tex; 1545 this.pixelscale = 1.0f; 1546 } 1547 1548 public void setPixelScale(float scale) { 1549 this.pixelscale = scale; 1550 } 1551 1552 @Override 1553 public ImageData filter(FilterContext fctx, BaseTransform transform, 1554 Rectangle outputClip, Object renderHelper, 1555 Effect defaultInput) 1556 { 1557 Filterable f = PrDrawable.create(fctx, tex); 1558 Rectangle r = new Rectangle(tex.getContentWidth(), tex.getContentHeight()); 1559 ImageData id = new ImageData(fctx, f, r); 1560 if (pixelscale != 1.0f || !transform.isIdentity()) { 1561 Affine2D a2d = new Affine2D(); 1562 a2d.scale(1.0f / pixelscale, 1.0f / pixelscale); 1563 a2d.concatenate(transform); 1564 id = id.transform(a2d); 1565 } 1566 return id; 1567 } 1568 1569 @Override 1570 public AccelType getAccelType(FilterContext fctx) { 1571 throw new UnsupportedOperationException("Not supported yet."); 1572 } 1573 1574 @Override 1575 public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) { 1576 Rectangle r = new Rectangle(tex.getContentWidth(), tex.getContentHeight()); 1577 return transformBounds(transform, new RectBounds(r)); 1578 } 1579 1580 @Override 1581 public boolean reducesOpaquePixels() { 1582 return false; 1583 } 1584 1585 @Override 1586 public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) { 1587 return null; // Never called 1588 } 1589 } 1590 }