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