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