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