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