1 /*
   2  * Copyright (c) 1996, 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 sun.java2d;
  27 
  28 import java.awt.Graphics;
  29 import java.awt.Graphics2D;
  30 import java.awt.RenderingHints;
  31 import java.awt.RenderingHints.Key;
  32 import java.awt.geom.Area;
  33 import java.awt.geom.AffineTransform;
  34 import java.awt.geom.NoninvertibleTransformException;
  35 import java.awt.AlphaComposite;
  36 import java.awt.BasicStroke;
  37 import java.awt.image.BufferedImage;
  38 import java.awt.image.BufferedImageOp;
  39 import java.awt.image.RenderedImage;
  40 import java.awt.image.renderable.RenderableImage;
  41 import java.awt.image.renderable.RenderContext;
  42 import java.awt.image.AffineTransformOp;
  43 import java.awt.image.Raster;
  44 import java.awt.image.WritableRaster;
  45 import java.awt.Image;
  46 import java.awt.Composite;
  47 import java.awt.Color;
  48 import java.awt.image.ColorModel;
  49 import java.awt.GraphicsConfiguration;
  50 import java.awt.Paint;
  51 import java.awt.GradientPaint;
  52 import java.awt.LinearGradientPaint;
  53 import java.awt.RadialGradientPaint;
  54 import java.awt.TexturePaint;
  55 import java.awt.geom.Rectangle2D;
  56 import java.awt.geom.PathIterator;
  57 import java.awt.geom.GeneralPath;
  58 import java.awt.Shape;
  59 import java.awt.Stroke;
  60 import java.awt.FontMetrics;
  61 import java.awt.Rectangle;
  62 import java.text.AttributedCharacterIterator;
  63 import java.awt.Font;
  64 import java.awt.image.ImageObserver;
  65 import java.awt.Transparency;
  66 import java.awt.font.GlyphVector;
  67 import java.awt.font.TextLayout;
  68 import sun.font.FontDesignMetrics;
  69 import sun.font.FontUtilities;
  70 import sun.java2d.pipe.PixelDrawPipe;
  71 import sun.java2d.pipe.PixelFillPipe;
  72 import sun.java2d.pipe.ShapeDrawPipe;
  73 import sun.java2d.pipe.ValidatePipe;
  74 import sun.java2d.pipe.ShapeSpanIterator;
  75 import sun.java2d.pipe.Region;
  76 import sun.java2d.pipe.TextPipe;
  77 import sun.java2d.pipe.DrawImagePipe;
  78 import sun.java2d.pipe.LoopPipe;
  79 import sun.java2d.loops.FontInfo;
  80 import sun.java2d.loops.RenderLoops;
  81 import sun.java2d.loops.CompositeType;
  82 import sun.java2d.loops.SurfaceType;
  83 import sun.java2d.loops.Blit;
  84 import sun.java2d.loops.MaskFill;
  85 import java.awt.font.FontRenderContext;
  86 import sun.java2d.loops.XORComposite;
  87 import sun.awt.ConstrainableGraphics;
  88 import sun.awt.SunHints;
  89 import java.util.Map;
  90 import java.util.Iterator;
  91 import sun.misc.PerformanceLogger;
  92 
  93 import javax.tools.annotation.GenerateNativeHeader;
  94 
  95 /**
  96  * This is a the master Graphics2D superclass for all of the Sun
  97  * Graphics implementations.  This class relies on subclasses to
  98  * manage the various device information, but provides an overall
  99  * general framework for performing all of the requests in the
 100  * Graphics and Graphics2D APIs.
 101  *
 102  * @author Jim Graham
 103  */
 104 /* No native methods here, but the constants are needed in the supporting JNI code */
 105 @GenerateNativeHeader
 106 public final class SunGraphics2D
 107     extends Graphics2D
 108     implements ConstrainableGraphics, Cloneable, DestSurfaceProvider
 109 {
 110     /*
 111      * Attribute States
 112      */
 113     /* Paint */
 114     public static final int PAINT_CUSTOM       = 6; /* Any other Paint object */
 115     public static final int PAINT_TEXTURE      = 5; /* Tiled Image */
 116     public static final int PAINT_RAD_GRADIENT = 4; /* Color RadialGradient */
 117     public static final int PAINT_LIN_GRADIENT = 3; /* Color LinearGradient */
 118     public static final int PAINT_GRADIENT     = 2; /* Color Gradient */
 119     public static final int PAINT_ALPHACOLOR   = 1; /* Non-opaque Color */
 120     public static final int PAINT_OPAQUECOLOR  = 0; /* Opaque Color */
 121 
 122     /* Composite*/
 123     public static final int COMP_CUSTOM = 3;/* Custom Composite */
 124     public static final int COMP_XOR    = 2;/* XOR Mode Composite */
 125     public static final int COMP_ALPHA  = 1;/* AlphaComposite */
 126     public static final int COMP_ISCOPY = 0;/* simple stores into destination,
 127                                              * i.e. Src, SrcOverNoEa, and other
 128                                              * alpha modes which replace
 129                                              * the destination.
 130                                              */
 131 
 132     /* Stroke */
 133     public static final int STROKE_CUSTOM = 3; /* custom Stroke */
 134     public static final int STROKE_WIDE   = 2; /* BasicStroke */
 135     public static final int STROKE_THINDASHED   = 1; /* BasicStroke */
 136     public static final int STROKE_THIN   = 0; /* BasicStroke */
 137 
 138     /* Transform */
 139     public static final int TRANSFORM_GENERIC = 4; /* any 3x2 */
 140     public static final int TRANSFORM_TRANSLATESCALE = 3; /* scale XY */
 141     public static final int TRANSFORM_ANY_TRANSLATE = 2; /* non-int translate */
 142     public static final int TRANSFORM_INT_TRANSLATE = 1; /* int translate */
 143     public static final int TRANSFORM_ISIDENT = 0; /* Identity */
 144 
 145     /* Clipping */
 146     public static final int CLIP_SHAPE       = 2; /* arbitrary clip */
 147     public static final int CLIP_RECTANGULAR = 1; /* rectangular clip */
 148     public static final int CLIP_DEVICE      = 0; /* no clipping set */
 149 
 150     /* The following fields are used when the current Paint is a Color. */
 151     public int eargb;  // ARGB value with ExtraAlpha baked in
 152     public int pixel;  // pixel value for eargb
 153 
 154     public SurfaceData surfaceData;
 155 
 156     public PixelDrawPipe drawpipe;
 157     public PixelFillPipe fillpipe;
 158     public DrawImagePipe imagepipe;
 159     public ShapeDrawPipe shapepipe;
 160     public TextPipe textpipe;
 161     public MaskFill alphafill;
 162 
 163     public RenderLoops loops;
 164 
 165     public CompositeType imageComp;     /* Image Transparency checked on fly */
 166 
 167     public int paintState;
 168     public int compositeState;
 169     public int strokeState;
 170     public int transformState;
 171     public int clipState;
 172 
 173     public Color foregroundColor;
 174     public Color backgroundColor;
 175 
 176     public AffineTransform transform;
 177     public int transX;
 178     public int transY;
 179 
 180     protected static final Stroke defaultStroke = new BasicStroke();
 181     protected static final Composite defaultComposite = AlphaComposite.SrcOver;
 182     private static final Font defaultFont =
 183         new Font(Font.DIALOG, Font.PLAIN, 12);
 184 
 185     public Paint paint;
 186     public Stroke stroke;
 187     public Composite composite;
 188     protected Font font;
 189     protected FontMetrics fontMetrics;
 190 
 191     public int renderHint;
 192     public int antialiasHint;
 193     public int textAntialiasHint;
 194     protected int fractionalMetricsHint;
 195 
 196     /* A gamma adjustment to the colour used in lcd text blitting */
 197     public int lcdTextContrast;
 198     private static int lcdTextContrastDefaultValue = 140;
 199 
 200     private int interpolationHint;      // raw value of rendering Hint
 201     public int strokeHint;
 202 
 203     public int interpolationType;       // algorithm choice based on
 204                                         // interpolation and render Hints
 205 
 206     public RenderingHints hints;
 207 
 208     public Region constrainClip;                // lightweight bounds
 209     public int constrainX;
 210     public int constrainY;
 211 
 212     public Region clipRegion;
 213     public Shape usrClip;
 214     protected Region devClip;           // Actual physical drawable
 215 
 216     // cached state for text rendering
 217     private boolean validFontInfo;
 218     private FontInfo fontInfo;
 219     private FontInfo glyphVectorFontInfo;
 220     private FontRenderContext glyphVectorFRC;
 221 
 222     private final static int slowTextTransformMask =
 223                             AffineTransform.TYPE_GENERAL_TRANSFORM
 224                         |   AffineTransform.TYPE_MASK_ROTATION
 225                         |   AffineTransform.TYPE_FLIP;
 226 
 227     static {
 228         if (PerformanceLogger.loggingEnabled()) {
 229             PerformanceLogger.setTime("SunGraphics2D static initialization");
 230         }
 231     }
 232 
 233     public SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f) {
 234         surfaceData = sd;
 235         foregroundColor = fg;
 236         backgroundColor = bg;
 237 
 238         transform = new AffineTransform();
 239         stroke = defaultStroke;
 240         composite = defaultComposite;
 241         paint = foregroundColor;
 242 
 243         imageComp = CompositeType.SrcOverNoEa;
 244 
 245         renderHint = SunHints.INTVAL_RENDER_DEFAULT;
 246         antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
 247         textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
 248         fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
 249         lcdTextContrast = lcdTextContrastDefaultValue;
 250         interpolationHint = -1;
 251         strokeHint = SunHints.INTVAL_STROKE_DEFAULT;
 252 
 253         interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
 254 
 255         validateColor();
 256 
 257         font = f;
 258         if (font == null) {
 259             font = defaultFont;
 260         }
 261 
 262         setDevClip(sd.getBounds());
 263         invalidatePipe();
 264     }
 265 
 266     protected Object clone() {
 267         try {
 268             SunGraphics2D g = (SunGraphics2D) super.clone();
 269             g.transform = new AffineTransform(this.transform);
 270             if (hints != null) {
 271                 g.hints = (RenderingHints) this.hints.clone();
 272             }
 273             /* FontInfos are re-used, so must be cloned too, if they
 274              * are valid, and be nulled out if invalid.
 275              * The implied trade-off is that there is more to be gained
 276              * from re-using these objects than is lost by having to
 277              * clone them when the SG2D is cloned.
 278              */
 279             if (this.fontInfo != null) {
 280                 if (this.validFontInfo) {
 281                     g.fontInfo = (FontInfo)this.fontInfo.clone();
 282                 } else {
 283                     g.fontInfo = null;
 284                 }
 285             }
 286             if (this.glyphVectorFontInfo != null) {
 287                 g.glyphVectorFontInfo =
 288                     (FontInfo)this.glyphVectorFontInfo.clone();
 289                 g.glyphVectorFRC = this.glyphVectorFRC;
 290             }
 291             //g.invalidatePipe();
 292             return g;
 293         } catch (CloneNotSupportedException e) {
 294         }
 295         return null;
 296     }
 297 
 298     /**
 299      * Create a new SunGraphics2D based on this one.
 300      */
 301     public Graphics create() {
 302         return (Graphics) clone();
 303     }
 304 
 305     public void setDevClip(int x, int y, int w, int h) {
 306         Region c = constrainClip;
 307         if (c == null) {
 308             devClip = Region.getInstanceXYWH(x, y, w, h);
 309         } else {
 310             devClip = c.getIntersectionXYWH(x, y, w, h);
 311         }
 312         validateCompClip();
 313     }
 314 
 315     public void setDevClip(Rectangle r) {
 316         setDevClip(r.x, r.y, r.width, r.height);
 317     }
 318 
 319     /**
 320      * Constrain rendering for lightweight objects.
 321      *
 322      * REMIND: This method will back off to the "workaround"
 323      * of using translate and clipRect if the Graphics
 324      * to be constrained has a complex transform.  The
 325      * drawback of the workaround is that the resulting
 326      * clip and device origin cannot be "enforced".
 327      *
 328      * @exception IllegalStateException If the Graphics
 329      * to be constrained has a complex transform.
 330      */
 331     public void constrain(int x, int y, int w, int h) {
 332         if ((x|y) != 0) {
 333             translate(x, y);
 334         }
 335         if (transformState >= TRANSFORM_TRANSLATESCALE) {
 336             clipRect(0, 0, w, h);
 337             return;
 338         }
 339         x = constrainX = transX;
 340         y = constrainY = transY;
 341         w = Region.dimAdd(x, w);
 342         h = Region.dimAdd(y, h);
 343         Region c = constrainClip;
 344         if (c == null) {
 345             c = Region.getInstanceXYXY(x, y, w, h);
 346         } else {
 347             c = c.getIntersectionXYXY(x, y, w, h);
 348             if (c == constrainClip) {
 349                 // Common case to ignore
 350                 return;
 351             }
 352         }
 353         constrainClip = c;
 354         if (!devClip.isInsideQuickCheck(c)) {
 355             devClip = devClip.getIntersection(c);
 356             validateCompClip();
 357         }
 358     }
 359 
 360     protected static ValidatePipe invalidpipe = new ValidatePipe();
 361 
 362     /*
 363      * Invalidate the pipeline
 364      */
 365     protected void invalidatePipe() {
 366         drawpipe = invalidpipe;
 367         fillpipe = invalidpipe;
 368         shapepipe = invalidpipe;
 369         textpipe = invalidpipe;
 370         imagepipe = invalidpipe;
 371         loops = null;
 372     }
 373 
 374     public void validatePipe() {
 375         /* This workaround is for the situation when we update the Pipelines
 376          * for invalid SurfaceData and run further code when the current
 377          * pipeline doesn't support the type of new SurfaceData created during
 378          * the current pipeline's work (in place of the invalid SurfaceData).
 379          * Usually SurfaceData and Pipelines are repaired (through revalidateAll)
 380          * and called again in the exception handlers */
 381 
 382         if (!surfaceData.isValid()) {
 383             throw new InvalidPipeException("attempt to validate Pipe with invalid SurfaceData");
 384         }
 385 
 386         surfaceData.validatePipe(this);
 387     }
 388 
 389     /*
 390      * Intersect two Shapes by the simplest method, attempting to produce
 391      * a simplified result.
 392      * The boolean arguments keep1 and keep2 specify whether or not
 393      * the first or second shapes can be modified during the operation
 394      * or whether that shape must be "kept" unmodified.
 395      */
 396     Shape intersectShapes(Shape s1, Shape s2, boolean keep1, boolean keep2) {
 397         if (s1 instanceof Rectangle && s2 instanceof Rectangle) {
 398             return ((Rectangle) s1).intersection((Rectangle) s2);
 399         }
 400         if (s1 instanceof Rectangle2D) {
 401             return intersectRectShape((Rectangle2D) s1, s2, keep1, keep2);
 402         } else if (s2 instanceof Rectangle2D) {
 403             return intersectRectShape((Rectangle2D) s2, s1, keep2, keep1);
 404         }
 405         return intersectByArea(s1, s2, keep1, keep2);
 406     }
 407 
 408     /*
 409      * Intersect a Rectangle with a Shape by the simplest method,
 410      * attempting to produce a simplified result.
 411      * The boolean arguments keep1 and keep2 specify whether or not
 412      * the first or second shapes can be modified during the operation
 413      * or whether that shape must be "kept" unmodified.
 414      */
 415     Shape intersectRectShape(Rectangle2D r, Shape s,
 416                              boolean keep1, boolean keep2) {
 417         if (s instanceof Rectangle2D) {
 418             Rectangle2D r2 = (Rectangle2D) s;
 419             Rectangle2D outrect;
 420             if (!keep1) {
 421                 outrect = r;
 422             } else if (!keep2) {
 423                 outrect = r2;
 424             } else {
 425                 outrect = new Rectangle2D.Float();
 426             }
 427             double x1 = Math.max(r.getX(), r2.getX());
 428             double x2 = Math.min(r.getX()  + r.getWidth(),
 429                                  r2.getX() + r2.getWidth());
 430             double y1 = Math.max(r.getY(), r2.getY());
 431             double y2 = Math.min(r.getY()  + r.getHeight(),
 432                                  r2.getY() + r2.getHeight());
 433 
 434             if (((x2 - x1) < 0) || ((y2 - y1) < 0))
 435                 // Width or height is negative. No intersection.
 436                 outrect.setFrameFromDiagonal(0, 0, 0, 0);
 437             else
 438                 outrect.setFrameFromDiagonal(x1, y1, x2, y2);
 439             return outrect;
 440         }
 441         if (r.contains(s.getBounds2D())) {
 442             if (keep2) {
 443                 s = cloneShape(s);
 444             }
 445             return s;
 446         }
 447         return intersectByArea(r, s, keep1, keep2);
 448     }
 449 
 450     protected static Shape cloneShape(Shape s) {
 451         return new GeneralPath(s);
 452     }
 453 
 454     /*
 455      * Intersect two Shapes using the Area class.  Presumably other
 456      * attempts at simpler intersection methods proved fruitless.
 457      * The boolean arguments keep1 and keep2 specify whether or not
 458      * the first or second shapes can be modified during the operation
 459      * or whether that shape must be "kept" unmodified.
 460      * @see #intersectShapes
 461      * @see #intersectRectShape
 462      */
 463     Shape intersectByArea(Shape s1, Shape s2, boolean keep1, boolean keep2) {
 464         Area a1, a2;
 465 
 466         // First see if we can find an overwriteable source shape
 467         // to use as our destination area to avoid duplication.
 468         if (!keep1 && (s1 instanceof Area)) {
 469             a1 = (Area) s1;
 470         } else if (!keep2 && (s2 instanceof Area)) {
 471             a1 = (Area) s2;
 472             s2 = s1;
 473         } else {
 474             a1 = new Area(s1);
 475         }
 476 
 477         if (s2 instanceof Area) {
 478             a2 = (Area) s2;
 479         } else {
 480             a2 = new Area(s2);
 481         }
 482 
 483         a1.intersect(a2);
 484         if (a1.isRectangular()) {
 485             return a1.getBounds();
 486         }
 487 
 488         return a1;
 489     }
 490 
 491     /*
 492      * Intersect usrClip bounds and device bounds to determine the composite
 493      * rendering boundaries.
 494      */
 495     public Region getCompClip() {
 496         if (!surfaceData.isValid()) {
 497             // revalidateAll() implicitly recalculcates the composite clip
 498             revalidateAll();
 499         }
 500 
 501         return clipRegion;
 502     }
 503 
 504     public Font getFont() {
 505         if (font == null) {
 506             font = defaultFont;
 507         }
 508         return font;
 509     }
 510 
 511     private static final double[] IDENT_MATRIX = {1, 0, 0, 1};
 512     private static final AffineTransform IDENT_ATX =
 513         new AffineTransform();
 514 
 515     private static final int MINALLOCATED = 8;
 516     private static final int TEXTARRSIZE = 17;
 517     private static double[][] textTxArr = new double[TEXTARRSIZE][];
 518     private static AffineTransform[] textAtArr =
 519         new AffineTransform[TEXTARRSIZE];
 520 
 521     static {
 522         for (int i=MINALLOCATED;i<TEXTARRSIZE;i++) {
 523           textTxArr[i] = new double [] {i, 0, 0, i};
 524           textAtArr[i] = new AffineTransform( textTxArr[i]);
 525         }
 526     }
 527 
 528     // cached state for various draw[String,Char,Byte] optimizations
 529     public FontInfo checkFontInfo(FontInfo info, Font font,
 530                                   FontRenderContext frc) {
 531         /* Do not create a FontInfo object as part of construction of an
 532          * SG2D as its possible it may never be needed - ie if no text
 533          * is drawn using this SG2D.
 534          */
 535         if (info == null) {
 536             info = new FontInfo();
 537         }
 538 
 539         float ptSize = font.getSize2D();
 540         int txFontType;
 541         AffineTransform devAt, textAt=null;
 542         if (font.isTransformed()) {
 543             textAt = font.getTransform();
 544             textAt.scale(ptSize, ptSize);
 545             txFontType = textAt.getType();
 546             info.originX = (float)textAt.getTranslateX();
 547             info.originY = (float)textAt.getTranslateY();
 548             textAt.translate(-info.originX, -info.originY);
 549             if (transformState >= TRANSFORM_TRANSLATESCALE) {
 550                 transform.getMatrix(info.devTx = new double[4]);
 551                 devAt = new AffineTransform(info.devTx);
 552                 textAt.preConcatenate(devAt);
 553             } else {
 554                 info.devTx = IDENT_MATRIX;
 555                 devAt = IDENT_ATX;
 556             }
 557             textAt.getMatrix(info.glyphTx = new double[4]);
 558             double shearx = textAt.getShearX();
 559             double scaley = textAt.getScaleY();
 560             if (shearx != 0) {
 561                 scaley = Math.sqrt(shearx * shearx + scaley * scaley);
 562             }
 563             info.pixelHeight = (int)(Math.abs(scaley)+0.5);
 564         } else {
 565             txFontType = AffineTransform.TYPE_IDENTITY;
 566             info.originX = info.originY = 0;
 567             if (transformState >= TRANSFORM_TRANSLATESCALE) {
 568                 transform.getMatrix(info.devTx = new double[4]);
 569                 devAt = new AffineTransform(info.devTx);
 570                 info.glyphTx = new double[4];
 571                 for (int i = 0; i < 4; i++) {
 572                     info.glyphTx[i] = info.devTx[i] * ptSize;
 573                 }
 574                 textAt = new AffineTransform(info.glyphTx);
 575                 double shearx = transform.getShearX();
 576                 double scaley = transform.getScaleY();
 577                 if (shearx != 0) {
 578                     scaley = Math.sqrt(shearx * shearx + scaley * scaley);
 579                 }
 580                 info.pixelHeight = (int)(Math.abs(scaley * ptSize)+0.5);
 581             } else {
 582                 /* If the double represents a common integral, we
 583                  * may have pre-allocated objects.
 584                  * A "sparse" array be seems to be as fast as a switch
 585                  * even for 3 or 4 pt sizes, and is more flexible.
 586                  * This should perform comparably in single-threaded
 587                  * rendering to the old code which synchronized on the
 588                  * class and scale better on MP systems.
 589                  */
 590                 int pszInt = (int)ptSize;
 591                 if (ptSize == pszInt &&
 592                     pszInt >= MINALLOCATED && pszInt < TEXTARRSIZE) {
 593                     info.glyphTx = textTxArr[pszInt];
 594                     textAt = textAtArr[pszInt];
 595                     info.pixelHeight = pszInt;
 596                 } else {
 597                     info.pixelHeight = (int)(ptSize+0.5);
 598                 }
 599                 if (textAt == null) {
 600                     info.glyphTx = new double[] {ptSize, 0, 0, ptSize};
 601                     textAt = new AffineTransform(info.glyphTx);
 602                 }
 603 
 604                 info.devTx = IDENT_MATRIX;
 605                 devAt = IDENT_ATX;
 606             }
 607         }
 608 
 609         info.font2D = FontUtilities.getFont2D(font);
 610 
 611         int fmhint = fractionalMetricsHint;
 612         if (fmhint == SunHints.INTVAL_FRACTIONALMETRICS_DEFAULT) {
 613             fmhint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
 614         }
 615         info.lcdSubPixPos = false; // conditionally set true in LCD mode.
 616 
 617         /* The text anti-aliasing hints that are set by the client need
 618          * to be interpreted for the current state and stored in the
 619          * FontInfo.aahint which is what will actually be used and
 620          * will be one of OFF, ON, LCD_HRGB or LCD_VRGB.
 621          * This is what pipe selection code should typically refer to, not
 622          * textAntialiasHint. This means we are now evaluating the meaning
 623          * of "default" here. Any pipe that really cares about that will
 624          * also need to consult that variable.
 625          * Otherwise these are being used only as args to getStrike,
 626          * and are encapsulated in that object which is part of the
 627          * FontInfo, so we do not need to store them directly as fields
 628          * in the FontInfo object.
 629          * That could change if FontInfo's were more selectively
 630          * revalidated when graphics state changed. Presently this
 631          * method re-evaluates all fields in the fontInfo.
 632          * The strike doesn't need to know the RGB subpixel order. Just
 633          * if its H or V orientation, so if an LCD option is specified we
 634          * always pass in the RGB hint to the strike.
 635          * frc is non-null only if this is a GlyphVector. For reasons
 636          * which are probably a historical mistake the AA hint in a GV
 637          * is honoured when we render, overriding the Graphics setting.
 638          */
 639         int aahint;
 640         if (frc == null) {
 641             aahint = textAntialiasHint;
 642         } else {
 643             aahint = ((SunHints.Value)frc.getAntiAliasingHint()).getIndex();
 644         }
 645         if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT) {
 646             if (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
 647                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 648             } else {
 649                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
 650             }
 651         } else {
 652             /* If we are in checkFontInfo because a rendering hint has been
 653              * set then all pipes are revalidated. But we can also
 654              * be here because setFont() has been called when the 'gasp'
 655              * hint is set, as then the font size determines the text pipe.
 656              * See comments in SunGraphics2d.setFont(Font).
 657              */
 658             if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP) {
 659                 if (info.font2D.useAAForPtSize(info.pixelHeight)) {
 660                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 661                 } else {
 662                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
 663                 }
 664             } else if (aahint >= SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB) {
 665                 /* loops for default rendering modes are installed in the SG2D
 666                  * constructor. If there are none this will be null.
 667                  * Not all compositing modes update the render loops, so
 668                  * we also test that this is a mode we know should support
 669                  * this. One minor issue is that the loops aren't necessarily
 670                  * installed for a new rendering mode until after this
 671                  * method is called during pipeline validation. So it is
 672                  * theoretically possible that it was set to null for a
 673                  * compositing mode, the composite is then set back to Src,
 674                  * but the loop is still null when this is called and AA=ON
 675                  * is installed instead of an LCD mode.
 676                  * However this is done in the right order in SurfaceData.java
 677                  * so this is not likely to be a problem - but not
 678                  * guaranteed.
 679                  */
 680                 if (
 681                     !surfaceData.canRenderLCDText(this)
 682 //                    loops.drawGlyphListLCDLoop == null ||
 683 //                    compositeState > COMP_ISCOPY ||
 684 //                    paintState > PAINT_ALPHACOLOR
 685                       ) {
 686                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 687                 } else {
 688                     info.lcdRGBOrder = true;
 689                     /* Collapse these into just HRGB or VRGB.
 690                      * Pipe selection code needs only to test for these two.
 691                      * Since these both select the same pipe anyway its
 692                      * tempting to collapse into one value. But they are
 693                      * different strikes (glyph caches) so the distinction
 694                      * needs to be made for that purpose.
 695                      */
 696                     if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HBGR) {
 697                         aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
 698                         info.lcdRGBOrder = false;
 699                     } else if
 700                         (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VBGR) {
 701                         aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB;
 702                         info.lcdRGBOrder = false;
 703                     }
 704                     /* Support subpixel positioning only for the case in
 705                      * which the horizontal resolution is increased
 706                      */
 707                     info.lcdSubPixPos =
 708                         fmhint == SunHints.INTVAL_FRACTIONALMETRICS_ON &&
 709                         aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
 710                 }
 711             }
 712         }
 713         info.aaHint = aahint;
 714         info.fontStrike = info.font2D.getStrike(font, devAt, textAt,
 715                                                 aahint, fmhint);
 716         return info;
 717     }
 718 
 719     public static boolean isRotated(double [] mtx) {
 720         if ((mtx[0] == mtx[3]) &&
 721             (mtx[1] == 0.0) &&
 722             (mtx[2] == 0.0) &&
 723             (mtx[0] > 0.0))
 724         {
 725             return false;
 726         }
 727 
 728         return true;
 729     }
 730 
 731     public void setFont(Font font) {
 732         /* replacing the reference equality test font != this.font with
 733          * !font.equals(this.font) did not yield any measurable difference
 734          * in testing, but there may be yet to be identified cases where it
 735          * is beneficial.
 736          */
 737         if (font != null && font!=this.font/*!font.equals(this.font)*/) {
 738             /* In the GASP AA case the textpipe depends on the glyph size
 739              * as determined by graphics and font transforms as well as the
 740              * font size, and information in the font. But we may invalidate
 741              * the pipe only to find that it made no difference.
 742              * Deferring pipe invalidation to checkFontInfo won't work because
 743              * when called we may already be rendering to the wrong pipe.
 744              * So, if the font is transformed, or the graphics has more than
 745              * a simple scale, we'll take that as enough of a hint to
 746              * revalidate everything. But if they aren't we will
 747              * use the font's point size to query the gasp table and see if
 748              * what it says matches what's currently being used, in which
 749              * case there's no need to invalidate the textpipe.
 750              * This should be sufficient for all typical uses cases.
 751              */
 752             if (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP &&
 753                 textpipe != invalidpipe &&
 754                 (transformState > TRANSFORM_ANY_TRANSLATE ||
 755                  font.isTransformed() ||
 756                  fontInfo == null || // Precaution, if true shouldn't get here
 757                  (fontInfo.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) !=
 758                      FontUtilities.getFont2D(font).
 759                          useAAForPtSize(font.getSize()))) {
 760                 textpipe = invalidpipe;
 761             }
 762             this.font = font;
 763             this.fontMetrics = null;
 764             this.validFontInfo = false;
 765         }
 766     }
 767 
 768     public FontInfo getFontInfo() {
 769         if (!validFontInfo) {
 770             this.fontInfo = checkFontInfo(this.fontInfo, font, null);
 771             validFontInfo = true;
 772         }
 773         return this.fontInfo;
 774     }
 775 
 776     /* Used by drawGlyphVector which specifies its own font. */
 777     public FontInfo getGVFontInfo(Font font, FontRenderContext frc) {
 778         if (glyphVectorFontInfo != null &&
 779             glyphVectorFontInfo.font == font &&
 780             glyphVectorFRC == frc) {
 781             return glyphVectorFontInfo;
 782         } else {
 783             glyphVectorFRC = frc;
 784             return glyphVectorFontInfo =
 785                 checkFontInfo(glyphVectorFontInfo, font, frc);
 786         }
 787     }
 788 
 789     public FontMetrics getFontMetrics() {
 790         if (this.fontMetrics != null) {
 791             return this.fontMetrics;
 792         }
 793         /* NB the constructor and the setter disallow "font" being null */
 794         return this.fontMetrics =
 795            FontDesignMetrics.getMetrics(font, getFontRenderContext());
 796     }
 797 
 798     public FontMetrics getFontMetrics(Font font) {
 799         if ((this.fontMetrics != null) && (font == this.font)) {
 800             return this.fontMetrics;
 801         }
 802         FontMetrics fm =
 803           FontDesignMetrics.getMetrics(font, getFontRenderContext());
 804 
 805         if (this.font == font) {
 806             this.fontMetrics = fm;
 807         }
 808         return fm;
 809     }
 810 
 811     /**
 812      * Checks to see if a Path intersects the specified Rectangle in device
 813      * space.  The rendering attributes taken into account include the
 814      * clip, transform, and stroke attributes.
 815      * @param rect The area in device space to check for a hit.
 816      * @param p The path to check for a hit.
 817      * @param onStroke Flag to choose between testing the stroked or
 818      * the filled path.
 819      * @return True if there is a hit, false otherwise.
 820      * @see #setStroke
 821      * @see #fillPath
 822      * @see #drawPath
 823      * @see #transform
 824      * @see #setTransform
 825      * @see #clip
 826      * @see #setClip
 827      */
 828     public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
 829         if (onStroke) {
 830             s = stroke.createStrokedShape(s);
 831         }
 832 
 833         s = transformShape(s);
 834         if ((constrainX|constrainY) != 0) {
 835             rect = new Rectangle(rect);
 836             rect.translate(constrainX, constrainY);
 837         }
 838 
 839         return s.intersects(rect);
 840     }
 841 
 842     /**
 843      * Return the ColorModel associated with this Graphics2D.
 844      */
 845     public ColorModel getDeviceColorModel() {
 846         return surfaceData.getColorModel();
 847     }
 848 
 849     /**
 850      * Return the device configuration associated with this Graphics2D.
 851      */
 852     public GraphicsConfiguration getDeviceConfiguration() {
 853         return surfaceData.getDeviceConfiguration();
 854     }
 855 
 856     /**
 857      * Return the SurfaceData object assigned to manage the destination
 858      * drawable surface of this Graphics2D.
 859      */
 860     public final SurfaceData getSurfaceData() {
 861         return surfaceData;
 862     }
 863 
 864     /**
 865      * Sets the Composite in the current graphics state. Composite is used
 866      * in all drawing methods such as drawImage, drawString, drawPath,
 867      * and fillPath.  It specifies how new pixels are to be combined with
 868      * the existing pixels on the graphics device in the rendering process.
 869      * @param comp The Composite object to be used for drawing.
 870      * @see java.awt.Graphics#setXORMode
 871      * @see java.awt.Graphics#setPaintMode
 872      * @see AlphaComposite
 873      */
 874     public void setComposite(Composite comp) {
 875         if (composite == comp) {
 876             return;
 877         }
 878         int newCompState;
 879         CompositeType newCompType;
 880         if (comp instanceof AlphaComposite) {
 881             AlphaComposite alphacomp = (AlphaComposite) comp;
 882             newCompType = CompositeType.forAlphaComposite(alphacomp);
 883             if (newCompType == CompositeType.SrcOverNoEa) {
 884                 if (paintState == PAINT_OPAQUECOLOR ||
 885                     (paintState > PAINT_ALPHACOLOR &&
 886                      paint.getTransparency() == Transparency.OPAQUE))
 887                 {
 888                     newCompState = COMP_ISCOPY;
 889                 } else {
 890                     newCompState = COMP_ALPHA;
 891                 }
 892             } else if (newCompType == CompositeType.SrcNoEa ||
 893                        newCompType == CompositeType.Src ||
 894                        newCompType == CompositeType.Clear)
 895             {
 896                 newCompState = COMP_ISCOPY;
 897             } else if (surfaceData.getTransparency() == Transparency.OPAQUE &&
 898                        newCompType == CompositeType.SrcIn)
 899             {
 900                 newCompState = COMP_ISCOPY;
 901             } else {
 902                 newCompState = COMP_ALPHA;
 903             }
 904         } else if (comp instanceof XORComposite) {
 905             newCompState = COMP_XOR;
 906             newCompType = CompositeType.Xor;
 907         } else if (comp == null) {
 908             throw new IllegalArgumentException("null Composite");
 909         } else {
 910             surfaceData.checkCustomComposite();
 911             newCompState = COMP_CUSTOM;
 912             newCompType = CompositeType.General;
 913         }
 914         if (compositeState != newCompState ||
 915             imageComp != newCompType)
 916         {
 917             compositeState = newCompState;
 918             imageComp = newCompType;
 919             invalidatePipe();
 920             validFontInfo = false;
 921         }
 922         composite = comp;
 923         if (paintState <= PAINT_ALPHACOLOR) {
 924             validateColor();
 925         }
 926     }
 927 
 928     /**
 929      * Sets the Paint in the current graphics state.
 930      * @param paint The Paint object to be used to generate color in
 931      * the rendering process.
 932      * @see java.awt.Graphics#setColor
 933      * @see GradientPaint
 934      * @see TexturePaint
 935      */
 936     public void setPaint(Paint paint) {
 937         if (paint instanceof Color) {
 938             setColor((Color) paint);
 939             return;
 940         }
 941         if (paint == null || this.paint == paint) {
 942             return;
 943         }
 944         this.paint = paint;
 945         if (imageComp == CompositeType.SrcOverNoEa) {
 946             // special case where compState depends on opacity of paint
 947             if (paint.getTransparency() == Transparency.OPAQUE) {
 948                 if (compositeState != COMP_ISCOPY) {
 949                     compositeState = COMP_ISCOPY;
 950                 }
 951             } else {
 952                 if (compositeState == COMP_ISCOPY) {
 953                     compositeState = COMP_ALPHA;
 954                 }
 955             }
 956         }
 957         Class<? extends Paint> paintClass = paint.getClass();
 958         if (paintClass == GradientPaint.class) {
 959             paintState = PAINT_GRADIENT;
 960         } else if (paintClass == LinearGradientPaint.class) {
 961             paintState = PAINT_LIN_GRADIENT;
 962         } else if (paintClass == RadialGradientPaint.class) {
 963             paintState = PAINT_RAD_GRADIENT;
 964         } else if (paintClass == TexturePaint.class) {
 965             paintState = PAINT_TEXTURE;
 966         } else {
 967             paintState = PAINT_CUSTOM;
 968         }
 969         validFontInfo = false;
 970         invalidatePipe();
 971     }
 972 
 973     static final int NON_UNIFORM_SCALE_MASK =
 974         (AffineTransform.TYPE_GENERAL_TRANSFORM |
 975          AffineTransform.TYPE_GENERAL_SCALE);
 976     public static final double MinPenSizeAA =
 977         sun.java2d.pipe.RenderingEngine.getInstance().getMinimumAAPenSize();
 978     public static final double MinPenSizeAASquared =
 979         (MinPenSizeAA * MinPenSizeAA);
 980     // Since inaccuracies in the trig package can cause us to
 981     // calculated a rotated pen width of just slightly greater
 982     // than 1.0, we add a fudge factor to our comparison value
 983     // here so that we do not misclassify single width lines as
 984     // wide lines under certain rotations.
 985     public static final double MinPenSizeSquared = 1.000000001;
 986 
 987     private void validateBasicStroke(BasicStroke bs) {
 988         boolean aa = (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON);
 989         if (transformState < TRANSFORM_TRANSLATESCALE) {
 990             if (aa) {
 991                 if (bs.getLineWidth() <= MinPenSizeAA) {
 992                     if (bs.getDashArray() == null) {
 993                         strokeState = STROKE_THIN;
 994                     } else {
 995                         strokeState = STROKE_THINDASHED;
 996                     }
 997                 } else {
 998                     strokeState = STROKE_WIDE;
 999                 }
1000             } else {
1001                 if (bs == defaultStroke) {
1002                     strokeState = STROKE_THIN;
1003                 } else if (bs.getLineWidth() <= 1.0f) {
1004                     if (bs.getDashArray() == null) {
1005                         strokeState = STROKE_THIN;
1006                     } else {
1007                         strokeState = STROKE_THINDASHED;
1008                     }
1009                 } else {
1010                     strokeState = STROKE_WIDE;
1011                 }
1012             }
1013         } else {
1014             double widthsquared;
1015             if ((transform.getType() & NON_UNIFORM_SCALE_MASK) == 0) {
1016                 /* sqrt omitted, compare to squared limits below. */
1017                 widthsquared = Math.abs(transform.getDeterminant());
1018             } else {
1019                 /* First calculate the "maximum scale" of this transform. */
1020                 double A = transform.getScaleX();       // m00
1021                 double C = transform.getShearX();       // m01
1022                 double B = transform.getShearY();       // m10
1023                 double D = transform.getScaleY();       // m11
1024 
1025                 /*
1026                  * Given a 2 x 2 affine matrix [ A B ] such that
1027                  *                             [ C D ]
1028                  * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
1029                  * find the maximum magnitude (norm) of the vector v'
1030                  * with the constraint (x^2 + y^2 = 1).
1031                  * The equation to maximize is
1032                  *     |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
1033                  * or  |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
1034                  * Since sqrt is monotonic we can maximize |v'|^2
1035                  * instead and plug in the substitution y = sqrt(1 - x^2).
1036                  * Trigonometric equalities can then be used to get
1037                  * rid of most of the sqrt terms.
1038                  */
1039                 double EA = A*A + B*B;          // x^2 coefficient
1040                 double EB = 2*(A*C + B*D);      // xy coefficient
1041                 double EC = C*C + D*D;          // y^2 coefficient
1042 
1043                 /*
1044                  * There is a lot of calculus omitted here.
1045                  *
1046                  * Conceptually, in the interests of understanding the
1047                  * terms that the calculus produced we can consider
1048                  * that EA and EC end up providing the lengths along
1049                  * the major axes and the hypot term ends up being an
1050                  * adjustment for the additional length along the off-axis
1051                  * angle of rotated or sheared ellipses as well as an
1052                  * adjustment for the fact that the equation below
1053                  * averages the two major axis lengths.  (Notice that
1054                  * the hypot term contains a part which resolves to the
1055                  * difference of these two axis lengths in the absence
1056                  * of rotation.)
1057                  *
1058                  * In the calculus, the ratio of the EB and (EA-EC) terms
1059                  * ends up being the tangent of 2*theta where theta is
1060                  * the angle that the long axis of the ellipse makes
1061                  * with the horizontal axis.  Thus, this equation is
1062                  * calculating the length of the hypotenuse of a triangle
1063                  * along that axis.
1064                  */
1065                 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
1066 
1067                 /* sqrt omitted, compare to squared limits below. */
1068                 widthsquared = ((EA + EC + hypot)/2.0);
1069             }
1070             if (bs != defaultStroke) {
1071                 widthsquared *= bs.getLineWidth() * bs.getLineWidth();
1072             }
1073             if (widthsquared <=
1074                 (aa ? MinPenSizeAASquared : MinPenSizeSquared))
1075             {
1076                 if (bs.getDashArray() == null) {
1077                     strokeState = STROKE_THIN;
1078                 } else {
1079                     strokeState = STROKE_THINDASHED;
1080                 }
1081             } else {
1082                 strokeState = STROKE_WIDE;
1083             }
1084         }
1085     }
1086 
1087     /*
1088      * Sets the Stroke in the current graphics state.
1089      * @param s The Stroke object to be used to stroke a Path in
1090      * the rendering process.
1091      * @see BasicStroke
1092      */
1093     public void setStroke(Stroke s) {
1094         if (s == null) {
1095             throw new IllegalArgumentException("null Stroke");
1096         }
1097         int saveStrokeState = strokeState;
1098         stroke = s;
1099         if (s instanceof BasicStroke) {
1100             validateBasicStroke((BasicStroke) s);
1101         } else {
1102             strokeState = STROKE_CUSTOM;
1103         }
1104         if (strokeState != saveStrokeState) {
1105             invalidatePipe();
1106         }
1107     }
1108 
1109     /**
1110      * Sets the preferences for the rendering algorithms.
1111      * Hint categories include controls for rendering quality and
1112      * overall time/quality trade-off in the rendering process.
1113      * @param hintKey The key of hint to be set. The strings are
1114      * defined in the RenderingHints class.
1115      * @param hintValue The value indicating preferences for the specified
1116      * hint category. These strings are defined in the RenderingHints
1117      * class.
1118      * @see RenderingHints
1119      */
1120     public void setRenderingHint(Key hintKey, Object hintValue) {
1121         // If we recognize the key, we must recognize the value
1122         //     otherwise throw an IllegalArgumentException
1123         //     and do not change the Hints object
1124         // If we do not recognize the key, just pass it through
1125         //     to the Hints object untouched
1126         if (!hintKey.isCompatibleValue(hintValue)) {
1127             throw new IllegalArgumentException
1128                 (hintValue+" is not compatible with "+hintKey);
1129         }
1130         if (hintKey instanceof SunHints.Key) {
1131             boolean stateChanged;
1132             boolean textStateChanged = false;
1133             boolean recognized = true;
1134             SunHints.Key sunKey = (SunHints.Key) hintKey;
1135             int newHint;
1136             if (sunKey == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST) {
1137                 newHint = ((Integer)hintValue).intValue();
1138             } else {
1139                 newHint = ((SunHints.Value) hintValue).getIndex();
1140             }
1141             switch (sunKey.getIndex()) {
1142             case SunHints.INTKEY_RENDERING:
1143                 stateChanged = (renderHint != newHint);
1144                 if (stateChanged) {
1145                     renderHint = newHint;
1146                     if (interpolationHint == -1) {
1147                         interpolationType =
1148                             (newHint == SunHints.INTVAL_RENDER_QUALITY
1149                              ? AffineTransformOp.TYPE_BILINEAR
1150                              : AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
1151                     }
1152                 }
1153                 break;
1154             case SunHints.INTKEY_ANTIALIASING:
1155                 stateChanged = (antialiasHint != newHint);
1156                 antialiasHint = newHint;
1157                 if (stateChanged) {
1158                     textStateChanged =
1159                         (textAntialiasHint ==
1160                          SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT);
1161                     if (strokeState != STROKE_CUSTOM) {
1162                         validateBasicStroke((BasicStroke) stroke);
1163                     }
1164                 }
1165                 break;
1166             case SunHints.INTKEY_TEXT_ANTIALIASING:
1167                 stateChanged = (textAntialiasHint != newHint);
1168                 textStateChanged = stateChanged;
1169                 textAntialiasHint = newHint;
1170                 break;
1171             case SunHints.INTKEY_FRACTIONALMETRICS:
1172                 stateChanged = (fractionalMetricsHint != newHint);
1173                 textStateChanged = stateChanged;
1174                 fractionalMetricsHint = newHint;
1175                 break;
1176             case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1177                 stateChanged = false;
1178                 /* Already have validated it is an int 100 <= newHint <= 250 */
1179                 lcdTextContrast = newHint;
1180                 break;
1181             case SunHints.INTKEY_INTERPOLATION:
1182                 interpolationHint = newHint;
1183                 switch (newHint) {
1184                 case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1185                     newHint = AffineTransformOp.TYPE_BICUBIC;
1186                     break;
1187                 case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1188                     newHint = AffineTransformOp.TYPE_BILINEAR;
1189                     break;
1190                 default:
1191                 case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1192                     newHint = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1193                     break;
1194                 }
1195                 stateChanged = (interpolationType != newHint);
1196                 interpolationType = newHint;
1197                 break;
1198             case SunHints.INTKEY_STROKE_CONTROL:
1199                 stateChanged = (strokeHint != newHint);
1200                 strokeHint = newHint;
1201                 break;
1202             default:
1203                 recognized = false;
1204                 stateChanged = false;
1205                 break;
1206             }
1207             if (recognized) {
1208                 if (stateChanged) {
1209                     invalidatePipe();
1210                     if (textStateChanged) {
1211                         fontMetrics = null;
1212                         this.cachedFRC = null;
1213                         validFontInfo = false;
1214                         this.glyphVectorFontInfo = null;
1215                     }
1216                 }
1217                 if (hints != null) {
1218                     hints.put(hintKey, hintValue);
1219                 }
1220                 return;
1221             }
1222         }
1223         // Nothing we recognize so none of "our state" has changed
1224         if (hints == null) {
1225             hints = makeHints(null);
1226         }
1227         hints.put(hintKey, hintValue);
1228     }
1229 
1230 
1231     /**
1232      * Returns the preferences for the rendering algorithms.
1233      * @param hintCategory The category of hint to be set. The strings
1234      * are defined in the RenderingHints class.
1235      * @return The preferences for rendering algorithms. The strings
1236      * are defined in the RenderingHints class.
1237      * @see RenderingHints
1238      */
1239     public Object getRenderingHint(Key hintKey) {
1240         if (hints != null) {
1241             return hints.get(hintKey);
1242         }
1243         if (!(hintKey instanceof SunHints.Key)) {
1244             return null;
1245         }
1246         int keyindex = ((SunHints.Key)hintKey).getIndex();
1247         switch (keyindex) {
1248         case SunHints.INTKEY_RENDERING:
1249             return SunHints.Value.get(SunHints.INTKEY_RENDERING,
1250                                       renderHint);
1251         case SunHints.INTKEY_ANTIALIASING:
1252             return SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,
1253                                       antialiasHint);
1254         case SunHints.INTKEY_TEXT_ANTIALIASING:
1255             return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
1256                                       textAntialiasHint);
1257         case SunHints.INTKEY_FRACTIONALMETRICS:
1258             return SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
1259                                       fractionalMetricsHint);
1260         case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1261             return new Integer(lcdTextContrast);
1262         case SunHints.INTKEY_INTERPOLATION:
1263             switch (interpolationHint) {
1264             case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1265                 return SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1266             case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1267                 return SunHints.VALUE_INTERPOLATION_BILINEAR;
1268             case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1269                 return SunHints.VALUE_INTERPOLATION_BICUBIC;
1270             }
1271             return null;
1272         case SunHints.INTKEY_STROKE_CONTROL:
1273             return SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,
1274                                       strokeHint);
1275         }
1276         return null;
1277     }
1278 
1279     /**
1280      * Sets the preferences for the rendering algorithms.
1281      * Hint categories include controls for rendering quality and
1282      * overall time/quality trade-off in the rendering process.
1283      * @param hints The rendering hints to be set
1284      * @see RenderingHints
1285      */
1286     public void setRenderingHints(Map<?,?> hints) {
1287         this.hints = null;
1288         renderHint = SunHints.INTVAL_RENDER_DEFAULT;
1289         antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
1290         textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
1291         fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
1292         lcdTextContrast = lcdTextContrastDefaultValue;
1293         interpolationHint = -1;
1294         interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1295         boolean customHintPresent = false;
1296         Iterator<?> iter = hints.keySet().iterator();
1297         while (iter.hasNext()) {
1298             Object key = iter.next();
1299             if (key == SunHints.KEY_RENDERING ||
1300                 key == SunHints.KEY_ANTIALIASING ||
1301                 key == SunHints.KEY_TEXT_ANTIALIASING ||
1302                 key == SunHints.KEY_FRACTIONALMETRICS ||
1303                 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||
1304                 key == SunHints.KEY_STROKE_CONTROL ||
1305                 key == SunHints.KEY_INTERPOLATION)
1306             {
1307                 setRenderingHint((Key) key, hints.get(key));
1308             } else {
1309                 customHintPresent = true;
1310             }
1311         }
1312         if (customHintPresent) {
1313             this.hints = makeHints(hints);
1314         }
1315         invalidatePipe();
1316     }
1317 
1318     /**
1319      * Adds a number of preferences for the rendering algorithms.
1320      * Hint categories include controls for rendering quality and
1321      * overall time/quality trade-off in the rendering process.
1322      * @param hints The rendering hints to be set
1323      * @see RenderingHints
1324      */
1325     public void addRenderingHints(Map<?,?> hints) {
1326         boolean customHintPresent = false;
1327         Iterator<?> iter = hints.keySet().iterator();
1328         while (iter.hasNext()) {
1329             Object key = iter.next();
1330             if (key == SunHints.KEY_RENDERING ||
1331                 key == SunHints.KEY_ANTIALIASING ||
1332                 key == SunHints.KEY_TEXT_ANTIALIASING ||
1333                 key == SunHints.KEY_FRACTIONALMETRICS ||
1334                 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||
1335                 key == SunHints.KEY_STROKE_CONTROL ||
1336                 key == SunHints.KEY_INTERPOLATION)
1337             {
1338                 setRenderingHint((Key) key, hints.get(key));
1339             } else {
1340                 customHintPresent = true;
1341             }
1342         }
1343         if (customHintPresent) {
1344             if (this.hints == null) {
1345                 this.hints = makeHints(hints);
1346             } else {
1347                 this.hints.putAll(hints);
1348             }
1349         }
1350     }
1351 
1352     /**
1353      * Gets the preferences for the rendering algorithms.
1354      * Hint categories include controls for rendering quality and
1355      * overall time/quality trade-off in the rendering process.
1356      * @see RenderingHints
1357      */
1358     public RenderingHints getRenderingHints() {
1359         if (hints == null) {
1360             return makeHints(null);
1361         } else {
1362             return (RenderingHints) hints.clone();
1363         }
1364     }
1365 
1366     RenderingHints makeHints(Map hints) {
1367         RenderingHints model = new RenderingHints(hints);
1368         model.put(SunHints.KEY_RENDERING,
1369                   SunHints.Value.get(SunHints.INTKEY_RENDERING,
1370                                      renderHint));
1371         model.put(SunHints.KEY_ANTIALIASING,
1372                   SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,
1373                                      antialiasHint));
1374         model.put(SunHints.KEY_TEXT_ANTIALIASING,
1375                   SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
1376                                      textAntialiasHint));
1377         model.put(SunHints.KEY_FRACTIONALMETRICS,
1378                   SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
1379                                      fractionalMetricsHint));
1380         model.put(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST,
1381                   Integer.valueOf(lcdTextContrast));
1382         Object value;
1383         switch (interpolationHint) {
1384         case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1385             value = SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1386             break;
1387         case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1388             value = SunHints.VALUE_INTERPOLATION_BILINEAR;
1389             break;
1390         case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1391             value = SunHints.VALUE_INTERPOLATION_BICUBIC;
1392             break;
1393         default:
1394             value = null;
1395             break;
1396         }
1397         if (value != null) {
1398             model.put(SunHints.KEY_INTERPOLATION, value);
1399         }
1400         model.put(SunHints.KEY_STROKE_CONTROL,
1401                   SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,
1402                                      strokeHint));
1403         return model;
1404     }
1405 
1406     /**
1407      * Concatenates the current transform of this Graphics2D with a
1408      * translation transformation.
1409      * This is equivalent to calling transform(T), where T is an
1410      * AffineTransform represented by the following matrix:
1411      * <pre>
1412      *          [   1    0    tx  ]
1413      *          [   0    1    ty  ]
1414      *          [   0    0    1   ]
1415      * </pre>
1416      */
1417     public void translate(double tx, double ty) {
1418         transform.translate(tx, ty);
1419         invalidateTransform();
1420     }
1421 
1422     /**
1423      * Concatenates the current transform of this Graphics2D with a
1424      * rotation transformation.
1425      * This is equivalent to calling transform(R), where R is an
1426      * AffineTransform represented by the following matrix:
1427      * <pre>
1428      *          [   cos(theta)    -sin(theta)    0   ]
1429      *          [   sin(theta)     cos(theta)    0   ]
1430      *          [       0              0         1   ]
1431      * </pre>
1432      * Rotating with a positive angle theta rotates points on the positive
1433      * x axis toward the positive y axis.
1434      * @param theta The angle of rotation in radians.
1435      */
1436     public void rotate(double theta) {
1437         transform.rotate(theta);
1438         invalidateTransform();
1439     }
1440 
1441     /**
1442      * Concatenates the current transform of this Graphics2D with a
1443      * translated rotation transformation.
1444      * This is equivalent to the following sequence of calls:
1445      * <pre>
1446      *          translate(x, y);
1447      *          rotate(theta);
1448      *          translate(-x, -y);
1449      * </pre>
1450      * Rotating with a positive angle theta rotates points on the positive
1451      * x axis toward the positive y axis.
1452      * @param theta The angle of rotation in radians.
1453      * @param x The x coordinate of the origin of the rotation
1454      * @param y The x coordinate of the origin of the rotation
1455      */
1456     public void rotate(double theta, double x, double y) {
1457         transform.rotate(theta, x, y);
1458         invalidateTransform();
1459     }
1460 
1461     /**
1462      * Concatenates the current transform of this Graphics2D with a
1463      * scaling transformation.
1464      * This is equivalent to calling transform(S), where S is an
1465      * AffineTransform represented by the following matrix:
1466      * <pre>
1467      *          [   sx   0    0   ]
1468      *          [   0    sy   0   ]
1469      *          [   0    0    1   ]
1470      * </pre>
1471      */
1472     public void scale(double sx, double sy) {
1473         transform.scale(sx, sy);
1474         invalidateTransform();
1475     }
1476 
1477     /**
1478      * Concatenates the current transform of this Graphics2D with a
1479      * shearing transformation.
1480      * This is equivalent to calling transform(SH), where SH is an
1481      * AffineTransform represented by the following matrix:
1482      * <pre>
1483      *          [   1   shx   0   ]
1484      *          [  shy   1    0   ]
1485      *          [   0    0    1   ]
1486      * </pre>
1487      * @param shx The factor by which coordinates are shifted towards the
1488      * positive X axis direction according to their Y coordinate
1489      * @param shy The factor by which coordinates are shifted towards the
1490      * positive Y axis direction according to their X coordinate
1491      */
1492     public void shear(double shx, double shy) {
1493         transform.shear(shx, shy);
1494         invalidateTransform();
1495     }
1496 
1497     /**
1498      * Composes a Transform object with the transform in this
1499      * Graphics2D according to the rule last-specified-first-applied.
1500      * If the currrent transform is Cx, the result of composition
1501      * with Tx is a new transform Cx'.  Cx' becomes the current
1502      * transform for this Graphics2D.
1503      * Transforming a point p by the updated transform Cx' is
1504      * equivalent to first transforming p by Tx and then transforming
1505      * the result by the original transform Cx.  In other words,
1506      * Cx'(p) = Cx(Tx(p)).
1507      * A copy of the Tx is made, if necessary, so further
1508      * modifications to Tx do not affect rendering.
1509      * @param Tx The Transform object to be composed with the current
1510      * transform.
1511      * @see #setTransform
1512      * @see AffineTransform
1513      */
1514     public void transform(AffineTransform xform) {
1515         this.transform.concatenate(xform);
1516         invalidateTransform();
1517     }
1518 
1519     /**
1520      * Translate
1521      */
1522     public void translate(int x, int y) {
1523         transform.translate(x, y);
1524         if (transformState <= TRANSFORM_INT_TRANSLATE) {
1525             transX += x;
1526             transY += y;
1527             transformState = (((transX | transY) == 0) ?
1528                               TRANSFORM_ISIDENT : TRANSFORM_INT_TRANSLATE);
1529         } else {
1530             invalidateTransform();
1531         }
1532     }
1533 
1534     /**
1535      * Sets the Transform in the current graphics state.
1536      * @param Tx The Transform object to be used in the rendering process.
1537      * @see #transform
1538      * @see TransformChain
1539      * @see AffineTransform
1540      */
1541     public void setTransform(AffineTransform Tx) {
1542         if ((constrainX|constrainY) == 0) {
1543             transform.setTransform(Tx);
1544         } else {
1545             transform.setToTranslation(constrainX, constrainY);
1546             transform.concatenate(Tx);
1547         }
1548         invalidateTransform();
1549     }
1550 
1551     protected void invalidateTransform() {
1552         int type = transform.getType();
1553         int origTransformState = transformState;
1554         if (type == AffineTransform.TYPE_IDENTITY) {
1555             transformState = TRANSFORM_ISIDENT;
1556             transX = transY = 0;
1557         } else if (type == AffineTransform.TYPE_TRANSLATION) {
1558             double dtx = transform.getTranslateX();
1559             double dty = transform.getTranslateY();
1560             transX = (int) Math.floor(dtx + 0.5);
1561             transY = (int) Math.floor(dty + 0.5);
1562             if (dtx == transX && dty == transY) {
1563                 transformState = TRANSFORM_INT_TRANSLATE;
1564             } else {
1565                 transformState = TRANSFORM_ANY_TRANSLATE;
1566             }
1567         } else if ((type & (AffineTransform.TYPE_FLIP |
1568                             AffineTransform.TYPE_MASK_ROTATION |
1569                             AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0)
1570         {
1571             transformState = TRANSFORM_TRANSLATESCALE;
1572             transX = transY = 0;
1573         } else {
1574             transformState = TRANSFORM_GENERIC;
1575             transX = transY = 0;
1576         }
1577 
1578         if (transformState >= TRANSFORM_TRANSLATESCALE ||
1579             origTransformState >= TRANSFORM_TRANSLATESCALE)
1580         {
1581             /* Its only in this case that the previous or current transform
1582              * was more than a translate that font info is invalidated
1583              */
1584             cachedFRC = null;
1585             this.validFontInfo = false;
1586             this.fontMetrics = null;
1587             this.glyphVectorFontInfo = null;
1588 
1589             if (transformState != origTransformState) {
1590                 invalidatePipe();
1591             }
1592         }
1593         if (strokeState != STROKE_CUSTOM) {
1594             validateBasicStroke((BasicStroke) stroke);
1595         }
1596     }
1597 
1598     /**
1599      * Returns the current Transform in the Graphics2D state.
1600      * @see #transform
1601      * @see #setTransform
1602      */
1603     public AffineTransform getTransform() {
1604         if ((constrainX|constrainY) == 0) {
1605             return new AffineTransform(transform);
1606         }
1607         AffineTransform tx =
1608             AffineTransform.getTranslateInstance(-constrainX, -constrainY);
1609         tx.concatenate(transform);
1610         return tx;
1611     }
1612 
1613     /**
1614      * Returns the current Transform ignoring the "constrain"
1615      * rectangle.
1616      */
1617     public AffineTransform cloneTransform() {
1618         return new AffineTransform(transform);
1619     }
1620 
1621     /**
1622      * Returns the current Paint in the Graphics2D state.
1623      * @see #setPaint
1624      * @see java.awt.Graphics#setColor
1625      */
1626     public Paint getPaint() {
1627         return paint;
1628     }
1629 
1630     /**
1631      * Returns the current Composite in the Graphics2D state.
1632      * @see #setComposite
1633      */
1634     public Composite getComposite() {
1635         return composite;
1636     }
1637 
1638     public Color getColor() {
1639         return foregroundColor;
1640     }
1641 
1642     /*
1643      * Validate the eargb and pixel fields against the current color.
1644      *
1645      * The eargb field must take into account the extraAlpha
1646      * value of an AlphaComposite.  It may also take into account
1647      * the Fsrc Porter-Duff blending function if such a function is
1648      * a constant (see handling of Clear mode below).  For instance,
1649      * by factoring in the (Fsrc == 0) state of the Clear mode we can
1650      * use a SrcNoEa loop just as easily as a general Alpha loop
1651      * since the math will be the same in both cases.
1652      *
1653      * The pixel field will always be the best pixel data choice for
1654      * the final result of all calculations applied to the eargb field.
1655      *
1656      * Note that this method is only necessary under the following
1657      * conditions:
1658      *     (paintState <= PAINT_ALPHA_COLOR &&
1659      *      compositeState <= COMP_CUSTOM)
1660      * though nothing bad will happen if it is run in other states.
1661      */
1662     final void validateColor() {
1663         int eargb;
1664         if (imageComp == CompositeType.Clear) {
1665             eargb = 0;
1666         } else {
1667             eargb = foregroundColor.getRGB();
1668             if (compositeState <= COMP_ALPHA &&
1669                 imageComp != CompositeType.SrcNoEa &&
1670                 imageComp != CompositeType.SrcOverNoEa)
1671             {
1672                 AlphaComposite alphacomp = (AlphaComposite) composite;
1673                 int a = Math.round(alphacomp.getAlpha() * (eargb >>> 24));
1674                 eargb = (eargb & 0x00ffffff) | (a << 24);
1675             }
1676         }
1677         this.eargb = eargb;
1678         this.pixel = surfaceData.pixelFor(eargb);
1679     }
1680 
1681     public void setColor(Color color) {
1682         if (color == null || color == paint) {
1683             return;
1684         }
1685         this.paint = foregroundColor = color;
1686         validateColor();
1687         if ((eargb >> 24) == -1) {
1688             if (paintState == PAINT_OPAQUECOLOR) {
1689                 return;
1690             }
1691             paintState = PAINT_OPAQUECOLOR;
1692             if (imageComp == CompositeType.SrcOverNoEa) {
1693                 // special case where compState depends on opacity of paint
1694                 compositeState = COMP_ISCOPY;
1695             }
1696         } else {
1697             if (paintState == PAINT_ALPHACOLOR) {
1698                 return;
1699             }
1700             paintState = PAINT_ALPHACOLOR;
1701             if (imageComp == CompositeType.SrcOverNoEa) {
1702                 // special case where compState depends on opacity of paint
1703                 compositeState = COMP_ALPHA;
1704             }
1705         }
1706         validFontInfo = false;
1707         invalidatePipe();
1708     }
1709 
1710     /**
1711      * Sets the background color in this context used for clearing a region.
1712      * When Graphics2D is constructed for a component, the backgroung color is
1713      * inherited from the component. Setting the background color in the
1714      * Graphics2D context only affects the subsequent clearRect() calls and
1715      * not the background color of the component. To change the background
1716      * of the component, use appropriate methods of the component.
1717      * @param color The background color that should be used in
1718      * subsequent calls to clearRect().
1719      * @see getBackground
1720      * @see Graphics.clearRect()
1721      */
1722     public void setBackground(Color color) {
1723         backgroundColor = color;
1724     }
1725 
1726     /**
1727      * Returns the background color used for clearing a region.
1728      * @see setBackground
1729      */
1730     public Color getBackground() {
1731         return backgroundColor;
1732     }
1733 
1734     /**
1735      * Returns the current Stroke in the Graphics2D state.
1736      * @see setStroke
1737      */
1738     public Stroke getStroke() {
1739         return stroke;
1740     }
1741 
1742     public Rectangle getClipBounds() {
1743         Rectangle r;
1744         if (clipState == CLIP_DEVICE) {
1745             r = null;
1746         } else if (transformState <= TRANSFORM_INT_TRANSLATE) {
1747             if (usrClip instanceof Rectangle) {
1748                 r = new Rectangle((Rectangle) usrClip);
1749             } else {
1750                 r = new Rectangle();
1751                 r.setFrame(usrClip.getBounds2D());
1752             }
1753             r.translate(-transX, -transY);
1754         } else {
1755             r = new Rectangle();
1756             r.setFrame(getClip().getBounds2D());
1757         }
1758         return r;
1759     }
1760 
1761     public Rectangle getClipBounds(Rectangle r) {
1762         if (clipState != CLIP_DEVICE) {
1763             if (transformState <= TRANSFORM_INT_TRANSLATE) {
1764                 if (usrClip instanceof Rectangle) {
1765                     r.setBounds((Rectangle) usrClip);
1766                 } else {
1767                     r.setFrame(usrClip.getBounds2D());
1768                 }
1769                 r.translate(-transX, -transY);
1770             } else {
1771                 r.setFrame(getClip().getBounds2D());
1772             }
1773         } else if (r == null) {
1774             throw new NullPointerException("null rectangle parameter");
1775         }
1776         return r;
1777     }
1778 
1779     public boolean hitClip(int x, int y, int width, int height) {
1780         if (width <= 0 || height <= 0) {
1781             return false;
1782         }
1783         if (transformState > TRANSFORM_INT_TRANSLATE) {
1784             // Note: Technically the most accurate test would be to
1785             // raster scan the parallelogram of the transformed rectangle
1786             // and do a span for span hit test against the clip, but for
1787             // speed we approximate the test with a bounding box of the
1788             // transformed rectangle.  The cost of rasterizing the
1789             // transformed rectangle is probably high enough that it is
1790             // not worth doing so to save the caller from having to call
1791             // a rendering method where we will end up discovering the
1792             // same answer in about the same amount of time anyway.
1793             // This logic breaks down if this hit test is being performed
1794             // on the bounds of a group of shapes in which case it might
1795             // be beneficial to be a little more accurate to avoid lots
1796             // of subsequent rendering calls.  In either case, this relaxed
1797             // test should not be significantly less accurate than the
1798             // optimal test for most transforms and so the conservative
1799             // answer should not cause too much extra work.
1800 
1801             double d[] = {
1802                 x, y,
1803                 x+width, y,
1804                 x, y+height,
1805                 x+width, y+height
1806             };
1807             transform.transform(d, 0, d, 0, 4);
1808             x = (int) Math.floor(Math.min(Math.min(d[0], d[2]),
1809                                           Math.min(d[4], d[6])));
1810             y = (int) Math.floor(Math.min(Math.min(d[1], d[3]),
1811                                           Math.min(d[5], d[7])));
1812             width = (int) Math.ceil(Math.max(Math.max(d[0], d[2]),
1813                                              Math.max(d[4], d[6])));
1814             height = (int) Math.ceil(Math.max(Math.max(d[1], d[3]),
1815                                               Math.max(d[5], d[7])));
1816         } else {
1817             x += transX;
1818             y += transY;
1819             width += x;
1820             height += y;
1821         }
1822 
1823         try {
1824             if (!getCompClip().intersectsQuickCheckXYXY(x, y, width, height)) {
1825                 return false;
1826             }
1827         } catch (InvalidPipeException e) {
1828             return false;
1829         }
1830         // REMIND: We could go one step further here and examine the
1831         // non-rectangular clip shape more closely if there is one.
1832         // Since the clip has already been rasterized, the performance
1833         // penalty of doing the scan is probably still within the bounds
1834         // of a good tradeoff between speed and quality of the answer.
1835         return true;
1836     }
1837 
1838     protected void validateCompClip() {
1839         int origClipState = clipState;
1840         if (usrClip == null) {
1841             clipState = CLIP_DEVICE;
1842             clipRegion = devClip;
1843         } else if (usrClip instanceof Rectangle2D) {
1844             clipState = CLIP_RECTANGULAR;
1845             if (usrClip instanceof Rectangle) {
1846                 clipRegion = devClip.getIntersection((Rectangle)usrClip);
1847             } else {
1848                 clipRegion = devClip.getIntersection(usrClip.getBounds());
1849             }
1850         } else {
1851             PathIterator cpi = usrClip.getPathIterator(null);
1852             int box[] = new int[4];
1853             ShapeSpanIterator sr = LoopPipe.getFillSSI(this);
1854             try {
1855                 sr.setOutputArea(devClip);
1856                 sr.appendPath(cpi);
1857                 sr.getPathBox(box);
1858                 Region r = Region.getInstance(box);
1859                 r.appendSpans(sr);
1860                 clipRegion = r;
1861                 clipState =
1862                     r.isRectangular() ? CLIP_RECTANGULAR : CLIP_SHAPE;
1863             } finally {
1864                 sr.dispose();
1865             }
1866         }
1867         if (origClipState != clipState &&
1868             (clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE))
1869         {
1870             validFontInfo = false;
1871             invalidatePipe();
1872         }
1873     }
1874 
1875     static final int NON_RECTILINEAR_TRANSFORM_MASK =
1876         (AffineTransform.TYPE_GENERAL_TRANSFORM |
1877          AffineTransform.TYPE_GENERAL_ROTATION);
1878 
1879     protected Shape transformShape(Shape s) {
1880         if (s == null) {
1881             return null;
1882         }
1883         if (transformState > TRANSFORM_INT_TRANSLATE) {
1884             return transformShape(transform, s);
1885         } else {
1886             return transformShape(transX, transY, s);
1887         }
1888     }
1889 
1890     public Shape untransformShape(Shape s) {
1891         if (s == null) {
1892             return null;
1893         }
1894         if (transformState > TRANSFORM_INT_TRANSLATE) {
1895             try {
1896                 return transformShape(transform.createInverse(), s);
1897             } catch (NoninvertibleTransformException e) {
1898                 return null;
1899             }
1900         } else {
1901             return transformShape(-transX, -transY, s);
1902         }
1903     }
1904 
1905     protected static Shape transformShape(int tx, int ty, Shape s) {
1906         if (s == null) {
1907             return null;
1908         }
1909 
1910         if (s instanceof Rectangle) {
1911             Rectangle r = s.getBounds();
1912             r.translate(tx, ty);
1913             return r;
1914         }
1915         if (s instanceof Rectangle2D) {
1916             Rectangle2D rect = (Rectangle2D) s;
1917             return new Rectangle2D.Double(rect.getX() + tx,
1918                                           rect.getY() + ty,
1919                                           rect.getWidth(),
1920                                           rect.getHeight());
1921         }
1922 
1923         if (tx == 0 && ty == 0) {
1924             return cloneShape(s);
1925         }
1926 
1927         AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty);
1928         return mat.createTransformedShape(s);
1929     }
1930 
1931     protected static Shape transformShape(AffineTransform tx, Shape clip) {
1932         if (clip == null) {
1933             return null;
1934         }
1935 
1936         if (clip instanceof Rectangle2D &&
1937             (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0)
1938         {
1939             Rectangle2D rect = (Rectangle2D) clip;
1940             double matrix[] = new double[4];
1941             matrix[0] = rect.getX();
1942             matrix[1] = rect.getY();
1943             matrix[2] = matrix[0] + rect.getWidth();
1944             matrix[3] = matrix[1] + rect.getHeight();
1945             tx.transform(matrix, 0, matrix, 0, 2);
1946             fixRectangleOrientation(matrix, rect);
1947             return new Rectangle2D.Double(matrix[0], matrix[1],
1948                                           matrix[2] - matrix[0],
1949                                           matrix[3] - matrix[1]);
1950         }
1951 
1952         if (tx.isIdentity()) {
1953             return cloneShape(clip);
1954         }
1955 
1956         return tx.createTransformedShape(clip);
1957     }
1958 
1959     /**
1960      * Sets orientation of the rectangle according to the clip.
1961      */
1962     private static void fixRectangleOrientation(double[] m, Rectangle2D clip) {
1963         if (clip.getWidth() > 0 != (m[2] - m[0] > 0)) {
1964             double t = m[0];
1965             m[0] = m[2];
1966             m[2] = t;
1967         }
1968         if (clip.getHeight() > 0 != (m[3] - m[1] > 0)) {
1969             double t = m[1];
1970             m[1] = m[3];
1971             m[3] = t;
1972         }
1973     }
1974 
1975     public void clipRect(int x, int y, int w, int h) {
1976         clip(new Rectangle(x, y, w, h));
1977     }
1978 
1979     public void setClip(int x, int y, int w, int h) {
1980         setClip(new Rectangle(x, y, w, h));
1981     }
1982 
1983     public Shape getClip() {
1984         return untransformShape(usrClip);
1985     }
1986 
1987     public void setClip(Shape sh) {
1988         usrClip = transformShape(sh);
1989         validateCompClip();
1990     }
1991 
1992     /**
1993      * Intersects the current clip with the specified Path and sets the
1994      * current clip to the resulting intersection. The clip is transformed
1995      * with the current transform in the Graphics2D state before being
1996      * intersected with the current clip. This method is used to make the
1997      * current clip smaller. To make the clip larger, use any setClip method.
1998      * @param p The Path to be intersected with the current clip.
1999      */
2000     public void clip(Shape s) {
2001         s = transformShape(s);
2002         if (usrClip != null) {
2003             s = intersectShapes(usrClip, s, true, true);
2004         }
2005         usrClip = s;
2006         validateCompClip();
2007     }
2008 
2009     public void setPaintMode() {
2010         setComposite(AlphaComposite.SrcOver);
2011     }
2012 
2013     public void setXORMode(Color c) {
2014         if (c == null) {
2015             throw new IllegalArgumentException("null XORColor");
2016         }
2017         setComposite(new XORComposite(c, surfaceData));
2018     }
2019 
2020     Blit lastCAblit;
2021     Composite lastCAcomp;
2022 
2023     public void copyArea(int x, int y, int w, int h, int dx, int dy) {
2024         try {
2025             doCopyArea(x, y, w, h, dx, dy);
2026         } catch (InvalidPipeException e) {
2027             try {
2028                 revalidateAll();
2029                 doCopyArea(x, y, w, h, dx, dy);
2030             } catch (InvalidPipeException e2) {
2031                 // Still catching the exception; we are not yet ready to
2032                 // validate the surfaceData correctly.  Fail for now and
2033                 // try again next time around.
2034             }
2035         } finally {
2036             surfaceData.markDirty();
2037         }
2038     }
2039 
2040     private void doCopyArea(int x, int y, int w, int h, int dx, int dy) {
2041         if (w <= 0 || h <= 0) {
2042             return;
2043         }
2044         SurfaceData theData = surfaceData;
2045         if (theData.copyArea(this, x, y, w, h, dx, dy)) {
2046             return;
2047         }
2048         if (transformState >= TRANSFORM_TRANSLATESCALE) {
2049             throw new InternalError("transformed copyArea not implemented yet");
2050         }
2051         // REMIND: This method does not deal with missing data from the
2052         // source object (i.e. it does not send exposure events...)
2053 
2054         Region clip = getCompClip();
2055 
2056         Composite comp = composite;
2057         if (lastCAcomp != comp) {
2058             SurfaceType dsttype = theData.getSurfaceType();
2059             CompositeType comptype = imageComp;
2060             if (CompositeType.SrcOverNoEa.equals(comptype) &&
2061                 theData.getTransparency() == Transparency.OPAQUE)
2062             {
2063                 comptype = CompositeType.SrcNoEa;
2064             }
2065             lastCAblit = Blit.locate(dsttype, comptype, dsttype);
2066             lastCAcomp = comp;
2067         }
2068 
2069         x += transX;
2070         y += transY;
2071 
2072         Blit ob = lastCAblit;
2073         if (dy == 0 && dx > 0 && dx < w) {
2074             while (w > 0) {
2075                 int partW = Math.min(w, dx);
2076                 w -= partW;
2077                 int sx = x + w;
2078                 ob.Blit(theData, theData, comp, clip,
2079                         sx, y, sx+dx, y+dy, partW, h);
2080             }
2081             return;
2082         }
2083         if (dy > 0 && dy < h && dx > -w && dx < w) {
2084             while (h > 0) {
2085                 int partH = Math.min(h, dy);
2086                 h -= partH;
2087                 int sy = y + h;
2088                 ob.Blit(theData, theData, comp, clip,
2089                         x, sy, x+dx, sy+dy, w, partH);
2090             }
2091             return;
2092         }
2093         ob.Blit(theData, theData, comp, clip, x, y, x+dx, y+dy, w, h);
2094     }
2095 
2096     /*
2097     public void XcopyArea(int x, int y, int w, int h, int dx, int dy) {
2098         Rectangle rect = new Rectangle(x, y, w, h);
2099         rect = transformBounds(rect, transform);
2100         Point2D    point = new Point2D.Float(dx, dy);
2101         Point2D    root  = new Point2D.Float(0, 0);
2102         point = transform.transform(point, point);
2103         root  = transform.transform(root, root);
2104         int fdx = (int)(point.getX()-root.getX());
2105         int fdy = (int)(point.getY()-root.getY());
2106 
2107         Rectangle r = getCompBounds().intersection(rect.getBounds());
2108 
2109         if (r.isEmpty()) {
2110             return;
2111         }
2112 
2113         // Begin Rasterizer for Clip Shape
2114         boolean skipClip = true;
2115         byte[] clipAlpha = null;
2116 
2117         if (clipState == CLIP_SHAPE) {
2118 
2119             int box[] = new int[4];
2120 
2121             clipRegion.getBounds(box);
2122             Rectangle devR = new Rectangle(box[0], box[1],
2123                                            box[2] - box[0],
2124                                            box[3] - box[1]);
2125             if (!devR.isEmpty()) {
2126                 OutputManager mgr = getOutputManager();
2127                 RegionIterator ri = clipRegion.getIterator();
2128                 while (ri.nextYRange(box)) {
2129                     int spany = box[1];
2130                     int spanh = box[3] - spany;
2131                     while (ri.nextXBand(box)) {
2132                         int spanx = box[0];
2133                         int spanw = box[2] - spanx;
2134                         mgr.copyArea(this, null,
2135                                      spanw, 0,
2136                                      spanx, spany,
2137                                      spanw, spanh,
2138                                      fdx, fdy,
2139                                      null);
2140                     }
2141                 }
2142             }
2143             return;
2144         }
2145         // End Rasterizer for Clip Shape
2146 
2147         getOutputManager().copyArea(this, null,
2148                                     r.width, 0,
2149                                     r.x, r.y, r.width,
2150                                     r.height, fdx, fdy,
2151                                     null);
2152     }
2153     */
2154 
2155     public void drawLine(int x1, int y1, int x2, int y2) {
2156         try {
2157             drawpipe.drawLine(this, x1, y1, x2, y2);
2158         } catch (InvalidPipeException e) {
2159             try {
2160                 revalidateAll();
2161                 drawpipe.drawLine(this, x1, y1, x2, y2);
2162             } catch (InvalidPipeException e2) {
2163                 // Still catching the exception; we are not yet ready to
2164                 // validate the surfaceData correctly.  Fail for now and
2165                 // try again next time around.
2166             }
2167         } finally {
2168             surfaceData.markDirty();
2169         }
2170     }
2171 
2172     public void drawRoundRect(int x, int y, int w, int h, int arcW, int arcH) {
2173         try {
2174             drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);
2175         } catch (InvalidPipeException e) {
2176             try {
2177                 revalidateAll();
2178                 drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);
2179             } catch (InvalidPipeException e2) {
2180                 // Still catching the exception; we are not yet ready to
2181                 // validate the surfaceData correctly.  Fail for now and
2182                 // try again next time around.
2183             }
2184         } finally {
2185             surfaceData.markDirty();
2186         }
2187     }
2188 
2189     public void fillRoundRect(int x, int y, int w, int h, int arcW, int arcH) {
2190         try {
2191             fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);
2192         } catch (InvalidPipeException e) {
2193             try {
2194                 revalidateAll();
2195                 fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);
2196             } catch (InvalidPipeException e2) {
2197                 // Still catching the exception; we are not yet ready to
2198                 // validate the surfaceData correctly.  Fail for now and
2199                 // try again next time around.
2200             }
2201         } finally {
2202             surfaceData.markDirty();
2203         }
2204     }
2205 
2206     public void drawOval(int x, int y, int w, int h) {
2207         try {
2208             drawpipe.drawOval(this, x, y, w, h);
2209         } catch (InvalidPipeException e) {
2210             try {
2211                 revalidateAll();
2212                 drawpipe.drawOval(this, x, y, w, h);
2213             } catch (InvalidPipeException e2) {
2214                 // Still catching the exception; we are not yet ready to
2215                 // validate the surfaceData correctly.  Fail for now and
2216                 // try again next time around.
2217             }
2218         } finally {
2219             surfaceData.markDirty();
2220         }
2221     }
2222 
2223     public void fillOval(int x, int y, int w, int h) {
2224         try {
2225             fillpipe.fillOval(this, x, y, w, h);
2226         } catch (InvalidPipeException e) {
2227             try {
2228                 revalidateAll();
2229                 fillpipe.fillOval(this, x, y, w, h);
2230             } catch (InvalidPipeException e2) {
2231                 // Still catching the exception; we are not yet ready to
2232                 // validate the surfaceData correctly.  Fail for now and
2233                 // try again next time around.
2234             }
2235         } finally {
2236             surfaceData.markDirty();
2237         }
2238     }
2239 
2240     public void drawArc(int x, int y, int w, int h,
2241                         int startAngl, int arcAngl) {
2242         try {
2243             drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);
2244         } catch (InvalidPipeException e) {
2245             try {
2246                 revalidateAll();
2247                 drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);
2248             } catch (InvalidPipeException e2) {
2249                 // Still catching the exception; we are not yet ready to
2250                 // validate the surfaceData correctly.  Fail for now and
2251                 // try again next time around.
2252             }
2253         } finally {
2254             surfaceData.markDirty();
2255         }
2256     }
2257 
2258     public void fillArc(int x, int y, int w, int h,
2259                         int startAngl, int arcAngl) {
2260         try {
2261             fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);
2262         } catch (InvalidPipeException e) {
2263             try {
2264                 revalidateAll();
2265                 fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);
2266             } catch (InvalidPipeException e2) {
2267                 // Still catching the exception; we are not yet ready to
2268                 // validate the surfaceData correctly.  Fail for now and
2269                 // try again next time around.
2270             }
2271         } finally {
2272             surfaceData.markDirty();
2273         }
2274     }
2275 
2276     public void drawPolyline(int xPoints[], int yPoints[], int nPoints) {
2277         try {
2278             drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);
2279         } catch (InvalidPipeException e) {
2280             try {
2281                 revalidateAll();
2282                 drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);
2283             } catch (InvalidPipeException e2) {
2284                 // Still catching the exception; we are not yet ready to
2285                 // validate the surfaceData correctly.  Fail for now and
2286                 // try again next time around.
2287             }
2288         } finally {
2289             surfaceData.markDirty();
2290         }
2291     }
2292 
2293     public void drawPolygon(int xPoints[], int yPoints[], int nPoints) {
2294         try {
2295             drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);
2296         } catch (InvalidPipeException e) {
2297             try {
2298                 revalidateAll();
2299                 drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);
2300             } catch (InvalidPipeException e2) {
2301                 // Still catching the exception; we are not yet ready to
2302                 // validate the surfaceData correctly.  Fail for now and
2303                 // try again next time around.
2304             }
2305         } finally {
2306             surfaceData.markDirty();
2307         }
2308     }
2309 
2310     public void fillPolygon(int xPoints[], int yPoints[], int nPoints) {
2311         try {
2312             fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);
2313         } catch (InvalidPipeException e) {
2314             try {
2315                 revalidateAll();
2316                 fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);
2317             } catch (InvalidPipeException e2) {
2318                 // Still catching the exception; we are not yet ready to
2319                 // validate the surfaceData correctly.  Fail for now and
2320                 // try again next time around.
2321             }
2322         } finally {
2323             surfaceData.markDirty();
2324         }
2325     }
2326 
2327     public void drawRect (int x, int y, int w, int h) {
2328         try {
2329             drawpipe.drawRect(this, x, y, w, h);
2330         } catch (InvalidPipeException e) {
2331             try {
2332                 revalidateAll();
2333                 drawpipe.drawRect(this, x, y, w, h);
2334             } catch (InvalidPipeException e2) {
2335                 // Still catching the exception; we are not yet ready to
2336                 // validate the surfaceData correctly.  Fail for now and
2337                 // try again next time around.
2338             }
2339         } finally {
2340             surfaceData.markDirty();
2341         }
2342     }
2343 
2344     public void fillRect (int x, int y, int w, int h) {
2345         try {
2346             fillpipe.fillRect(this, x, y, w, h);
2347         } catch (InvalidPipeException e) {
2348             try {
2349                 revalidateAll();
2350                 fillpipe.fillRect(this, x, y, w, h);
2351             } catch (InvalidPipeException e2) {
2352                 // Still catching the exception; we are not yet ready to
2353                 // validate the surfaceData correctly.  Fail for now and
2354                 // try again next time around.
2355             }
2356         } finally {
2357             surfaceData.markDirty();
2358         }
2359     }
2360 
2361     private void revalidateAll() {
2362         try {
2363             // REMIND: This locking needs to be done around the
2364             // caller of this method so that the pipe stays valid
2365             // long enough to call the new primitive.
2366             // REMIND: No locking yet in screen SurfaceData objects!
2367             // surfaceData.lock();
2368             surfaceData = surfaceData.getReplacement();
2369             if (surfaceData == null) {
2370                 surfaceData = NullSurfaceData.theInstance;
2371             }
2372 
2373             // this will recalculate the composite clip
2374             setDevClip(surfaceData.getBounds());
2375 
2376             if (paintState <= PAINT_ALPHACOLOR) {
2377                 validateColor();
2378             }
2379             if (composite instanceof XORComposite) {
2380                 Color c = ((XORComposite) composite).getXorColor();
2381                 setComposite(new XORComposite(c, surfaceData));
2382             }
2383             validatePipe();
2384         } finally {
2385             // REMIND: No locking yet in screen SurfaceData objects!
2386             // surfaceData.unlock();
2387         }
2388     }
2389 
2390     public void clearRect(int x, int y, int w, int h) {
2391         // REMIND: has some "interesting" consequences if threads are
2392         // not synchronized
2393         Composite c = composite;
2394         Paint p = paint;
2395         setComposite(AlphaComposite.Src);
2396         setColor(getBackground());
2397         fillRect(x, y, w, h);
2398         setPaint(p);
2399         setComposite(c);
2400     }
2401 
2402     /**
2403      * Strokes the outline of a Path using the settings of the current
2404      * graphics state.  The rendering attributes applied include the
2405      * clip, transform, paint or color, composite and stroke attributes.
2406      * @param p The path to be drawn.
2407      * @see #setStroke
2408      * @see #setPaint
2409      * @see java.awt.Graphics#setColor
2410      * @see #transform
2411      * @see #setTransform
2412      * @see #clip
2413      * @see #setClip
2414      * @see #setComposite
2415      */
2416     public void draw(Shape s) {
2417         try {
2418             shapepipe.draw(this, s);
2419         } catch (InvalidPipeException e) {
2420             try {
2421                 revalidateAll();
2422                 shapepipe.draw(this, s);
2423             } catch (InvalidPipeException e2) {
2424                 // Still catching the exception; we are not yet ready to
2425                 // validate the surfaceData correctly.  Fail for now and
2426                 // try again next time around.
2427             }
2428         } finally {
2429             surfaceData.markDirty();
2430         }
2431     }
2432 
2433 
2434     /**
2435      * Fills the interior of a Path using the settings of the current
2436      * graphics state. The rendering attributes applied include the
2437      * clip, transform, paint or color, and composite.
2438      * @see #setPaint
2439      * @see java.awt.Graphics#setColor
2440      * @see #transform
2441      * @see #setTransform
2442      * @see #setComposite
2443      * @see #clip
2444      * @see #setClip
2445      */
2446     public void fill(Shape s) {
2447         try {
2448             shapepipe.fill(this, s);
2449         } catch (InvalidPipeException e) {
2450             try {
2451                 revalidateAll();
2452                 shapepipe.fill(this, s);
2453             } catch (InvalidPipeException e2) {
2454                 // Still catching the exception; we are not yet ready to
2455                 // validate the surfaceData correctly.  Fail for now and
2456                 // try again next time around.
2457             }
2458         } finally {
2459             surfaceData.markDirty();
2460         }
2461     }
2462 
2463     /**
2464      * Returns true if the given AffineTransform is an integer
2465      * translation.
2466      */
2467     private static boolean isIntegerTranslation(AffineTransform xform) {
2468         if (xform.isIdentity()) {
2469             return true;
2470         }
2471         if (xform.getType() == AffineTransform.TYPE_TRANSLATION) {
2472             double tx = xform.getTranslateX();
2473             double ty = xform.getTranslateY();
2474             return (tx == (int)tx && ty == (int)ty);
2475         }
2476         return false;
2477     }
2478 
2479     /**
2480      * Returns the index of the tile corresponding to the supplied position
2481      * given the tile grid offset and size along the same axis.
2482      */
2483     private static int getTileIndex(int p, int tileGridOffset, int tileSize) {
2484         p -= tileGridOffset;
2485         if (p < 0) {
2486             p += 1 - tileSize;          // force round to -infinity (ceiling)
2487         }
2488         return p/tileSize;
2489     }
2490 
2491     /**
2492      * Returns a rectangle in image coordinates that may be required
2493      * in order to draw the given image into the given clipping region
2494      * through a pair of AffineTransforms.  In addition, horizontal and
2495      * vertical padding factors for antialising and interpolation may
2496      * be used.
2497      */
2498     private static Rectangle getImageRegion(RenderedImage img,
2499                                             Region compClip,
2500                                             AffineTransform transform,
2501                                             AffineTransform xform,
2502                                             int padX, int padY) {
2503         Rectangle imageRect =
2504             new Rectangle(img.getMinX(), img.getMinY(),
2505                           img.getWidth(), img.getHeight());
2506 
2507         Rectangle result = null;
2508         try {
2509             double p[] = new double[8];
2510             p[0] = p[2] = compClip.getLoX();
2511             p[4] = p[6] = compClip.getHiX();
2512             p[1] = p[5] = compClip.getLoY();
2513             p[3] = p[7] = compClip.getHiY();
2514 
2515             // Inverse transform the output bounding rect
2516             transform.inverseTransform(p, 0, p, 0, 4);
2517             xform.inverseTransform(p, 0, p, 0, 4);
2518 
2519             // Determine a bounding box for the inverse transformed region
2520             double x0,x1,y0,y1;
2521             x0 = x1 = p[0];
2522             y0 = y1 = p[1];
2523 
2524             for (int i = 2; i < 8; ) {
2525                 double pt = p[i++];
2526                 if (pt < x0)  {
2527                     x0 = pt;
2528                 } else if (pt > x1) {
2529                     x1 = pt;
2530                 }
2531                 pt = p[i++];
2532                 if (pt < y0)  {
2533                     y0 = pt;
2534                 } else if (pt > y1) {
2535                     y1 = pt;
2536                 }
2537             }
2538 
2539             // This is padding for anti-aliasing and such.  It may
2540             // be more than is needed.
2541             int x = (int)x0 - padX;
2542             int w = (int)(x1 - x0 + 2*padX);
2543             int y = (int)y0 - padY;
2544             int h = (int)(y1 - y0 + 2*padY);
2545 
2546             Rectangle clipRect = new Rectangle(x,y,w,h);
2547             result = clipRect.intersection(imageRect);
2548         } catch (NoninvertibleTransformException nte) {
2549             // Worst case bounds are the bounds of the image.
2550             result = imageRect;
2551         }
2552 
2553         return result;
2554     }
2555 
2556     /**
2557      * Draws an image, applying a transform from image space into user space
2558      * before drawing.
2559      * The transformation from user space into device space is done with
2560      * the current transform in the Graphics2D.
2561      * The given transformation is applied to the image before the
2562      * transform attribute in the Graphics2D state is applied.
2563      * The rendering attributes applied include the clip, transform,
2564      * and composite attributes. Note that the result is
2565      * undefined, if the given transform is noninvertible.
2566      * @param img The image to be drawn. Does nothing if img is null.
2567      * @param xform The transformation from image space into user space.
2568      * @see #transform
2569      * @see #setTransform
2570      * @see #setComposite
2571      * @see #clip
2572      * @see #setClip
2573      */
2574     public void drawRenderedImage(RenderedImage img,
2575                                   AffineTransform xform) {
2576 
2577         if (img == null) {
2578             return;
2579         }
2580 
2581         // BufferedImage case: use a simple drawImage call
2582         if (img instanceof BufferedImage) {
2583             BufferedImage bufImg = (BufferedImage)img;
2584             drawImage(bufImg,xform,null);
2585             return;
2586         }
2587 
2588         // transformState tracks the state of transform and
2589         // transX, transY contain the integer casts of the
2590         // translation factors
2591         boolean isIntegerTranslate =
2592             (transformState <= TRANSFORM_INT_TRANSLATE) &&
2593             isIntegerTranslation(xform);
2594 
2595         // Include padding for interpolation/antialiasing if necessary
2596         int pad = isIntegerTranslate ? 0 : 3;
2597 
2598         Region clip;
2599         try {
2600             clip = getCompClip();
2601         } catch (InvalidPipeException e) {
2602             return;
2603         }
2604 
2605         // Determine the region of the image that may contribute to
2606         // the clipped drawing area
2607         Rectangle region = getImageRegion(img,
2608                                           clip,
2609                                           transform,
2610                                           xform,
2611                                           pad, pad);
2612         if (region.width <= 0 || region.height <= 0) {
2613             return;
2614         }
2615 
2616         // Attempt to optimize integer translation of tiled images.
2617         // Although theoretically we are O.K. if the concatenation of
2618         // the user transform and the device transform is an integer
2619         // translation, we'll play it safe and only optimize the case
2620         // where both are integer translations.
2621         if (isIntegerTranslate) {
2622             // Use optimized code
2623             // Note that drawTranslatedRenderedImage calls copyImage
2624             // which takes the user space to device space transform into
2625             // account, but we need to provide the image space to user space
2626             // translations.
2627 
2628             drawTranslatedRenderedImage(img, region,
2629                                         (int) xform.getTranslateX(),
2630                                         (int) xform.getTranslateY());
2631             return;
2632         }
2633 
2634         // General case: cobble the necessary region into a single Raster
2635         Raster raster = img.getData(region);
2636 
2637         // Make a new Raster with the same contents as raster
2638         // but starting at (0, 0).  This raster is thus in the same
2639         // coordinate system as the SampleModel of the original raster.
2640         WritableRaster wRaster =
2641               Raster.createWritableRaster(raster.getSampleModel(),
2642                                           raster.getDataBuffer(),
2643                                           null);
2644 
2645         // If the original raster was in a different coordinate
2646         // system than its SampleModel, we need to perform an
2647         // additional translation in order to get the (minX, minY)
2648         // pixel of raster to be pixel (0, 0) of wRaster.  We also
2649         // have to have the correct width and height.
2650         int minX = raster.getMinX();
2651         int minY = raster.getMinY();
2652         int width = raster.getWidth();
2653         int height = raster.getHeight();
2654         int px = minX - raster.getSampleModelTranslateX();
2655         int py = minY - raster.getSampleModelTranslateY();
2656         if (px != 0 || py != 0 || width != wRaster.getWidth() ||
2657             height != wRaster.getHeight()) {
2658             wRaster =
2659                 wRaster.createWritableChild(px,
2660                                             py,
2661                                             width,
2662                                             height,
2663                                             0, 0,
2664                                             null);
2665         }
2666 
2667         // Now we have a BufferedImage starting at (0, 0)
2668         // with the same contents that started at (minX, minY)
2669         // in raster.  So we must draw the BufferedImage with a
2670         // translation of (minX, minY).
2671         AffineTransform transXform = (AffineTransform)xform.clone();
2672         transXform.translate(minX, minY);
2673 
2674         ColorModel cm = img.getColorModel();
2675         BufferedImage bufImg = new BufferedImage(cm,
2676                                                  wRaster,
2677                                                  cm.isAlphaPremultiplied(),
2678                                                  null);
2679         drawImage(bufImg, transXform, null);
2680     }
2681 
2682     /**
2683      * Intersects <code>destRect</code> with <code>clip</code> and
2684      * overwrites <code>destRect</code> with the result.
2685      * Returns false if the intersection was empty, true otherwise.
2686      */
2687     private boolean clipTo(Rectangle destRect, Rectangle clip) {
2688         int x1 = Math.max(destRect.x, clip.x);
2689         int x2 = Math.min(destRect.x + destRect.width, clip.x + clip.width);
2690         int y1 = Math.max(destRect.y, clip.y);
2691         int y2 = Math.min(destRect.y + destRect.height, clip.y + clip.height);
2692         if (((x2 - x1) < 0) || ((y2 - y1) < 0)) {
2693             destRect.width = -1; // Set both just to be safe
2694             destRect.height = -1;
2695             return false;
2696         } else {
2697             destRect.x = x1;
2698             destRect.y = y1;
2699             destRect.width = x2 - x1;
2700             destRect.height = y2 - y1;
2701             return true;
2702         }
2703     }
2704 
2705     /**
2706      * Draw a portion of a RenderedImage tile-by-tile with a given
2707      * integer image to user space translation.  The user to
2708      * device transform must also be an integer translation.
2709      */
2710     private void drawTranslatedRenderedImage(RenderedImage img,
2711                                              Rectangle region,
2712                                              int i2uTransX,
2713                                              int i2uTransY) {
2714         // Cache tile grid info
2715         int tileGridXOffset = img.getTileGridXOffset();
2716         int tileGridYOffset = img.getTileGridYOffset();
2717         int tileWidth = img.getTileWidth();
2718         int tileHeight = img.getTileHeight();
2719 
2720         // Determine the tile index extrema in each direction
2721         int minTileX =
2722             getTileIndex(region.x, tileGridXOffset, tileWidth);
2723         int minTileY =
2724             getTileIndex(region.y, tileGridYOffset, tileHeight);
2725         int maxTileX =
2726             getTileIndex(region.x + region.width - 1,
2727                          tileGridXOffset, tileWidth);
2728         int maxTileY =
2729             getTileIndex(region.y + region.height - 1,
2730                          tileGridYOffset, tileHeight);
2731 
2732         // Create a single ColorModel to use for all BufferedImages
2733         ColorModel colorModel = img.getColorModel();
2734 
2735         // Reuse the same Rectangle for each iteration
2736         Rectangle tileRect = new Rectangle();
2737 
2738         for (int ty = minTileY; ty <= maxTileY; ty++) {
2739             for (int tx = minTileX; tx <= maxTileX; tx++) {
2740                 // Get the current tile.
2741                 Raster raster = img.getTile(tx, ty);
2742 
2743                 // Fill in tileRect with the tile bounds
2744                 tileRect.x = tx*tileWidth + tileGridXOffset;
2745                 tileRect.y = ty*tileHeight + tileGridYOffset;
2746                 tileRect.width = tileWidth;
2747                 tileRect.height = tileHeight;
2748 
2749                 // Clip the tile against the image bounds and
2750                 // backwards mapped clip region
2751                 // The result can't be empty
2752                 clipTo(tileRect, region);
2753 
2754                 // Create a WritableRaster containing the tile
2755                 WritableRaster wRaster = null;
2756                 if (raster instanceof WritableRaster) {
2757                     wRaster = (WritableRaster)raster;
2758                 } else {
2759                     // Create a WritableRaster in the same coordinate system
2760                     // as the original raster.
2761                     wRaster =
2762                         Raster.createWritableRaster(raster.getSampleModel(),
2763                                                     raster.getDataBuffer(),
2764                                                     null);
2765                 }
2766 
2767                 // Translate wRaster to start at (0, 0) and to contain
2768                 // only the relevent portion of the tile
2769                 wRaster = wRaster.createWritableChild(tileRect.x, tileRect.y,
2770                                                       tileRect.width,
2771                                                       tileRect.height,
2772                                                       0, 0,
2773                                                       null);
2774 
2775                 // Wrap wRaster in a BufferedImage
2776                 BufferedImage bufImg =
2777                     new BufferedImage(colorModel,
2778                                       wRaster,
2779                                       colorModel.isAlphaPremultiplied(),
2780                                       null);
2781                 // Now we have a BufferedImage starting at (0, 0) that
2782                 // represents data from a Raster starting at
2783                 // (tileRect.x, tileRect.y).  Additionally, it needs
2784                 // to be translated by (i2uTransX, i2uTransY).  We call
2785                 // copyImage to draw just the region of interest
2786                 // without needing to create a child image.
2787                 copyImage(bufImg, tileRect.x + i2uTransX,
2788                           tileRect.y + i2uTransY, 0, 0, tileRect.width,
2789                           tileRect.height, null, null);
2790             }
2791         }
2792     }
2793 
2794     public void drawRenderableImage(RenderableImage img,
2795                                     AffineTransform xform) {
2796 
2797         if (img == null) {
2798             return;
2799         }
2800 
2801         AffineTransform pipeTransform = transform;
2802         AffineTransform concatTransform = new AffineTransform(xform);
2803         concatTransform.concatenate(pipeTransform);
2804         AffineTransform reverseTransform;
2805 
2806         RenderContext rc = new RenderContext(concatTransform);
2807 
2808         try {
2809             reverseTransform = pipeTransform.createInverse();
2810         } catch (NoninvertibleTransformException nte) {
2811             rc = new RenderContext(pipeTransform);
2812             reverseTransform = new AffineTransform();
2813         }
2814 
2815         RenderedImage rendering = img.createRendering(rc);
2816         drawRenderedImage(rendering,reverseTransform);
2817     }
2818 
2819 
2820 
2821     /*
2822      * Transform the bounding box of the BufferedImage
2823      */
2824     protected Rectangle transformBounds(Rectangle rect,
2825                                         AffineTransform tx) {
2826         if (tx.isIdentity()) {
2827             return rect;
2828         }
2829 
2830         Shape s = transformShape(tx, rect);
2831         return s.getBounds();
2832     }
2833 
2834     // text rendering methods
2835     public void drawString(String str, int x, int y) {
2836         if (str == null) {
2837             throw new NullPointerException("String is null");
2838         }
2839 
2840         if (font.hasLayoutAttributes()) {
2841             if (str.length() == 0) {
2842                 return;
2843             }
2844             new TextLayout(str, font, getFontRenderContext()).draw(this, x, y);
2845             return;
2846         }
2847 
2848         try {
2849             textpipe.drawString(this, str, x, y);
2850         } catch (InvalidPipeException e) {
2851             try {
2852                 revalidateAll();
2853                 textpipe.drawString(this, str, x, y);
2854             } catch (InvalidPipeException e2) {
2855                 // Still catching the exception; we are not yet ready to
2856                 // validate the surfaceData correctly.  Fail for now and
2857                 // try again next time around.
2858             }
2859         } finally {
2860             surfaceData.markDirty();
2861         }
2862     }
2863 
2864     public void drawString(String str, float x, float y) {
2865         if (str == null) {
2866             throw new NullPointerException("String is null");
2867         }
2868 
2869         if (font.hasLayoutAttributes()) {
2870             if (str.length() == 0) {
2871                 return;
2872             }
2873             new TextLayout(str, font, getFontRenderContext()).draw(this, x, y);
2874             return;
2875         }
2876 
2877         try {
2878             textpipe.drawString(this, str, x, y);
2879         } catch (InvalidPipeException e) {
2880             try {
2881                 revalidateAll();
2882                 textpipe.drawString(this, str, x, y);
2883             } catch (InvalidPipeException e2) {
2884                 // Still catching the exception; we are not yet ready to
2885                 // validate the surfaceData correctly.  Fail for now and
2886                 // try again next time around.
2887             }
2888         } finally {
2889             surfaceData.markDirty();
2890         }
2891     }
2892 
2893     public void drawString(AttributedCharacterIterator iterator,
2894                            int x, int y) {
2895         if (iterator == null) {
2896             throw new NullPointerException("AttributedCharacterIterator is null");
2897         }
2898         if (iterator.getBeginIndex() == iterator.getEndIndex()) {
2899             return; /* nothing to draw */
2900         }
2901         TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2902         tl.draw(this, (float) x, (float) y);
2903     }
2904 
2905     public void drawString(AttributedCharacterIterator iterator,
2906                            float x, float y) {
2907         if (iterator == null) {
2908             throw new NullPointerException("AttributedCharacterIterator is null");
2909         }
2910         if (iterator.getBeginIndex() == iterator.getEndIndex()) {
2911             return; /* nothing to draw */
2912         }
2913         TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2914         tl.draw(this, x, y);
2915     }
2916 
2917     public void drawGlyphVector(GlyphVector gv, float x, float y)
2918     {
2919         if (gv == null) {
2920             throw new NullPointerException("GlyphVector is null");
2921         }
2922 
2923         try {
2924             textpipe.drawGlyphVector(this, gv, x, y);
2925         } catch (InvalidPipeException e) {
2926             try {
2927                 revalidateAll();
2928                 textpipe.drawGlyphVector(this, gv, x, y);
2929             } catch (InvalidPipeException e2) {
2930                 // Still catching the exception; we are not yet ready to
2931                 // validate the surfaceData correctly.  Fail for now and
2932                 // try again next time around.
2933             }
2934         } finally {
2935             surfaceData.markDirty();
2936         }
2937     }
2938 
2939     public void drawChars(char data[], int offset, int length, int x, int y) {
2940 
2941         if (data == null) {
2942             throw new NullPointerException("char data is null");
2943         }
2944         if (offset < 0 || length < 0 || offset + length > data.length) {
2945             throw new ArrayIndexOutOfBoundsException("bad offset/length");
2946         }
2947         if (font.hasLayoutAttributes()) {
2948             if (data.length == 0) {
2949                 return;
2950             }
2951             new TextLayout(new String(data, offset, length),
2952                            font, getFontRenderContext()).draw(this, x, y);
2953             return;
2954         }
2955 
2956         try {
2957             textpipe.drawChars(this, data, offset, length, x, y);
2958         } catch (InvalidPipeException e) {
2959             try {
2960                 revalidateAll();
2961                 textpipe.drawChars(this, data, offset, length, x, y);
2962             } catch (InvalidPipeException e2) {
2963                 // Still catching the exception; we are not yet ready to
2964                 // validate the surfaceData correctly.  Fail for now and
2965                 // try again next time around.
2966             }
2967         } finally {
2968             surfaceData.markDirty();
2969         }
2970     }
2971 
2972     public void drawBytes(byte data[], int offset, int length, int x, int y) {
2973         if (data == null) {
2974             throw new NullPointerException("byte data is null");
2975         }
2976         if (offset < 0 || length < 0 || offset + length > data.length) {
2977             throw new ArrayIndexOutOfBoundsException("bad offset/length");
2978         }
2979         /* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */
2980         char chData[] = new char[length];
2981         for (int i = length; i-- > 0; ) {
2982             chData[i] = (char)(data[i+offset] & 0xff);
2983         }
2984         if (font.hasLayoutAttributes()) {
2985             if (data.length == 0) {
2986                 return;
2987             }
2988             new TextLayout(new String(chData),
2989                            font, getFontRenderContext()).draw(this, x, y);
2990             return;
2991         }
2992 
2993         try {
2994             textpipe.drawChars(this, chData, 0, length, x, y);
2995         } catch (InvalidPipeException e) {
2996             try {
2997                 revalidateAll();
2998                 textpipe.drawChars(this, chData, 0, length, x, y);
2999             } catch (InvalidPipeException e2) {
3000                 // Still catching the exception; we are not yet ready to
3001                 // validate the surfaceData correctly.  Fail for now and
3002                 // try again next time around.
3003             }
3004         } finally {
3005             surfaceData.markDirty();
3006         }
3007     }
3008 // end of text rendering methods
3009 
3010     /**
3011      * Draws an image scaled to x,y,w,h in nonblocking mode with a
3012      * callback object.
3013      */
3014     public boolean drawImage(Image img, int x, int y, int width, int height,
3015                              ImageObserver observer) {
3016         return drawImage(img, x, y, width, height, null, observer);
3017     }
3018 
3019     /**
3020      * Not part of the advertised API but a useful utility method
3021      * to call internally.  This is for the case where we are
3022      * drawing to/from given coordinates using a given width/height,
3023      * but we guarantee that the weidth/height of the src and dest
3024      * areas are equal (no scale needed).
3025      */
3026     public boolean copyImage(Image img, int dx, int dy, int sx, int sy,
3027                              int width, int height, Color bgcolor,
3028                              ImageObserver observer) {
3029         try {
3030             return imagepipe.copyImage(this, img, dx, dy, sx, sy,
3031                                        width, height, bgcolor, observer);
3032         } catch (InvalidPipeException e) {
3033             try {
3034                 revalidateAll();
3035                 return imagepipe.copyImage(this, img, dx, dy, sx, sy,
3036                                            width, height, bgcolor, observer);
3037             } catch (InvalidPipeException e2) {
3038                 // Still catching the exception; we are not yet ready to
3039                 // validate the surfaceData correctly.  Fail for now and
3040                 // try again next time around.
3041                 return false;
3042             }
3043         } finally {
3044             surfaceData.markDirty();
3045         }
3046     }
3047 
3048     /**
3049      * Draws an image scaled to x,y,w,h in nonblocking mode with a
3050      * solid background color and a callback object.
3051      */
3052     public boolean drawImage(Image img, int x, int y, int width, int height,
3053                              Color bg, ImageObserver observer) {
3054 
3055         if (img == null) {
3056             return true;
3057         }
3058 
3059         if ((width == 0) || (height == 0)) {
3060             return true;
3061         }
3062         if (width == img.getWidth(null) && height == img.getHeight(null)) {
3063             return copyImage(img, x, y, 0, 0, width, height, bg, observer);
3064         }
3065 
3066         try {
3067             return imagepipe.scaleImage(this, img, x, y, width, height,
3068                                         bg, observer);
3069         } catch (InvalidPipeException e) {
3070             try {
3071                 revalidateAll();
3072                 return imagepipe.scaleImage(this, img, x, y, width, height,
3073                                             bg, observer);
3074             } catch (InvalidPipeException e2) {
3075                 // Still catching the exception; we are not yet ready to
3076                 // validate the surfaceData correctly.  Fail for now and
3077                 // try again next time around.
3078                 return false;
3079             }
3080         } finally {
3081             surfaceData.markDirty();
3082         }
3083     }
3084 
3085     /**
3086      * Draws an image at x,y in nonblocking mode.
3087      */
3088     public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
3089         return drawImage(img, x, y, null, observer);
3090     }
3091 
3092     /**
3093      * Draws an image at x,y in nonblocking mode with a solid background
3094      * color and a callback object.
3095      */
3096     public boolean drawImage(Image img, int x, int y, Color bg,
3097                              ImageObserver observer) {
3098 
3099         if (img == null) {
3100             return true;
3101         }
3102 
3103         try {
3104             return imagepipe.copyImage(this, img, x, y, bg, observer);
3105         } catch (InvalidPipeException e) {
3106             try {
3107                 revalidateAll();
3108                 return imagepipe.copyImage(this, img, x, y, bg, observer);
3109             } catch (InvalidPipeException e2) {
3110                 // Still catching the exception; we are not yet ready to
3111                 // validate the surfaceData correctly.  Fail for now and
3112                 // try again next time around.
3113                 return false;
3114             }
3115         } finally {
3116             surfaceData.markDirty();
3117         }
3118     }
3119 
3120     /**
3121      * Draws a subrectangle of an image scaled to a destination rectangle
3122      * in nonblocking mode with a callback object.
3123      */
3124     public boolean drawImage(Image img,
3125                              int dx1, int dy1, int dx2, int dy2,
3126                              int sx1, int sy1, int sx2, int sy2,
3127                              ImageObserver observer) {
3128         return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null,
3129                          observer);
3130     }
3131 
3132     /**
3133      * Draws a subrectangle of an image scaled to a destination rectangle in
3134      * nonblocking mode with a solid background color and a callback object.
3135      */
3136     public boolean drawImage(Image img,
3137                              int dx1, int dy1, int dx2, int dy2,
3138                              int sx1, int sy1, int sx2, int sy2,
3139                              Color bgcolor, ImageObserver observer) {
3140 
3141         if (img == null) {
3142             return true;
3143         }
3144 
3145         if (dx1 == dx2 || dy1 == dy2 ||
3146             sx1 == sx2 || sy1 == sy2)
3147         {
3148             return true;
3149         }
3150 
3151         if (((sx2 - sx1) == (dx2 - dx1)) &&
3152             ((sy2 - sy1) == (dy2 - dy1)))
3153         {
3154             // Not a scale - forward it to a copy routine
3155             int srcX, srcY, dstX, dstY, width, height;
3156             if (sx2 > sx1) {
3157                 width = sx2 - sx1;
3158                 srcX = sx1;
3159                 dstX = dx1;
3160             } else {
3161                 width = sx1 - sx2;
3162                 srcX = sx2;
3163                 dstX = dx2;
3164             }
3165             if (sy2 > sy1) {
3166                 height = sy2-sy1;
3167                 srcY = sy1;
3168                 dstY = dy1;
3169             } else {
3170                 height = sy1-sy2;
3171                 srcY = sy2;
3172                 dstY = dy2;
3173             }
3174             return copyImage(img, dstX, dstY, srcX, srcY,
3175                              width, height, bgcolor, observer);
3176         }
3177 
3178         try {
3179             return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,
3180                                           sx1, sy1, sx2, sy2, bgcolor,
3181                                           observer);
3182         } catch (InvalidPipeException e) {
3183             try {
3184                 revalidateAll();
3185                 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,
3186                                               sx1, sy1, sx2, sy2, bgcolor,
3187                                               observer);
3188             } catch (InvalidPipeException e2) {
3189                 // Still catching the exception; we are not yet ready to
3190                 // validate the surfaceData correctly.  Fail for now and
3191                 // try again next time around.
3192                 return false;
3193             }
3194         } finally {
3195             surfaceData.markDirty();
3196         }
3197     }
3198 
3199     /**
3200      * Draw an image, applying a transform from image space into user space
3201      * before drawing.
3202      * The transformation from user space into device space is done with
3203      * the current transform in the Graphics2D.
3204      * The given transformation is applied to the image before the
3205      * transform attribute in the Graphics2D state is applied.
3206      * The rendering attributes applied include the clip, transform,
3207      * paint or color and composite attributes. Note that the result is
3208      * undefined, if the given transform is non-invertible.
3209      * @param img The image to be drawn.
3210      * @param xform The transformation from image space into user space.
3211      * @param observer The image observer to be notified on the image producing
3212      * progress.
3213      * @see #transform
3214      * @see #setComposite
3215      * @see #setClip
3216      */
3217     public boolean drawImage(Image img,
3218                              AffineTransform xform,
3219                              ImageObserver observer) {
3220 
3221         if (img == null) {
3222             return true;
3223         }
3224 
3225         if (xform == null || xform.isIdentity()) {
3226             return drawImage(img, 0, 0, null, observer);
3227         }
3228 
3229         try {
3230             return imagepipe.transformImage(this, img, xform, observer);
3231         } catch (InvalidPipeException e) {
3232             try {
3233                 revalidateAll();
3234                 return imagepipe.transformImage(this, img, xform, observer);
3235             } catch (InvalidPipeException e2) {
3236                 // Still catching the exception; we are not yet ready to
3237                 // validate the surfaceData correctly.  Fail for now and
3238                 // try again next time around.
3239                 return false;
3240             }
3241         } finally {
3242             surfaceData.markDirty();
3243         }
3244     }
3245 
3246     public void drawImage(BufferedImage bImg,
3247                           BufferedImageOp op,
3248                           int x,
3249                           int y)  {
3250 
3251         if (bImg == null) {
3252             return;
3253         }
3254 
3255         try {
3256             imagepipe.transformImage(this, bImg, op, x, y);
3257         } catch (InvalidPipeException e) {
3258             try {
3259                 revalidateAll();
3260                 imagepipe.transformImage(this, bImg, op, x, y);
3261             } catch (InvalidPipeException e2) {
3262                 // Still catching the exception; we are not yet ready to
3263                 // validate the surfaceData correctly.  Fail for now and
3264                 // try again next time around.
3265             }
3266         } finally {
3267             surfaceData.markDirty();
3268         }
3269     }
3270 
3271     /**
3272     * Get the rendering context of the font
3273     * within this Graphics2D context.
3274     */
3275     public FontRenderContext getFontRenderContext() {
3276         if (cachedFRC == null) {
3277             int aahint = textAntialiasHint;
3278             if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT &&
3279                 antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
3280                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
3281             }
3282             // Translation components should be excluded from the FRC transform
3283             AffineTransform tx = null;
3284             if (transformState >= TRANSFORM_TRANSLATESCALE) {
3285                 if (transform.getTranslateX() == 0 &&
3286                     transform.getTranslateY() == 0) {
3287                     tx = transform;
3288                 } else {
3289                     tx = new AffineTransform(transform.getScaleX(),
3290                                              transform.getShearY(),
3291                                              transform.getShearX(),
3292                                              transform.getScaleY(),
3293                                              0, 0);
3294                 }
3295             }
3296             cachedFRC = new FontRenderContext(tx,
3297              SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, aahint),
3298              SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
3299                                 fractionalMetricsHint));
3300         }
3301         return cachedFRC;
3302     }
3303     private FontRenderContext cachedFRC;
3304 
3305     /**
3306      * This object has no resources to dispose of per se, but the
3307      * doc comments for the base method in java.awt.Graphics imply
3308      * that this object will not be useable after it is disposed.
3309      * So, we sabotage the object to prevent further use to prevent
3310      * developers from relying on behavior that may not work on
3311      * other, less forgiving, VMs that really need to dispose of
3312      * resources.
3313      */
3314     public void dispose() {
3315         surfaceData = NullSurfaceData.theInstance;
3316         invalidatePipe();
3317     }
3318 
3319     /**
3320      * Graphics has a finalize method that automatically calls dispose()
3321      * for subclasses.  For SunGraphics2D we do not need to be finalized
3322      * so that method simply causes us to be enqueued on the Finalizer
3323      * queues for no good reason.  Unfortunately, that method and
3324      * implementation are now considered part of the public contract
3325      * of that base class so we can not remove or gut the method.
3326      * We override it here with an empty method and the VM is smart
3327      * enough to know that if our override is empty then it should not
3328      * mark us as finalizeable.
3329      */
3330     public void finalize() {
3331         // DO NOT REMOVE THIS METHOD
3332     }
3333 
3334     /**
3335      * Returns destination that this Graphics renders to.  This could be
3336      * either an Image or a Component; subclasses of SurfaceData are
3337      * responsible for returning the appropriate object.
3338      */
3339     public Object getDestination() {
3340         return surfaceData.getDestination();
3341     }
3342 
3343     /**
3344      * {@inheritDoc}
3345      *
3346      * @see sun.java2d.DestSurfaceProvider#getDestSurface
3347      */
3348     @Override
3349     public Surface getDestSurface() {
3350         return surfaceData;
3351     }
3352 }