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