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