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