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