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