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