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