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