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