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.Point;
  65 import java.awt.image.ImageObserver;
  66 import java.awt.Transparency;
  67 import java.awt.font.GlyphVector;
  68 import java.awt.font.TextLayout;
  69 
  70 import sun.awt.image.SurfaceManager;
  71 import sun.font.FontDesignMetrics;
  72 import sun.font.FontUtilities;
  73 import sun.java2d.pipe.PixelDrawPipe;
  74 import sun.java2d.pipe.PixelFillPipe;
  75 import sun.java2d.pipe.ShapeDrawPipe;
  76 import sun.java2d.pipe.ValidatePipe;
  77 import sun.java2d.pipe.ShapeSpanIterator;
  78 import sun.java2d.pipe.Region;
  79 import sun.java2d.pipe.TextPipe;
  80 import sun.java2d.pipe.DrawImagePipe;
  81 import sun.java2d.pipe.LoopPipe;
  82 import sun.java2d.loops.FontInfo;
  83 import sun.java2d.loops.RenderLoops;
  84 import sun.java2d.loops.CompositeType;
  85 import sun.java2d.loops.SurfaceType;
  86 import sun.java2d.loops.Blit;
  87 import sun.java2d.loops.MaskFill;
  88 import java.awt.font.FontRenderContext;
  89 import sun.java2d.loops.XORComposite;
  90 import sun.awt.ConstrainableGraphics;
  91 import sun.awt.SunHints;
  92 import java.util.Map;
  93 import java.util.Iterator;
  94 import sun.misc.PerformanceLogger;
  95 
  96 import java.lang.annotation.Native;
  97 import sun.awt.image.MultiResolutionImage;
  98 
  99 import static java.awt.geom.AffineTransform.TYPE_FLIP;
 100 import static java.awt.geom.AffineTransform.TYPE_MASK_SCALE;
 101 import static java.awt.geom.AffineTransform.TYPE_TRANSLATION;
 102 import sun.awt.image.MultiResolutionToolkitImage;
 103 import sun.awt.image.ToolkitImage;
 104 
 105 /**
 106  * This is a the master Graphics2D superclass for all of the Sun
 107  * Graphics implementations.  This class relies on subclasses to
 108  * manage the various device information, but provides an overall
 109  * general framework for performing all of the requests in the
 110  * Graphics and Graphics2D APIs.
 111  *
 112  * @author Jim Graham
 113  */
 114 public final class SunGraphics2D
 115     extends Graphics2D
 116     implements ConstrainableGraphics, Cloneable, DestSurfaceProvider
 117 {
 118     /*
 119      * Attribute States
 120      */
 121     /* Paint */
 122     @Native
 123     public static final int PAINT_CUSTOM       = 6; /* Any other Paint object */
 124     @Native
 125     public static final int PAINT_TEXTURE      = 5; /* Tiled Image */
 126     @Native
 127     public static final int PAINT_RAD_GRADIENT = 4; /* Color RadialGradient */
 128     @Native
 129     public static final int PAINT_LIN_GRADIENT = 3; /* Color LinearGradient */
 130     @Native
 131     public static final int PAINT_GRADIENT     = 2; /* Color Gradient */
 132     @Native
 133     public static final int PAINT_ALPHACOLOR   = 1; /* Non-opaque Color */
 134     @Native
 135     public static final int PAINT_OPAQUECOLOR  = 0; /* Opaque Color */
 136 
 137     /* Composite*/
 138     @Native
 139     public static final int COMP_CUSTOM = 3;/* Custom Composite */
 140     @Native
 141     public static final int COMP_XOR    = 2;/* XOR Mode Composite */
 142     @Native
 143     public static final int COMP_ALPHA  = 1;/* AlphaComposite */
 144     @Native
 145     public static final int COMP_ISCOPY = 0;/* simple stores into destination,
 146                                              * i.e. Src, SrcOverNoEa, and other
 147                                              * alpha modes which replace
 148                                              * the destination.
 149                                              */
 150 
 151     /* Stroke */
 152     @Native
 153     public static final int STROKE_CUSTOM = 3; /* custom Stroke */
 154     @Native
 155     public static final int STROKE_WIDE   = 2; /* BasicStroke */
 156     @Native
 157     public static final int STROKE_THINDASHED   = 1; /* BasicStroke */
 158     @Native
 159     public static final int STROKE_THIN   = 0; /* BasicStroke */
 160 
 161     /* Transform */
 162     @Native
 163     public static final int TRANSFORM_GENERIC = 4; /* any 3x2 */
 164     @Native
 165     public static final int TRANSFORM_TRANSLATESCALE = 3; /* scale XY */
 166     @Native
 167     public static final int TRANSFORM_ANY_TRANSLATE = 2; /* non-int translate */
 168     @Native
 169     public static final int TRANSFORM_INT_TRANSLATE = 1; /* int translate */
 170     @Native
 171     public static final int TRANSFORM_ISIDENT = 0; /* Identity */
 172 
 173     /* Clipping */
 174     @Native
 175     public static final int CLIP_SHAPE       = 2; /* arbitrary clip */
 176     @Native
 177     public static final int CLIP_RECTANGULAR = 1; /* rectangular clip */
 178     @Native
 179     public static final int CLIP_DEVICE      = 0; /* no clipping set */
 180 
 181     /* The following fields are used when the current Paint is a Color. */
 182     public int eargb;  // ARGB value with ExtraAlpha baked in
 183     public int pixel;  // pixel value for eargb
 184 
 185     public SurfaceData surfaceData;
 186 
 187     public PixelDrawPipe drawpipe;
 188     public PixelFillPipe fillpipe;
 189     public DrawImagePipe imagepipe;
 190     public ShapeDrawPipe shapepipe;
 191     public TextPipe textpipe;
 192     public MaskFill alphafill;
 193 
 194     public RenderLoops loops;
 195 
 196     public CompositeType imageComp;     /* Image Transparency checked on fly */
 197 
 198     public int paintState;
 199     public int compositeState;
 200     public int strokeState;
 201     public int transformState;
 202     public int clipState;
 203 
 204     public Color foregroundColor;
 205     public Color backgroundColor;
 206 
 207     public AffineTransform transform;
 208     public int transX;
 209     public int transY;
 210 
 211     protected static final Stroke defaultStroke = new BasicStroke();
 212     protected static final Composite defaultComposite = AlphaComposite.SrcOver;
 213     private static final Font defaultFont =
 214         new Font(Font.DIALOG, Font.PLAIN, 12);
 215 
 216     public Paint paint;
 217     public Stroke stroke;
 218     public Composite composite;
 219     protected Font font;
 220     protected FontMetrics fontMetrics;
 221 
 222     public int renderHint;
 223     public int antialiasHint;
 224     public int textAntialiasHint;
 225     protected int fractionalMetricsHint;
 226 
 227     /* A gamma adjustment to the colour used in lcd text blitting */
 228     public int lcdTextContrast;
 229     private static int lcdTextContrastDefaultValue = 140;
 230 
 231     private int interpolationHint;      // raw value of rendering Hint
 232     public int strokeHint;
 233 
 234     public int interpolationType;       // algorithm choice based on
 235                                         // interpolation and render Hints
 236 
 237     public RenderingHints hints;
 238 
 239     public Region constrainClip;        // lightweight bounds in pixels
 240     public int constrainX;
 241     public int constrainY;
 242 
 243     public Region clipRegion;
 244     public Shape usrClip;
 245     protected Region devClip;           // Actual physical drawable in pixels
 246 

 247     private int resolutionVariantHint;
 248 
 249     // cached state for text rendering
 250     private boolean validFontInfo;
 251     private FontInfo fontInfo;
 252     private FontInfo glyphVectorFontInfo;
 253     private FontRenderContext glyphVectorFRC;
 254 
 255     private final static int slowTextTransformMask =
 256                             AffineTransform.TYPE_GENERAL_TRANSFORM
 257                         |   AffineTransform.TYPE_MASK_ROTATION
 258                         |   AffineTransform.TYPE_FLIP;
 259 
 260     static {
 261         if (PerformanceLogger.loggingEnabled()) {
 262             PerformanceLogger.setTime("SunGraphics2D static initialization");
 263         }
 264     }
 265 
 266     public SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f) {
 267         surfaceData = sd;
 268         foregroundColor = fg;
 269         backgroundColor = bg;


 270         stroke = defaultStroke;
 271         composite = defaultComposite;
 272         paint = foregroundColor;
 273 
 274         imageComp = CompositeType.SrcOverNoEa;
 275 
 276         renderHint = SunHints.INTVAL_RENDER_DEFAULT;
 277         antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
 278         textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
 279         fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
 280         lcdTextContrast = lcdTextContrastDefaultValue;
 281         interpolationHint = -1;
 282         strokeHint = SunHints.INTVAL_STROKE_DEFAULT;
 283         resolutionVariantHint = SunHints.INTVAL_RESOLUTION_VARIANT_DEFAULT;
 284 
 285         interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
 286 
 287         transform = getDefaultTransform();
 288         if (!transform.isIdentity()) {



 289             invalidateTransform();
 290         }
 291 
 292         validateColor();
 293 
 294         font = f;
 295         if (font == null) {
 296             font = defaultFont;
 297         }
 298 
 299         setDevClip(sd.getBounds());
 300         invalidatePipe();
 301     }
 302 
 303     private AffineTransform getDefaultTransform() {
 304         GraphicsConfiguration gc = getDeviceConfiguration();
 305         return (gc == null) ? new AffineTransform() : gc.getDefaultTransform();
 306     }
 307 
 308     protected Object clone() {
 309         try {
 310             SunGraphics2D g = (SunGraphics2D) super.clone();
 311             g.transform = new AffineTransform(this.transform);
 312             if (hints != null) {
 313                 g.hints = (RenderingHints) this.hints.clone();
 314             }
 315             /* FontInfos are re-used, so must be cloned too, if they
 316              * are valid, and be nulled out if invalid.
 317              * The implied trade-off is that there is more to be gained
 318              * from re-using these objects than is lost by having to
 319              * clone them when the SG2D is cloned.
 320              */
 321             if (this.fontInfo != null) {
 322                 if (this.validFontInfo) {
 323                     g.fontInfo = (FontInfo)this.fontInfo.clone();
 324                 } else {
 325                     g.fontInfo = null;
 326                 }
 327             }
 328             if (this.glyphVectorFontInfo != null) {
 329                 g.glyphVectorFontInfo =
 330                     (FontInfo)this.glyphVectorFontInfo.clone();
 331                 g.glyphVectorFRC = this.glyphVectorFRC;
 332             }
 333             //g.invalidatePipe();
 334             return g;
 335         } catch (CloneNotSupportedException e) {
 336         }
 337         return null;
 338     }
 339 
 340     /**
 341      * Create a new SunGraphics2D based on this one.
 342      */
 343     public Graphics create() {
 344         return (Graphics) clone();
 345     }
 346 
 347     public void setDevClip(int x, int y, int w, int h) {
 348         Region c = constrainClip;
 349         if (c == null) {
 350             devClip = Region.getInstanceXYWH(x, y, w, h);
 351         } else {
 352             devClip = c.getIntersectionXYWH(x, y, w, h);
 353         }
 354         validateCompClip();
 355     }
 356 
 357     public void setDevClip(Rectangle r) {
 358         setDevClip(r.x, r.y, r.width, r.height);
 359     }
 360 
 361     /**
 362      * Constrain rendering for lightweight objects.
 363      */
 364     public void constrain(int x, int y, int w, int h, Region region) {
 365         if ((x | y) != 0) {
 366             translate(x, y);
 367         }
 368         if (transformState > TRANSFORM_TRANSLATESCALE) {
 369             clipRect(0, 0, w, h);
 370             return;
 371         }
 372         // changes parameters according to the current scale and translate.
 373         final double scaleX = transform.getScaleX();
 374         final double scaleY = transform.getScaleY();
 375         x = constrainX = (int) transform.getTranslateX();
 376         y = constrainY = (int) transform.getTranslateY();
 377         w = Region.dimAdd(x, Region.clipScale(w, scaleX));
 378         h = Region.dimAdd(y, Region.clipScale(h, scaleY));
 379 
 380         Region c = constrainClip;
 381         if (c == null) {
 382             c = Region.getInstanceXYXY(x, y, w, h);
 383         } else {
 384             c = c.getIntersectionXYXY(x, y, w, h);
 385         }
 386         if (region != null) {
 387             region = region.getScaledRegion(scaleX, scaleY);
 388             region = region.getTranslatedRegion(x, y);
 389             c = c.getIntersection(region);
 390         }
 391 
 392         if (c == constrainClip) {
 393             // Common case to ignore
 394             return;
 395         }
 396 
 397         constrainClip = c;
 398         if (!devClip.isInsideQuickCheck(c)) {
 399             devClip = devClip.getIntersection(c);
 400             validateCompClip();
 401         }
 402     }
 403 
 404     /**
 405      * Constrain rendering for lightweight objects.
 406      *
 407      * REMIND: This method will back off to the "workaround"
 408      * of using translate and clipRect if the Graphics
 409      * to be constrained has a complex transform.  The
 410      * drawback of the workaround is that the resulting
 411      * clip and device origin cannot be "enforced".
 412      *
 413      * @exception IllegalStateException If the Graphics
 414      * to be constrained has a complex transform.
 415      */
 416     @Override
 417     public void constrain(int x, int y, int w, int h) {
 418         constrain(x, y, w, h, null);
 419     }
 420 
 421     protected static ValidatePipe invalidpipe = new ValidatePipe();
 422 
 423     /*
 424      * Invalidate the pipeline
 425      */
 426     protected void invalidatePipe() {
 427         drawpipe = invalidpipe;
 428         fillpipe = invalidpipe;
 429         shapepipe = invalidpipe;
 430         textpipe = invalidpipe;
 431         imagepipe = invalidpipe;
 432         loops = null;
 433     }
 434 
 435     public void validatePipe() {
 436         /* This workaround is for the situation when we update the Pipelines
 437          * for invalid SurfaceData and run further code when the current
 438          * pipeline doesn't support the type of new SurfaceData created during
 439          * the current pipeline's work (in place of the invalid SurfaceData).
 440          * Usually SurfaceData and Pipelines are repaired (through revalidateAll)
 441          * and called again in the exception handlers */
 442 
 443         if (!surfaceData.isValid()) {
 444             throw new InvalidPipeException("attempt to validate Pipe with invalid SurfaceData");
 445         }
 446 
 447         surfaceData.validatePipe(this);
 448     }
 449 
 450     /*
 451      * Intersect two Shapes by the simplest method, attempting to produce
 452      * a simplified result.
 453      * The boolean arguments keep1 and keep2 specify whether or not
 454      * the first or second shapes can be modified during the operation
 455      * or whether that shape must be "kept" unmodified.
 456      */
 457     Shape intersectShapes(Shape s1, Shape s2, boolean keep1, boolean keep2) {
 458         if (s1 instanceof Rectangle && s2 instanceof Rectangle) {
 459             return ((Rectangle) s1).intersection((Rectangle) s2);
 460         }
 461         if (s1 instanceof Rectangle2D) {
 462             return intersectRectShape((Rectangle2D) s1, s2, keep1, keep2);
 463         } else if (s2 instanceof Rectangle2D) {
 464             return intersectRectShape((Rectangle2D) s2, s1, keep2, keep1);
 465         }
 466         return intersectByArea(s1, s2, keep1, keep2);
 467     }
 468 
 469     /*
 470      * Intersect a Rectangle with a Shape by the simplest method,
 471      * attempting to produce a simplified result.
 472      * The boolean arguments keep1 and keep2 specify whether or not
 473      * the first or second shapes can be modified during the operation
 474      * or whether that shape must be "kept" unmodified.
 475      */
 476     Shape intersectRectShape(Rectangle2D r, Shape s,
 477                              boolean keep1, boolean keep2) {
 478         if (s instanceof Rectangle2D) {
 479             Rectangle2D r2 = (Rectangle2D) s;
 480             Rectangle2D outrect;
 481             if (!keep1) {
 482                 outrect = r;
 483             } else if (!keep2) {
 484                 outrect = r2;
 485             } else {
 486                 outrect = new Rectangle2D.Float();
 487             }
 488             double x1 = Math.max(r.getX(), r2.getX());
 489             double x2 = Math.min(r.getX()  + r.getWidth(),
 490                                  r2.getX() + r2.getWidth());
 491             double y1 = Math.max(r.getY(), r2.getY());
 492             double y2 = Math.min(r.getY()  + r.getHeight(),
 493                                  r2.getY() + r2.getHeight());
 494 
 495             if (((x2 - x1) < 0) || ((y2 - y1) < 0))
 496                 // Width or height is negative. No intersection.
 497                 outrect.setFrameFromDiagonal(0, 0, 0, 0);
 498             else
 499                 outrect.setFrameFromDiagonal(x1, y1, x2, y2);
 500             return outrect;
 501         }
 502         if (r.contains(s.getBounds2D())) {
 503             if (keep2) {
 504                 s = cloneShape(s);
 505             }
 506             return s;
 507         }
 508         return intersectByArea(r, s, keep1, keep2);
 509     }
 510 
 511     protected static Shape cloneShape(Shape s) {
 512         return new GeneralPath(s);
 513     }
 514 
 515     /*
 516      * Intersect two Shapes using the Area class.  Presumably other
 517      * attempts at simpler intersection methods proved fruitless.
 518      * The boolean arguments keep1 and keep2 specify whether or not
 519      * the first or second shapes can be modified during the operation
 520      * or whether that shape must be "kept" unmodified.
 521      * @see #intersectShapes
 522      * @see #intersectRectShape
 523      */
 524     Shape intersectByArea(Shape s1, Shape s2, boolean keep1, boolean keep2) {
 525         Area a1, a2;
 526 
 527         // First see if we can find an overwriteable source shape
 528         // to use as our destination area to avoid duplication.
 529         if (!keep1 && (s1 instanceof Area)) {
 530             a1 = (Area) s1;
 531         } else if (!keep2 && (s2 instanceof Area)) {
 532             a1 = (Area) s2;
 533             s2 = s1;
 534         } else {
 535             a1 = new Area(s1);
 536         }
 537 
 538         if (s2 instanceof Area) {
 539             a2 = (Area) s2;
 540         } else {
 541             a2 = new Area(s2);
 542         }
 543 
 544         a1.intersect(a2);
 545         if (a1.isRectangular()) {
 546             return a1.getBounds();
 547         }
 548 
 549         return a1;
 550     }
 551 
 552     /*
 553      * Intersect usrClip bounds and device bounds to determine the composite
 554      * rendering boundaries.
 555      */
 556     public Region getCompClip() {
 557         if (!surfaceData.isValid()) {
 558             // revalidateAll() implicitly recalculcates the composite clip
 559             revalidateAll();
 560         }
 561 
 562         return clipRegion;
 563     }
 564 
 565     public Font getFont() {
 566         if (font == null) {
 567             font = defaultFont;
 568         }
 569         return font;
 570     }
 571 
 572     private static final double[] IDENT_MATRIX = {1, 0, 0, 1};
 573     private static final AffineTransform IDENT_ATX =
 574         new AffineTransform();
 575 
 576     private static final int MINALLOCATED = 8;
 577     private static final int TEXTARRSIZE = 17;
 578     private static double[][] textTxArr = new double[TEXTARRSIZE][];
 579     private static AffineTransform[] textAtArr =
 580         new AffineTransform[TEXTARRSIZE];
 581 
 582     static {
 583         for (int i=MINALLOCATED;i<TEXTARRSIZE;i++) {
 584           textTxArr[i] = new double [] {i, 0, 0, i};
 585           textAtArr[i] = new AffineTransform( textTxArr[i]);
 586         }
 587     }
 588 
 589     // cached state for various draw[String,Char,Byte] optimizations
 590     public FontInfo checkFontInfo(FontInfo info, Font font,
 591                                   FontRenderContext frc) {
 592         /* Do not create a FontInfo object as part of construction of an
 593          * SG2D as its possible it may never be needed - ie if no text
 594          * is drawn using this SG2D.
 595          */
 596         if (info == null) {
 597             info = new FontInfo();
 598         }
 599 
 600         float ptSize = font.getSize2D();
 601         int txFontType;
 602         AffineTransform devAt, textAt=null;
 603         if (font.isTransformed()) {
 604             textAt = font.getTransform();
 605             textAt.scale(ptSize, ptSize);
 606             txFontType = textAt.getType();
 607             info.originX = (float)textAt.getTranslateX();
 608             info.originY = (float)textAt.getTranslateY();
 609             textAt.translate(-info.originX, -info.originY);
 610             if (transformState >= TRANSFORM_TRANSLATESCALE) {
 611                 transform.getMatrix(info.devTx = new double[4]);
 612                 devAt = new AffineTransform(info.devTx);
 613                 textAt.preConcatenate(devAt);
 614             } else {
 615                 info.devTx = IDENT_MATRIX;
 616                 devAt = IDENT_ATX;
 617             }
 618             textAt.getMatrix(info.glyphTx = new double[4]);
 619             double shearx = textAt.getShearX();
 620             double scaley = textAt.getScaleY();
 621             if (shearx != 0) {
 622                 scaley = Math.sqrt(shearx * shearx + scaley * scaley);
 623             }
 624             info.pixelHeight = (int)(Math.abs(scaley)+0.5);
 625         } else {
 626             txFontType = AffineTransform.TYPE_IDENTITY;
 627             info.originX = info.originY = 0;
 628             if (transformState >= TRANSFORM_TRANSLATESCALE) {
 629                 transform.getMatrix(info.devTx = new double[4]);
 630                 devAt = new AffineTransform(info.devTx);
 631                 info.glyphTx = new double[4];
 632                 for (int i = 0; i < 4; i++) {
 633                     info.glyphTx[i] = info.devTx[i] * ptSize;
 634                 }
 635                 textAt = new AffineTransform(info.glyphTx);
 636                 double shearx = transform.getShearX();
 637                 double scaley = transform.getScaleY();
 638                 if (shearx != 0) {
 639                     scaley = Math.sqrt(shearx * shearx + scaley * scaley);
 640                 }
 641                 info.pixelHeight = (int)(Math.abs(scaley * ptSize)+0.5);
 642             } else {
 643                 /* If the double represents a common integral, we
 644                  * may have pre-allocated objects.
 645                  * A "sparse" array be seems to be as fast as a switch
 646                  * even for 3 or 4 pt sizes, and is more flexible.
 647                  * This should perform comparably in single-threaded
 648                  * rendering to the old code which synchronized on the
 649                  * class and scale better on MP systems.
 650                  */
 651                 int pszInt = (int)ptSize;
 652                 if (ptSize == pszInt &&
 653                     pszInt >= MINALLOCATED && pszInt < TEXTARRSIZE) {
 654                     info.glyphTx = textTxArr[pszInt];
 655                     textAt = textAtArr[pszInt];
 656                     info.pixelHeight = pszInt;
 657                 } else {
 658                     info.pixelHeight = (int)(ptSize+0.5);
 659                 }
 660                 if (textAt == null) {
 661                     info.glyphTx = new double[] {ptSize, 0, 0, ptSize};
 662                     textAt = new AffineTransform(info.glyphTx);
 663                 }
 664 
 665                 info.devTx = IDENT_MATRIX;
 666                 devAt = IDENT_ATX;
 667             }
 668         }
 669 
 670         info.font2D = FontUtilities.getFont2D(font);
 671 
 672         int fmhint = fractionalMetricsHint;
 673         if (fmhint == SunHints.INTVAL_FRACTIONALMETRICS_DEFAULT) {
 674             fmhint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
 675         }
 676         info.lcdSubPixPos = false; // conditionally set true in LCD mode.
 677 
 678         /* The text anti-aliasing hints that are set by the client need
 679          * to be interpreted for the current state and stored in the
 680          * FontInfo.aahint which is what will actually be used and
 681          * will be one of OFF, ON, LCD_HRGB or LCD_VRGB.
 682          * This is what pipe selection code should typically refer to, not
 683          * textAntialiasHint. This means we are now evaluating the meaning
 684          * of "default" here. Any pipe that really cares about that will
 685          * also need to consult that variable.
 686          * Otherwise these are being used only as args to getStrike,
 687          * and are encapsulated in that object which is part of the
 688          * FontInfo, so we do not need to store them directly as fields
 689          * in the FontInfo object.
 690          * That could change if FontInfo's were more selectively
 691          * revalidated when graphics state changed. Presently this
 692          * method re-evaluates all fields in the fontInfo.
 693          * The strike doesn't need to know the RGB subpixel order. Just
 694          * if its H or V orientation, so if an LCD option is specified we
 695          * always pass in the RGB hint to the strike.
 696          * frc is non-null only if this is a GlyphVector. For reasons
 697          * which are probably a historical mistake the AA hint in a GV
 698          * is honoured when we render, overriding the Graphics setting.
 699          */
 700         int aahint;
 701         if (frc == null) {
 702             aahint = textAntialiasHint;
 703         } else {
 704             aahint = ((SunHints.Value)frc.getAntiAliasingHint()).getIndex();
 705         }
 706         if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT) {
 707             if (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
 708                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 709             } else {
 710                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
 711             }
 712         } else {
 713             /* If we are in checkFontInfo because a rendering hint has been
 714              * set then all pipes are revalidated. But we can also
 715              * be here because setFont() has been called when the 'gasp'
 716              * hint is set, as then the font size determines the text pipe.
 717              * See comments in SunGraphics2d.setFont(Font).
 718              */
 719             if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP) {
 720                 if (info.font2D.useAAForPtSize(info.pixelHeight)) {
 721                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 722                 } else {
 723                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
 724                 }
 725             } else if (aahint >= SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB) {
 726                 /* loops for default rendering modes are installed in the SG2D
 727                  * constructor. If there are none this will be null.
 728                  * Not all compositing modes update the render loops, so
 729                  * we also test that this is a mode we know should support
 730                  * this. One minor issue is that the loops aren't necessarily
 731                  * installed for a new rendering mode until after this
 732                  * method is called during pipeline validation. So it is
 733                  * theoretically possible that it was set to null for a
 734                  * compositing mode, the composite is then set back to Src,
 735                  * but the loop is still null when this is called and AA=ON
 736                  * is installed instead of an LCD mode.
 737                  * However this is done in the right order in SurfaceData.java
 738                  * so this is not likely to be a problem - but not
 739                  * guaranteed.
 740                  */
 741                 if (
 742                     !surfaceData.canRenderLCDText(this)
 743 //                    loops.drawGlyphListLCDLoop == null ||
 744 //                    compositeState > COMP_ISCOPY ||
 745 //                    paintState > PAINT_ALPHACOLOR
 746                       ) {
 747                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 748                 } else {
 749                     info.lcdRGBOrder = true;
 750                     /* Collapse these into just HRGB or VRGB.
 751                      * Pipe selection code needs only to test for these two.
 752                      * Since these both select the same pipe anyway its
 753                      * tempting to collapse into one value. But they are
 754                      * different strikes (glyph caches) so the distinction
 755                      * needs to be made for that purpose.
 756                      */
 757                     if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HBGR) {
 758                         aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
 759                         info.lcdRGBOrder = false;
 760                     } else if
 761                         (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VBGR) {
 762                         aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB;
 763                         info.lcdRGBOrder = false;
 764                     }
 765                     /* Support subpixel positioning only for the case in
 766                      * which the horizontal resolution is increased
 767                      */
 768                     info.lcdSubPixPos =
 769                         fmhint == SunHints.INTVAL_FRACTIONALMETRICS_ON &&
 770                         aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
 771                 }
 772             }
 773         }
 774         info.aaHint = aahint;
 775         info.fontStrike = info.font2D.getStrike(font, devAt, textAt,
 776                                                 aahint, fmhint);
 777         return info;
 778     }
 779 
 780     public static boolean isRotated(double [] mtx) {
 781         if ((mtx[0] == mtx[3]) &&
 782             (mtx[1] == 0.0) &&
 783             (mtx[2] == 0.0) &&
 784             (mtx[0] > 0.0))
 785         {
 786             return false;
 787         }
 788 
 789         return true;
 790     }
 791 
 792     public void setFont(Font font) {
 793         /* replacing the reference equality test font != this.font with
 794          * !font.equals(this.font) did not yield any measurable difference
 795          * in testing, but there may be yet to be identified cases where it
 796          * is beneficial.
 797          */
 798         if (font != null && font!=this.font/*!font.equals(this.font)*/) {
 799             /* In the GASP AA case the textpipe depends on the glyph size
 800              * as determined by graphics and font transforms as well as the
 801              * font size, and information in the font. But we may invalidate
 802              * the pipe only to find that it made no difference.
 803              * Deferring pipe invalidation to checkFontInfo won't work because
 804              * when called we may already be rendering to the wrong pipe.
 805              * So, if the font is transformed, or the graphics has more than
 806              * a simple scale, we'll take that as enough of a hint to
 807              * revalidate everything. But if they aren't we will
 808              * use the font's point size to query the gasp table and see if
 809              * what it says matches what's currently being used, in which
 810              * case there's no need to invalidate the textpipe.
 811              * This should be sufficient for all typical uses cases.
 812              */
 813             if (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP &&
 814                 textpipe != invalidpipe &&
 815                 (transformState > TRANSFORM_ANY_TRANSLATE ||
 816                  font.isTransformed() ||
 817                  fontInfo == null || // Precaution, if true shouldn't get here
 818                  (fontInfo.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) !=
 819                      FontUtilities.getFont2D(font).
 820                          useAAForPtSize(font.getSize()))) {
 821                 textpipe = invalidpipe;
 822             }
 823             this.font = font;
 824             this.fontMetrics = null;
 825             this.validFontInfo = false;
 826         }
 827     }
 828 
 829     public FontInfo getFontInfo() {
 830         if (!validFontInfo) {
 831             this.fontInfo = checkFontInfo(this.fontInfo, font, null);
 832             validFontInfo = true;
 833         }
 834         return this.fontInfo;
 835     }
 836 
 837     /* Used by drawGlyphVector which specifies its own font. */
 838     public FontInfo getGVFontInfo(Font font, FontRenderContext frc) {
 839         if (glyphVectorFontInfo != null &&
 840             glyphVectorFontInfo.font == font &&
 841             glyphVectorFRC == frc) {
 842             return glyphVectorFontInfo;
 843         } else {
 844             glyphVectorFRC = frc;
 845             return glyphVectorFontInfo =
 846                 checkFontInfo(glyphVectorFontInfo, font, frc);
 847         }
 848     }
 849 
 850     public FontMetrics getFontMetrics() {
 851         if (this.fontMetrics != null) {
 852             return this.fontMetrics;
 853         }
 854         /* NB the constructor and the setter disallow "font" being null */
 855         return this.fontMetrics =
 856            FontDesignMetrics.getMetrics(font, getFontRenderContext());
 857     }
 858 
 859     public FontMetrics getFontMetrics(Font font) {
 860         if ((this.fontMetrics != null) && (font == this.font)) {
 861             return this.fontMetrics;
 862         }
 863         FontMetrics fm =
 864           FontDesignMetrics.getMetrics(font, getFontRenderContext());
 865 
 866         if (this.font == font) {
 867             this.fontMetrics = fm;
 868         }
 869         return fm;
 870     }
 871 
 872     /**
 873      * Checks to see if a Path intersects the specified Rectangle in device
 874      * space.  The rendering attributes taken into account include the
 875      * clip, transform, and stroke attributes.
 876      * @param rect The area in device space to check for a hit.
 877      * @param p The path to check for a hit.
 878      * @param onStroke Flag to choose between testing the stroked or
 879      * the filled path.
 880      * @return True if there is a hit, false otherwise.
 881      * @see #setStroke
 882      * @see #fillPath
 883      * @see #drawPath
 884      * @see #transform
 885      * @see #setTransform
 886      * @see #clip
 887      * @see #setClip
 888      */
 889     public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
 890         if (onStroke) {
 891             s = stroke.createStrokedShape(s);
 892         }
 893 
 894         s = transformShape(s);
 895         if ((constrainX|constrainY) != 0) {
 896             rect = new Rectangle(rect);
 897             rect.translate(constrainX, constrainY);
 898         }
 899 
 900         return s.intersects(rect);
 901     }
 902 
 903     /**
 904      * Return the ColorModel associated with this Graphics2D.
 905      */
 906     public ColorModel getDeviceColorModel() {
 907         return surfaceData.getColorModel();
 908     }
 909 
 910     /**
 911      * Return the device configuration associated with this Graphics2D.
 912      */
 913     public GraphicsConfiguration getDeviceConfiguration() {
 914         return surfaceData.getDeviceConfiguration();
 915     }
 916 
 917     /**
 918      * Return the SurfaceData object assigned to manage the destination
 919      * drawable surface of this Graphics2D.
 920      */
 921     public SurfaceData getSurfaceData() {
 922         return surfaceData;
 923     }
 924 
 925     /**
 926      * Sets the Composite in the current graphics state. Composite is used
 927      * in all drawing methods such as drawImage, drawString, drawPath,
 928      * and fillPath.  It specifies how new pixels are to be combined with
 929      * the existing pixels on the graphics device in the rendering process.
 930      * @param comp The Composite object to be used for drawing.
 931      * @see java.awt.Graphics#setXORMode
 932      * @see java.awt.Graphics#setPaintMode
 933      * @see AlphaComposite
 934      */
 935     public void setComposite(Composite comp) {
 936         if (composite == comp) {
 937             return;
 938         }
 939         int newCompState;
 940         CompositeType newCompType;
 941         if (comp instanceof AlphaComposite) {
 942             AlphaComposite alphacomp = (AlphaComposite) comp;
 943             newCompType = CompositeType.forAlphaComposite(alphacomp);
 944             if (newCompType == CompositeType.SrcOverNoEa) {
 945                 if (paintState == PAINT_OPAQUECOLOR ||
 946                     (paintState > PAINT_ALPHACOLOR &&
 947                      paint.getTransparency() == Transparency.OPAQUE))
 948                 {
 949                     newCompState = COMP_ISCOPY;
 950                 } else {
 951                     newCompState = COMP_ALPHA;
 952                 }
 953             } else if (newCompType == CompositeType.SrcNoEa ||
 954                        newCompType == CompositeType.Src ||
 955                        newCompType == CompositeType.Clear)
 956             {
 957                 newCompState = COMP_ISCOPY;
 958             } else if (surfaceData.getTransparency() == Transparency.OPAQUE &&
 959                        newCompType == CompositeType.SrcIn)
 960             {
 961                 newCompState = COMP_ISCOPY;
 962             } else {
 963                 newCompState = COMP_ALPHA;
 964             }
 965         } else if (comp instanceof XORComposite) {
 966             newCompState = COMP_XOR;
 967             newCompType = CompositeType.Xor;
 968         } else if (comp == null) {
 969             throw new IllegalArgumentException("null Composite");
 970         } else {
 971             surfaceData.checkCustomComposite();
 972             newCompState = COMP_CUSTOM;
 973             newCompType = CompositeType.General;
 974         }
 975         if (compositeState != newCompState ||
 976             imageComp != newCompType)
 977         {
 978             compositeState = newCompState;
 979             imageComp = newCompType;
 980             invalidatePipe();
 981             validFontInfo = false;
 982         }
 983         composite = comp;
 984         if (paintState <= PAINT_ALPHACOLOR) {
 985             validateColor();
 986         }
 987     }
 988 
 989     /**
 990      * Sets the Paint in the current graphics state.
 991      * @param paint The Paint object to be used to generate color in
 992      * the rendering process.
 993      * @see java.awt.Graphics#setColor
 994      * @see GradientPaint
 995      * @see TexturePaint
 996      */
 997     public void setPaint(Paint paint) {
 998         if (paint instanceof Color) {
 999             setColor((Color) paint);
1000             return;
1001         }
1002         if (paint == null || this.paint == paint) {
1003             return;
1004         }
1005         this.paint = paint;
1006         if (imageComp == CompositeType.SrcOverNoEa) {
1007             // special case where compState depends on opacity of paint
1008             if (paint.getTransparency() == Transparency.OPAQUE) {
1009                 if (compositeState != COMP_ISCOPY) {
1010                     compositeState = COMP_ISCOPY;
1011                 }
1012             } else {
1013                 if (compositeState == COMP_ISCOPY) {
1014                     compositeState = COMP_ALPHA;
1015                 }
1016             }
1017         }
1018         Class<? extends Paint> paintClass = paint.getClass();
1019         if (paintClass == GradientPaint.class) {
1020             paintState = PAINT_GRADIENT;
1021         } else if (paintClass == LinearGradientPaint.class) {
1022             paintState = PAINT_LIN_GRADIENT;
1023         } else if (paintClass == RadialGradientPaint.class) {
1024             paintState = PAINT_RAD_GRADIENT;
1025         } else if (paintClass == TexturePaint.class) {
1026             paintState = PAINT_TEXTURE;
1027         } else {
1028             paintState = PAINT_CUSTOM;
1029         }
1030         validFontInfo = false;
1031         invalidatePipe();
1032     }
1033 
1034     static final int NON_UNIFORM_SCALE_MASK =
1035         (AffineTransform.TYPE_GENERAL_TRANSFORM |
1036          AffineTransform.TYPE_GENERAL_SCALE);
1037     public static final double MinPenSizeAA =
1038         sun.java2d.pipe.RenderingEngine.getInstance().getMinimumAAPenSize();
1039     public static final double MinPenSizeAASquared =
1040         (MinPenSizeAA * MinPenSizeAA);
1041     // Since inaccuracies in the trig package can cause us to
1042     // calculated a rotated pen width of just slightly greater
1043     // than 1.0, we add a fudge factor to our comparison value
1044     // here so that we do not misclassify single width lines as
1045     // wide lines under certain rotations.
1046     public static final double MinPenSizeSquared = 1.000000001;
1047 
1048     private void validateBasicStroke(BasicStroke bs) {
1049         boolean aa = (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON);
1050         if (transformState < TRANSFORM_TRANSLATESCALE) {
1051             if (aa) {
1052                 if (bs.getLineWidth() <= MinPenSizeAA) {
1053                     if (bs.getDashArray() == null) {
1054                         strokeState = STROKE_THIN;
1055                     } else {
1056                         strokeState = STROKE_THINDASHED;
1057                     }
1058                 } else {
1059                     strokeState = STROKE_WIDE;
1060                 }
1061             } else {
1062                 if (bs == defaultStroke) {
1063                     strokeState = STROKE_THIN;
1064                 } else if (bs.getLineWidth() <= 1.0f) {
1065                     if (bs.getDashArray() == null) {
1066                         strokeState = STROKE_THIN;
1067                     } else {
1068                         strokeState = STROKE_THINDASHED;
1069                     }
1070                 } else {
1071                     strokeState = STROKE_WIDE;
1072                 }
1073             }
1074         } else {
1075             double widthsquared;
1076             if ((transform.getType() & NON_UNIFORM_SCALE_MASK) == 0) {
1077                 /* sqrt omitted, compare to squared limits below. */
1078                 widthsquared = Math.abs(transform.getDeterminant());
1079             } else {
1080                 /* First calculate the "maximum scale" of this transform. */
1081                 double A = transform.getScaleX();       // m00
1082                 double C = transform.getShearX();       // m01
1083                 double B = transform.getShearY();       // m10
1084                 double D = transform.getScaleY();       // m11
1085 
1086                 /*
1087                  * Given a 2 x 2 affine matrix [ A B ] such that
1088                  *                             [ C D ]
1089                  * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
1090                  * find the maximum magnitude (norm) of the vector v'
1091                  * with the constraint (x^2 + y^2 = 1).
1092                  * The equation to maximize is
1093                  *     |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
1094                  * or  |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
1095                  * Since sqrt is monotonic we can maximize |v'|^2
1096                  * instead and plug in the substitution y = sqrt(1 - x^2).
1097                  * Trigonometric equalities can then be used to get
1098                  * rid of most of the sqrt terms.
1099                  */
1100                 double EA = A*A + B*B;          // x^2 coefficient
1101                 double EB = 2*(A*C + B*D);      // xy coefficient
1102                 double EC = C*C + D*D;          // y^2 coefficient
1103 
1104                 /*
1105                  * There is a lot of calculus omitted here.
1106                  *
1107                  * Conceptually, in the interests of understanding the
1108                  * terms that the calculus produced we can consider
1109                  * that EA and EC end up providing the lengths along
1110                  * the major axes and the hypot term ends up being an
1111                  * adjustment for the additional length along the off-axis
1112                  * angle of rotated or sheared ellipses as well as an
1113                  * adjustment for the fact that the equation below
1114                  * averages the two major axis lengths.  (Notice that
1115                  * the hypot term contains a part which resolves to the
1116                  * difference of these two axis lengths in the absence
1117                  * of rotation.)
1118                  *
1119                  * In the calculus, the ratio of the EB and (EA-EC) terms
1120                  * ends up being the tangent of 2*theta where theta is
1121                  * the angle that the long axis of the ellipse makes
1122                  * with the horizontal axis.  Thus, this equation is
1123                  * calculating the length of the hypotenuse of a triangle
1124                  * along that axis.
1125                  */
1126                 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
1127 
1128                 /* sqrt omitted, compare to squared limits below. */
1129                 widthsquared = ((EA + EC + hypot)/2.0);
1130             }
1131             if (bs != defaultStroke) {
1132                 widthsquared *= bs.getLineWidth() * bs.getLineWidth();
1133             }
1134             if (widthsquared <=
1135                 (aa ? MinPenSizeAASquared : MinPenSizeSquared))
1136             {
1137                 if (bs.getDashArray() == null) {
1138                     strokeState = STROKE_THIN;
1139                 } else {
1140                     strokeState = STROKE_THINDASHED;
1141                 }
1142             } else {
1143                 strokeState = STROKE_WIDE;
1144             }
1145         }
1146     }
1147 
1148     /*
1149      * Sets the Stroke in the current graphics state.
1150      * @param s The Stroke object to be used to stroke a Path in
1151      * the rendering process.
1152      * @see BasicStroke
1153      */
1154     public void setStroke(Stroke s) {
1155         if (s == null) {
1156             throw new IllegalArgumentException("null Stroke");
1157         }
1158         int saveStrokeState = strokeState;
1159         stroke = s;
1160         if (s instanceof BasicStroke) {
1161             validateBasicStroke((BasicStroke) s);
1162         } else {
1163             strokeState = STROKE_CUSTOM;
1164         }
1165         if (strokeState != saveStrokeState) {
1166             invalidatePipe();
1167         }
1168     }
1169 
1170     /**
1171      * Sets the preferences for the rendering algorithms.
1172      * Hint categories include controls for rendering quality and
1173      * overall time/quality trade-off in the rendering process.
1174      * @param hintKey The key of hint to be set. The strings are
1175      * defined in the RenderingHints class.
1176      * @param hintValue The value indicating preferences for the specified
1177      * hint category. These strings are defined in the RenderingHints
1178      * class.
1179      * @see RenderingHints
1180      */
1181     public void setRenderingHint(Key hintKey, Object hintValue) {
1182         // If we recognize the key, we must recognize the value
1183         //     otherwise throw an IllegalArgumentException
1184         //     and do not change the Hints object
1185         // If we do not recognize the key, just pass it through
1186         //     to the Hints object untouched
1187         if (!hintKey.isCompatibleValue(hintValue)) {
1188             throw new IllegalArgumentException
1189                 (hintValue+" is not compatible with "+hintKey);
1190         }
1191         if (hintKey instanceof SunHints.Key) {
1192             boolean stateChanged;
1193             boolean textStateChanged = false;
1194             boolean recognized = true;
1195             SunHints.Key sunKey = (SunHints.Key) hintKey;
1196             int newHint;
1197             if (sunKey == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST) {
1198                 newHint = ((Integer)hintValue).intValue();
1199             } else {
1200                 newHint = ((SunHints.Value) hintValue).getIndex();
1201             }
1202             switch (sunKey.getIndex()) {
1203             case SunHints.INTKEY_RENDERING:
1204                 stateChanged = (renderHint != newHint);
1205                 if (stateChanged) {
1206                     renderHint = newHint;
1207                     if (interpolationHint == -1) {
1208                         interpolationType =
1209                             (newHint == SunHints.INTVAL_RENDER_QUALITY
1210                              ? AffineTransformOp.TYPE_BILINEAR
1211                              : AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
1212                     }
1213                 }
1214                 break;
1215             case SunHints.INTKEY_ANTIALIASING:
1216                 stateChanged = (antialiasHint != newHint);
1217                 antialiasHint = newHint;
1218                 if (stateChanged) {
1219                     textStateChanged =
1220                         (textAntialiasHint ==
1221                          SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT);
1222                     if (strokeState != STROKE_CUSTOM) {
1223                         validateBasicStroke((BasicStroke) stroke);
1224                     }
1225                 }
1226                 break;
1227             case SunHints.INTKEY_TEXT_ANTIALIASING:
1228                 stateChanged = (textAntialiasHint != newHint);
1229                 textStateChanged = stateChanged;
1230                 textAntialiasHint = newHint;
1231                 break;
1232             case SunHints.INTKEY_FRACTIONALMETRICS:
1233                 stateChanged = (fractionalMetricsHint != newHint);
1234                 textStateChanged = stateChanged;
1235                 fractionalMetricsHint = newHint;
1236                 break;
1237             case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1238                 stateChanged = false;
1239                 /* Already have validated it is an int 100 <= newHint <= 250 */
1240                 lcdTextContrast = newHint;
1241                 break;
1242             case SunHints.INTKEY_INTERPOLATION:
1243                 interpolationHint = newHint;
1244                 switch (newHint) {
1245                 case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1246                     newHint = AffineTransformOp.TYPE_BICUBIC;
1247                     break;
1248                 case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1249                     newHint = AffineTransformOp.TYPE_BILINEAR;
1250                     break;
1251                 default:
1252                 case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1253                     newHint = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1254                     break;
1255                 }
1256                 stateChanged = (interpolationType != newHint);
1257                 interpolationType = newHint;
1258                 break;
1259             case SunHints.INTKEY_STROKE_CONTROL:
1260                 stateChanged = (strokeHint != newHint);
1261                 strokeHint = newHint;
1262                 break;
1263             case SunHints.INTKEY_RESOLUTION_VARIANT:
1264                 stateChanged = (resolutionVariantHint != newHint);
1265                 resolutionVariantHint = newHint;
1266                 break;
1267             default:
1268                 recognized = false;
1269                 stateChanged = false;
1270                 break;
1271             }
1272             if (recognized) {
1273                 if (stateChanged) {
1274                     invalidatePipe();
1275                     if (textStateChanged) {
1276                         fontMetrics = null;
1277                         this.cachedFRC = null;
1278                         validFontInfo = false;
1279                         this.glyphVectorFontInfo = null;
1280                     }
1281                 }
1282                 if (hints != null) {
1283                     hints.put(hintKey, hintValue);
1284                 }
1285                 return;
1286             }
1287         }
1288         // Nothing we recognize so none of "our state" has changed
1289         if (hints == null) {
1290             hints = makeHints(null);
1291         }
1292         hints.put(hintKey, hintValue);
1293     }
1294 
1295 
1296     /**
1297      * Returns the preferences for the rendering algorithms.
1298      * @param hintCategory The category of hint to be set. The strings
1299      * are defined in the RenderingHints class.
1300      * @return The preferences for rendering algorithms. The strings
1301      * are defined in the RenderingHints class.
1302      * @see RenderingHints
1303      */
1304     public Object getRenderingHint(Key hintKey) {
1305         if (hints != null) {
1306             return hints.get(hintKey);
1307         }
1308         if (!(hintKey instanceof SunHints.Key)) {
1309             return null;
1310         }
1311         int keyindex = ((SunHints.Key)hintKey).getIndex();
1312         switch (keyindex) {
1313         case SunHints.INTKEY_RENDERING:
1314             return SunHints.Value.get(SunHints.INTKEY_RENDERING,
1315                                       renderHint);
1316         case SunHints.INTKEY_ANTIALIASING:
1317             return SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,
1318                                       antialiasHint);
1319         case SunHints.INTKEY_TEXT_ANTIALIASING:
1320             return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
1321                                       textAntialiasHint);
1322         case SunHints.INTKEY_FRACTIONALMETRICS:
1323             return SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
1324                                       fractionalMetricsHint);
1325         case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1326             return lcdTextContrast;
1327         case SunHints.INTKEY_INTERPOLATION:
1328             switch (interpolationHint) {
1329             case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1330                 return SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1331             case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1332                 return SunHints.VALUE_INTERPOLATION_BILINEAR;
1333             case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1334                 return SunHints.VALUE_INTERPOLATION_BICUBIC;
1335             }
1336             return null;
1337         case SunHints.INTKEY_STROKE_CONTROL:
1338             return SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,
1339                                       strokeHint);
1340         case SunHints.INTKEY_RESOLUTION_VARIANT:
1341             return SunHints.Value.get(SunHints.INTKEY_RESOLUTION_VARIANT,
1342                                       resolutionVariantHint);
1343         }
1344         return null;
1345     }
1346 
1347     /**
1348      * Sets the preferences for the rendering algorithms.
1349      * Hint categories include controls for rendering quality and
1350      * overall time/quality trade-off in the rendering process.
1351      * @param hints The rendering hints to be set
1352      * @see RenderingHints
1353      */
1354     public void setRenderingHints(Map<?,?> hints) {
1355         this.hints = null;
1356         renderHint = SunHints.INTVAL_RENDER_DEFAULT;
1357         antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
1358         textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
1359         fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
1360         lcdTextContrast = lcdTextContrastDefaultValue;
1361         interpolationHint = -1;
1362         interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1363         boolean customHintPresent = false;
1364         Iterator<?> iter = hints.keySet().iterator();
1365         while (iter.hasNext()) {
1366             Object key = iter.next();
1367             if (key == SunHints.KEY_RENDERING ||
1368                 key == SunHints.KEY_ANTIALIASING ||
1369                 key == SunHints.KEY_TEXT_ANTIALIASING ||
1370                 key == SunHints.KEY_FRACTIONALMETRICS ||
1371                 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||
1372                 key == SunHints.KEY_STROKE_CONTROL ||
1373                 key == SunHints.KEY_INTERPOLATION)
1374             {
1375                 setRenderingHint((Key) key, hints.get(key));
1376             } else {
1377                 customHintPresent = true;
1378             }
1379         }
1380         if (customHintPresent) {
1381             this.hints = makeHints(hints);
1382         }
1383         invalidatePipe();
1384     }
1385 
1386     /**
1387      * Adds a number of preferences for the rendering algorithms.
1388      * Hint categories include controls for rendering quality and
1389      * overall time/quality trade-off in the rendering process.
1390      * @param hints The rendering hints to be set
1391      * @see RenderingHints
1392      */
1393     public void addRenderingHints(Map<?,?> hints) {
1394         boolean customHintPresent = false;
1395         Iterator<?> iter = hints.keySet().iterator();
1396         while (iter.hasNext()) {
1397             Object key = iter.next();
1398             if (key == SunHints.KEY_RENDERING ||
1399                 key == SunHints.KEY_ANTIALIASING ||
1400                 key == SunHints.KEY_TEXT_ANTIALIASING ||
1401                 key == SunHints.KEY_FRACTIONALMETRICS ||
1402                 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||
1403                 key == SunHints.KEY_STROKE_CONTROL ||
1404                 key == SunHints.KEY_INTERPOLATION)
1405             {
1406                 setRenderingHint((Key) key, hints.get(key));
1407             } else {
1408                 customHintPresent = true;
1409             }
1410         }
1411         if (customHintPresent) {
1412             if (this.hints == null) {
1413                 this.hints = makeHints(hints);
1414             } else {
1415                 this.hints.putAll(hints);
1416             }
1417         }
1418     }
1419 
1420     /**
1421      * Gets the preferences for the rendering algorithms.
1422      * Hint categories include controls for rendering quality and
1423      * overall time/quality trade-off in the rendering process.
1424      * @see RenderingHints
1425      */
1426     public RenderingHints getRenderingHints() {
1427         if (hints == null) {
1428             return makeHints(null);
1429         } else {
1430             return (RenderingHints) hints.clone();
1431         }
1432     }
1433 
1434     RenderingHints makeHints(Map<?,?> hints) {
1435         RenderingHints model = new RenderingHints(null);
1436         if (hints != null) {
1437             model.putAll(hints);
1438         }
1439         model.put(SunHints.KEY_RENDERING,
1440                   SunHints.Value.get(SunHints.INTKEY_RENDERING,
1441                                      renderHint));
1442         model.put(SunHints.KEY_ANTIALIASING,
1443                   SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,
1444                                      antialiasHint));
1445         model.put(SunHints.KEY_TEXT_ANTIALIASING,
1446                   SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
1447                                      textAntialiasHint));
1448         model.put(SunHints.KEY_FRACTIONALMETRICS,
1449                   SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
1450                                      fractionalMetricsHint));
1451         model.put(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST,
1452                   Integer.valueOf(lcdTextContrast));
1453         Object value;
1454         switch (interpolationHint) {
1455         case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1456             value = SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1457             break;
1458         case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1459             value = SunHints.VALUE_INTERPOLATION_BILINEAR;
1460             break;
1461         case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1462             value = SunHints.VALUE_INTERPOLATION_BICUBIC;
1463             break;
1464         default:
1465             value = null;
1466             break;
1467         }
1468         if (value != null) {
1469             model.put(SunHints.KEY_INTERPOLATION, value);
1470         }
1471         model.put(SunHints.KEY_STROKE_CONTROL,
1472                   SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,
1473                                      strokeHint));
1474         return model;
1475     }
1476 
1477     /**
1478      * Concatenates the current transform of this Graphics2D with a
1479      * translation transformation.
1480      * This is equivalent to calling transform(T), where T is an
1481      * AffineTransform represented by the following matrix:
1482      * <pre>
1483      *          [   1    0    tx  ]
1484      *          [   0    1    ty  ]
1485      *          [   0    0    1   ]
1486      * </pre>
1487      */
1488     public void translate(double tx, double ty) {
1489         transform.translate(tx, ty);
1490         invalidateTransform();
1491     }
1492 
1493     /**
1494      * Concatenates the current transform of this Graphics2D with a
1495      * rotation transformation.
1496      * This is equivalent to calling transform(R), where R is an
1497      * AffineTransform represented by the following matrix:
1498      * <pre>
1499      *          [   cos(theta)    -sin(theta)    0   ]
1500      *          [   sin(theta)     cos(theta)    0   ]
1501      *          [       0              0         1   ]
1502      * </pre>
1503      * Rotating with a positive angle theta rotates points on the positive
1504      * x axis toward the positive y axis.
1505      * @param theta The angle of rotation in radians.
1506      */
1507     public void rotate(double theta) {
1508         transform.rotate(theta);
1509         invalidateTransform();
1510     }
1511 
1512     /**
1513      * Concatenates the current transform of this Graphics2D with a
1514      * translated rotation transformation.
1515      * This is equivalent to the following sequence of calls:
1516      * <pre>
1517      *          translate(x, y);
1518      *          rotate(theta);
1519      *          translate(-x, -y);
1520      * </pre>
1521      * Rotating with a positive angle theta rotates points on the positive
1522      * x axis toward the positive y axis.
1523      * @param theta The angle of rotation in radians.
1524      * @param x The x coordinate of the origin of the rotation
1525      * @param y The x coordinate of the origin of the rotation
1526      */
1527     public void rotate(double theta, double x, double y) {
1528         transform.rotate(theta, x, y);
1529         invalidateTransform();
1530     }
1531 
1532     /**
1533      * Concatenates the current transform of this Graphics2D with a
1534      * scaling transformation.
1535      * This is equivalent to calling transform(S), where S is an
1536      * AffineTransform represented by the following matrix:
1537      * <pre>
1538      *          [   sx   0    0   ]
1539      *          [   0    sy   0   ]
1540      *          [   0    0    1   ]
1541      * </pre>
1542      */
1543     public void scale(double sx, double sy) {
1544         transform.scale(sx, sy);
1545         invalidateTransform();
1546     }
1547 
1548     /**
1549      * Concatenates the current transform of this Graphics2D with a
1550      * shearing transformation.
1551      * This is equivalent to calling transform(SH), where SH is an
1552      * AffineTransform represented by the following matrix:
1553      * <pre>
1554      *          [   1   shx   0   ]
1555      *          [  shy   1    0   ]
1556      *          [   0    0    1   ]
1557      * </pre>
1558      * @param shx The factor by which coordinates are shifted towards the
1559      * positive X axis direction according to their Y coordinate
1560      * @param shy The factor by which coordinates are shifted towards the
1561      * positive Y axis direction according to their X coordinate
1562      */
1563     public void shear(double shx, double shy) {
1564         transform.shear(shx, shy);
1565         invalidateTransform();
1566     }
1567 
1568     /**
1569      * Composes a Transform object with the transform in this
1570      * Graphics2D according to the rule last-specified-first-applied.
1571      * If the currrent transform is Cx, the result of composition
1572      * with Tx is a new transform Cx'.  Cx' becomes the current
1573      * transform for this Graphics2D.
1574      * Transforming a point p by the updated transform Cx' is
1575      * equivalent to first transforming p by Tx and then transforming
1576      * the result by the original transform Cx.  In other words,
1577      * Cx'(p) = Cx(Tx(p)).
1578      * A copy of the Tx is made, if necessary, so further
1579      * modifications to Tx do not affect rendering.
1580      * @param Tx The Transform object to be composed with the current
1581      * transform.
1582      * @see #setTransform
1583      * @see AffineTransform
1584      */
1585     public void transform(AffineTransform xform) {
1586         this.transform.concatenate(xform);
1587         invalidateTransform();
1588     }
1589 
1590     /**
1591      * Translate
1592      */
1593     public void translate(int x, int y) {
1594         transform.translate(x, y);
1595         if (transformState <= TRANSFORM_INT_TRANSLATE) {
1596             transX += x;
1597             transY += y;
1598             transformState = (((transX | transY) == 0) ?
1599                               TRANSFORM_ISIDENT : TRANSFORM_INT_TRANSLATE);
1600         } else {
1601             invalidateTransform();
1602         }
1603     }
1604 
1605     /**
1606      * Sets the Transform in the current graphics state.
1607      * @param Tx The Transform object to be used in the rendering process.
1608      * @see #transform
1609      * @see TransformChain
1610      * @see AffineTransform
1611      */
1612     @Override
1613     public void setTransform(AffineTransform Tx) {
1614         if ((constrainX | constrainY) == 0) {
1615             transform.setTransform(Tx);
1616         } else {
1617             transform.setToTranslation(constrainX, constrainY);

1618             transform.concatenate(Tx);
1619         }
1620         invalidateTransform();
1621     }
1622 
1623     protected void invalidateTransform() {
1624         int type = transform.getType();
1625         int origTransformState = transformState;
1626         if (type == AffineTransform.TYPE_IDENTITY) {
1627             transformState = TRANSFORM_ISIDENT;
1628             transX = transY = 0;
1629         } else if (type == AffineTransform.TYPE_TRANSLATION) {
1630             double dtx = transform.getTranslateX();
1631             double dty = transform.getTranslateY();
1632             transX = (int) Math.floor(dtx + 0.5);
1633             transY = (int) Math.floor(dty + 0.5);
1634             if (dtx == transX && dty == transY) {
1635                 transformState = TRANSFORM_INT_TRANSLATE;
1636             } else {
1637                 transformState = TRANSFORM_ANY_TRANSLATE;
1638             }
1639         } else if ((type & (AffineTransform.TYPE_FLIP |
1640                             AffineTransform.TYPE_MASK_ROTATION |
1641                             AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0)
1642         {
1643             transformState = TRANSFORM_TRANSLATESCALE;
1644             transX = transY = 0;
1645         } else {
1646             transformState = TRANSFORM_GENERIC;
1647             transX = transY = 0;
1648         }
1649 
1650         if (transformState >= TRANSFORM_TRANSLATESCALE ||
1651             origTransformState >= TRANSFORM_TRANSLATESCALE)
1652         {
1653             /* Its only in this case that the previous or current transform
1654              * was more than a translate that font info is invalidated
1655              */
1656             cachedFRC = null;
1657             this.validFontInfo = false;
1658             this.fontMetrics = null;
1659             this.glyphVectorFontInfo = null;
1660 
1661             if (transformState != origTransformState) {
1662                 invalidatePipe();
1663             }
1664         }
1665         if (strokeState != STROKE_CUSTOM) {
1666             validateBasicStroke((BasicStroke) stroke);
1667         }
1668     }
1669 
1670     /**
1671      * Returns the current Transform in the Graphics2D state.
1672      * @see #transform
1673      * @see #setTransform
1674      */
1675     @Override
1676     public AffineTransform getTransform() {
1677         if ((constrainX | constrainY) == 0) {
1678             return new AffineTransform(transform);
1679         }
1680         AffineTransform tx
1681                 = AffineTransform.getTranslateInstance(-constrainX, -constrainY);


1682         tx.concatenate(transform);
1683         return tx;
1684     }
1685 
1686     /**
1687      * Returns the current Transform ignoring the "constrain"
1688      * rectangle.
1689      */
1690     public AffineTransform cloneTransform() {
1691         return new AffineTransform(transform);
1692     }
1693 
1694     /**
1695      * Returns the current Paint in the Graphics2D state.
1696      * @see #setPaint
1697      * @see java.awt.Graphics#setColor
1698      */
1699     public Paint getPaint() {
1700         return paint;
1701     }
1702 
1703     /**
1704      * Returns the current Composite in the Graphics2D state.
1705      * @see #setComposite
1706      */
1707     public Composite getComposite() {
1708         return composite;
1709     }
1710 
1711     public Color getColor() {
1712         return foregroundColor;
1713     }
1714 
1715     /*
1716      * Validate the eargb and pixel fields against the current color.
1717      *
1718      * The eargb field must take into account the extraAlpha
1719      * value of an AlphaComposite.  It may also take into account
1720      * the Fsrc Porter-Duff blending function if such a function is
1721      * a constant (see handling of Clear mode below).  For instance,
1722      * by factoring in the (Fsrc == 0) state of the Clear mode we can
1723      * use a SrcNoEa loop just as easily as a general Alpha loop
1724      * since the math will be the same in both cases.
1725      *
1726      * The pixel field will always be the best pixel data choice for
1727      * the final result of all calculations applied to the eargb field.
1728      *
1729      * Note that this method is only necessary under the following
1730      * conditions:
1731      *     (paintState <= PAINT_ALPHA_COLOR &&
1732      *      compositeState <= COMP_CUSTOM)
1733      * though nothing bad will happen if it is run in other states.
1734      */
1735     void validateColor() {
1736         int eargb;
1737         if (imageComp == CompositeType.Clear) {
1738             eargb = 0;
1739         } else {
1740             eargb = foregroundColor.getRGB();
1741             if (compositeState <= COMP_ALPHA &&
1742                 imageComp != CompositeType.SrcNoEa &&
1743                 imageComp != CompositeType.SrcOverNoEa)
1744             {
1745                 AlphaComposite alphacomp = (AlphaComposite) composite;
1746                 int a = Math.round(alphacomp.getAlpha() * (eargb >>> 24));
1747                 eargb = (eargb & 0x00ffffff) | (a << 24);
1748             }
1749         }
1750         this.eargb = eargb;
1751         this.pixel = surfaceData.pixelFor(eargb);
1752     }
1753 
1754     public void setColor(Color color) {
1755         if (color == null || color == paint) {
1756             return;
1757         }
1758         this.paint = foregroundColor = color;
1759         validateColor();
1760         if ((eargb >> 24) == -1) {
1761             if (paintState == PAINT_OPAQUECOLOR) {
1762                 return;
1763             }
1764             paintState = PAINT_OPAQUECOLOR;
1765             if (imageComp == CompositeType.SrcOverNoEa) {
1766                 // special case where compState depends on opacity of paint
1767                 compositeState = COMP_ISCOPY;
1768             }
1769         } else {
1770             if (paintState == PAINT_ALPHACOLOR) {
1771                 return;
1772             }
1773             paintState = PAINT_ALPHACOLOR;
1774             if (imageComp == CompositeType.SrcOverNoEa) {
1775                 // special case where compState depends on opacity of paint
1776                 compositeState = COMP_ALPHA;
1777             }
1778         }
1779         validFontInfo = false;
1780         invalidatePipe();
1781     }
1782 
1783     /**
1784      * Sets the background color in this context used for clearing a region.
1785      * When Graphics2D is constructed for a component, the backgroung color is
1786      * inherited from the component. Setting the background color in the
1787      * Graphics2D context only affects the subsequent clearRect() calls and
1788      * not the background color of the component. To change the background
1789      * of the component, use appropriate methods of the component.
1790      * @param color The background color that should be used in
1791      * subsequent calls to clearRect().
1792      * @see getBackground
1793      * @see Graphics.clearRect()
1794      */
1795     public void setBackground(Color color) {
1796         backgroundColor = color;
1797     }
1798 
1799     /**
1800      * Returns the background color used for clearing a region.
1801      * @see setBackground
1802      */
1803     public Color getBackground() {
1804         return backgroundColor;
1805     }
1806 
1807     /**
1808      * Returns the current Stroke in the Graphics2D state.
1809      * @see setStroke
1810      */
1811     public Stroke getStroke() {
1812         return stroke;
1813     }
1814 
1815     public Rectangle getClipBounds() {
1816         if (clipState == CLIP_DEVICE) {
1817             return null;
1818         }
1819         return getClipBounds(new Rectangle());
1820     }
1821 
1822     public Rectangle getClipBounds(Rectangle r) {
1823         if (clipState != CLIP_DEVICE) {
1824             if (transformState <= TRANSFORM_INT_TRANSLATE) {
1825                 if (usrClip instanceof Rectangle) {
1826                     r.setBounds((Rectangle) usrClip);
1827                 } else {
1828                     r.setFrame(usrClip.getBounds2D());
1829                 }
1830                 r.translate(-transX, -transY);
1831             } else {
1832                 r.setFrame(getClip().getBounds2D());
1833             }
1834         } else if (r == null) {
1835             throw new NullPointerException("null rectangle parameter");
1836         }
1837         return r;
1838     }
1839 
1840     public boolean hitClip(int x, int y, int width, int height) {
1841         if (width <= 0 || height <= 0) {
1842             return false;
1843         }
1844         if (transformState > TRANSFORM_INT_TRANSLATE) {
1845             // Note: Technically the most accurate test would be to
1846             // raster scan the parallelogram of the transformed rectangle
1847             // and do a span for span hit test against the clip, but for
1848             // speed we approximate the test with a bounding box of the
1849             // transformed rectangle.  The cost of rasterizing the
1850             // transformed rectangle is probably high enough that it is
1851             // not worth doing so to save the caller from having to call
1852             // a rendering method where we will end up discovering the
1853             // same answer in about the same amount of time anyway.
1854             // This logic breaks down if this hit test is being performed
1855             // on the bounds of a group of shapes in which case it might
1856             // be beneficial to be a little more accurate to avoid lots
1857             // of subsequent rendering calls.  In either case, this relaxed
1858             // test should not be significantly less accurate than the
1859             // optimal test for most transforms and so the conservative
1860             // answer should not cause too much extra work.
1861 
1862             double d[] = {
1863                 x, y,
1864                 x+width, y,
1865                 x, y+height,
1866                 x+width, y+height
1867             };
1868             transform.transform(d, 0, d, 0, 4);
1869             x = (int) Math.floor(Math.min(Math.min(d[0], d[2]),
1870                                           Math.min(d[4], d[6])));
1871             y = (int) Math.floor(Math.min(Math.min(d[1], d[3]),
1872                                           Math.min(d[5], d[7])));
1873             width = (int) Math.ceil(Math.max(Math.max(d[0], d[2]),
1874                                              Math.max(d[4], d[6])));
1875             height = (int) Math.ceil(Math.max(Math.max(d[1], d[3]),
1876                                               Math.max(d[5], d[7])));
1877         } else {
1878             x += transX;
1879             y += transY;
1880             width += x;
1881             height += y;
1882         }
1883 
1884         try {
1885             if (!getCompClip().intersectsQuickCheckXYXY(x, y, width, height)) {
1886                 return false;
1887             }
1888         } catch (InvalidPipeException e) {
1889             return false;
1890         }
1891         // REMIND: We could go one step further here and examine the
1892         // non-rectangular clip shape more closely if there is one.
1893         // Since the clip has already been rasterized, the performance
1894         // penalty of doing the scan is probably still within the bounds
1895         // of a good tradeoff between speed and quality of the answer.
1896         return true;
1897     }
1898 
1899     protected void validateCompClip() {
1900         int origClipState = clipState;
1901         if (usrClip == null) {
1902             clipState = CLIP_DEVICE;
1903             clipRegion = devClip;
1904         } else if (usrClip instanceof Rectangle2D) {
1905             clipState = CLIP_RECTANGULAR;
1906             if (usrClip instanceof Rectangle) {
1907                 clipRegion = devClip.getIntersection((Rectangle)usrClip);
1908             } else {
1909                 clipRegion = devClip.getIntersection(usrClip.getBounds());
1910             }
1911         } else {
1912             PathIterator cpi = usrClip.getPathIterator(null);
1913             int box[] = new int[4];
1914             ShapeSpanIterator sr = LoopPipe.getFillSSI(this);
1915             try {
1916                 sr.setOutputArea(devClip);
1917                 sr.appendPath(cpi);
1918                 sr.getPathBox(box);
1919                 Region r = Region.getInstance(box);
1920                 r.appendSpans(sr);
1921                 clipRegion = r;
1922                 clipState =
1923                     r.isRectangular() ? CLIP_RECTANGULAR : CLIP_SHAPE;
1924             } finally {
1925                 sr.dispose();
1926             }
1927         }
1928         if (origClipState != clipState &&
1929             (clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE))
1930         {
1931             validFontInfo = false;
1932             invalidatePipe();
1933         }
1934     }
1935 
1936     static final int NON_RECTILINEAR_TRANSFORM_MASK =
1937         (AffineTransform.TYPE_GENERAL_TRANSFORM |
1938          AffineTransform.TYPE_GENERAL_ROTATION);
1939 
1940     protected Shape transformShape(Shape s) {
1941         if (s == null) {
1942             return null;
1943         }
1944         if (transformState > TRANSFORM_INT_TRANSLATE) {
1945             return transformShape(transform, s);
1946         } else {
1947             return transformShape(transX, transY, s);
1948         }
1949     }
1950 
1951     public Shape untransformShape(Shape s) {
1952         if (s == null) {
1953             return null;
1954         }
1955         if (transformState > TRANSFORM_INT_TRANSLATE) {
1956             try {
1957                 return transformShape(transform.createInverse(), s);
1958             } catch (NoninvertibleTransformException e) {
1959                 return null;
1960             }
1961         } else {
1962             return transformShape(-transX, -transY, s);
1963         }
1964     }
1965 
1966     protected static Shape transformShape(int tx, int ty, Shape s) {
1967         if (s == null) {
1968             return null;
1969         }
1970 
1971         if (s instanceof Rectangle) {
1972             Rectangle r = s.getBounds();
1973             r.translate(tx, ty);
1974             return r;
1975         }
1976         if (s instanceof Rectangle2D) {
1977             Rectangle2D rect = (Rectangle2D) s;
1978             return new Rectangle2D.Double(rect.getX() + tx,
1979                                           rect.getY() + ty,
1980                                           rect.getWidth(),
1981                                           rect.getHeight());
1982         }
1983 
1984         if (tx == 0 && ty == 0) {
1985             return cloneShape(s);
1986         }
1987 
1988         AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty);
1989         return mat.createTransformedShape(s);
1990     }
1991 
1992     protected static Shape transformShape(AffineTransform tx, Shape clip) {
1993         if (clip == null) {
1994             return null;
1995         }
1996 
1997         if (clip instanceof Rectangle2D &&
1998             (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0)
1999         {
2000             Rectangle2D rect = (Rectangle2D) clip;
2001             double matrix[] = new double[4];
2002             matrix[0] = rect.getX();
2003             matrix[1] = rect.getY();
2004             matrix[2] = matrix[0] + rect.getWidth();
2005             matrix[3] = matrix[1] + rect.getHeight();
2006             tx.transform(matrix, 0, matrix, 0, 2);
2007             fixRectangleOrientation(matrix, rect);
2008             return new Rectangle2D.Double(matrix[0], matrix[1],
2009                                           matrix[2] - matrix[0],
2010                                           matrix[3] - matrix[1]);
2011         }
2012 
2013         if (tx.isIdentity()) {
2014             return cloneShape(clip);
2015         }
2016 
2017         return tx.createTransformedShape(clip);
2018     }
2019 
2020     /**
2021      * Sets orientation of the rectangle according to the clip.
2022      */
2023     private static void fixRectangleOrientation(double[] m, Rectangle2D clip) {
2024         if (clip.getWidth() > 0 != (m[2] - m[0] > 0)) {
2025             double t = m[0];
2026             m[0] = m[2];
2027             m[2] = t;
2028         }
2029         if (clip.getHeight() > 0 != (m[3] - m[1] > 0)) {
2030             double t = m[1];
2031             m[1] = m[3];
2032             m[3] = t;
2033         }
2034     }
2035 
2036     public void clipRect(int x, int y, int w, int h) {
2037         clip(new Rectangle(x, y, w, h));
2038     }
2039 
2040     public void setClip(int x, int y, int w, int h) {
2041         setClip(new Rectangle(x, y, w, h));
2042     }
2043 
2044     public Shape getClip() {
2045         return untransformShape(usrClip);
2046     }
2047 
2048     public void setClip(Shape sh) {
2049         usrClip = transformShape(sh);
2050         validateCompClip();
2051     }
2052 
2053     /**
2054      * Intersects the current clip with the specified Path and sets the
2055      * current clip to the resulting intersection. The clip is transformed
2056      * with the current transform in the Graphics2D state before being
2057      * intersected with the current clip. This method is used to make the
2058      * current clip smaller. To make the clip larger, use any setClip method.
2059      * @param p The Path to be intersected with the current clip.
2060      */
2061     public void clip(Shape s) {
2062         s = transformShape(s);
2063         if (usrClip != null) {
2064             s = intersectShapes(usrClip, s, true, true);
2065         }
2066         usrClip = s;
2067         validateCompClip();
2068     }
2069 
2070     public void setPaintMode() {
2071         setComposite(AlphaComposite.SrcOver);
2072     }
2073 
2074     public void setXORMode(Color c) {
2075         if (c == null) {
2076             throw new IllegalArgumentException("null XORColor");
2077         }
2078         setComposite(new XORComposite(c, surfaceData));
2079     }
2080 
2081     Blit lastCAblit;
2082     Composite lastCAcomp;
2083 
2084     public void copyArea(int x, int y, int w, int h, int dx, int dy) {
2085         try {
2086             doCopyArea(x, y, w, h, dx, dy);
2087         } catch (InvalidPipeException e) {
2088             try {
2089                 revalidateAll();
2090                 doCopyArea(x, y, w, h, dx, dy);
2091             } catch (InvalidPipeException e2) {
2092                 // Still catching the exception; we are not yet ready to
2093                 // validate the surfaceData correctly.  Fail for now and
2094                 // try again next time around.
2095             }
2096         } finally {
2097             surfaceData.markDirty();
2098         }
2099     }
2100 
2101     private void doCopyArea(int x, int y, int w, int h, int dx, int dy) {
2102         if (w <= 0 || h <= 0) {
2103             return;
2104         }
2105         SurfaceData theData = surfaceData;
2106         if (theData.copyArea(this, x, y, w, h, dx, dy)) {
2107             return;
2108         }
2109         if (transformState > TRANSFORM_TRANSLATESCALE) {
2110             throw new InternalError("transformed copyArea not implemented yet");
2111         }
2112         // REMIND: This method does not deal with missing data from the
2113         // source object (i.e. it does not send exposure events...)
2114 
2115         Region clip = getCompClip();
2116 
2117         Composite comp = composite;
2118         if (lastCAcomp != comp) {
2119             SurfaceType dsttype = theData.getSurfaceType();
2120             CompositeType comptype = imageComp;
2121             if (CompositeType.SrcOverNoEa.equals(comptype) &&
2122                 theData.getTransparency() == Transparency.OPAQUE)
2123             {
2124                 comptype = CompositeType.SrcNoEa;
2125             }
2126             lastCAblit = Blit.locate(dsttype, comptype, dsttype);
2127             lastCAcomp = comp;
2128         }
2129 
2130         double[] coords = {x, y, x + w, y + h, x + dx, y + dy};
2131         transform.transform(coords, 0, coords, 0, 3);
2132 
2133         x = (int)Math.ceil(coords[0] - 0.5);
2134         y = (int)Math.ceil(coords[1] - 0.5);
2135         w = ((int)Math.ceil(coords[2] - 0.5)) - x;
2136         h = ((int)Math.ceil(coords[3] - 0.5)) - y;
2137         dx = ((int)Math.ceil(coords[4] - 0.5)) - x;
2138         dy = ((int)Math.ceil(coords[5] - 0.5)) - y;
2139 
2140         // In case of negative scale transform, reflect the rect coords.
2141         if (w < 0) {
2142             w *= -1;
2143             x -= w;
2144         }
2145         if (h < 0) {
2146             h *= -1;
2147             y -= h;
2148         }
2149 
2150         Blit ob = lastCAblit;
2151         if (dy == 0 && dx > 0 && dx < w) {
2152             while (w > 0) {
2153                 int partW = Math.min(w, dx);
2154                 w -= partW;
2155                 int sx = x + w;
2156                 ob.Blit(theData, theData, comp, clip,
2157                         sx, y, sx+dx, y+dy, partW, h);
2158             }
2159             return;
2160         }
2161         if (dy > 0 && dy < h && dx > -w && dx < w) {
2162             while (h > 0) {
2163                 int partH = Math.min(h, dy);
2164                 h -= partH;
2165                 int sy = y + h;
2166                 ob.Blit(theData, theData, comp, clip,
2167                         x, sy, x+dx, sy+dy, w, partH);
2168             }
2169             return;
2170         }
2171         ob.Blit(theData, theData, comp, clip, x, y, x+dx, y+dy, w, h);
2172     }
2173 
2174     /*
2175     public void XcopyArea(int x, int y, int w, int h, int dx, int dy) {
2176         Rectangle rect = new Rectangle(x, y, w, h);
2177         rect = transformBounds(rect, transform);
2178         Point2D    point = new Point2D.Float(dx, dy);
2179         Point2D    root  = new Point2D.Float(0, 0);
2180         point = transform.transform(point, point);
2181         root  = transform.transform(root, root);
2182         int fdx = (int)(point.getX()-root.getX());
2183         int fdy = (int)(point.getY()-root.getY());
2184 
2185         Rectangle r = getCompBounds().intersection(rect.getBounds());
2186 
2187         if (r.isEmpty()) {
2188             return;
2189         }
2190 
2191         // Begin Rasterizer for Clip Shape
2192         boolean skipClip = true;
2193         byte[] clipAlpha = null;
2194 
2195         if (clipState == CLIP_SHAPE) {
2196 
2197             int box[] = new int[4];
2198 
2199             clipRegion.getBounds(box);
2200             Rectangle devR = new Rectangle(box[0], box[1],
2201                                            box[2] - box[0],
2202                                            box[3] - box[1]);
2203             if (!devR.isEmpty()) {
2204                 OutputManager mgr = getOutputManager();
2205                 RegionIterator ri = clipRegion.getIterator();
2206                 while (ri.nextYRange(box)) {
2207                     int spany = box[1];
2208                     int spanh = box[3] - spany;
2209                     while (ri.nextXBand(box)) {
2210                         int spanx = box[0];
2211                         int spanw = box[2] - spanx;
2212                         mgr.copyArea(this, null,
2213                                      spanw, 0,
2214                                      spanx, spany,
2215                                      spanw, spanh,
2216                                      fdx, fdy,
2217                                      null);
2218                     }
2219                 }
2220             }
2221             return;
2222         }
2223         // End Rasterizer for Clip Shape
2224 
2225         getOutputManager().copyArea(this, null,
2226                                     r.width, 0,
2227                                     r.x, r.y, r.width,
2228                                     r.height, fdx, fdy,
2229                                     null);
2230     }
2231     */
2232 
2233     public void drawLine(int x1, int y1, int x2, int y2) {
2234         try {
2235             drawpipe.drawLine(this, x1, y1, x2, y2);
2236         } catch (InvalidPipeException e) {
2237             try {
2238                 revalidateAll();
2239                 drawpipe.drawLine(this, x1, y1, x2, y2);
2240             } catch (InvalidPipeException e2) {
2241                 // Still catching the exception; we are not yet ready to
2242                 // validate the surfaceData correctly.  Fail for now and
2243                 // try again next time around.
2244             }
2245         } finally {
2246             surfaceData.markDirty();
2247         }
2248     }
2249 
2250     public void drawRoundRect(int x, int y, int w, int h, int arcW, int arcH) {
2251         try {
2252             drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);
2253         } catch (InvalidPipeException e) {
2254             try {
2255                 revalidateAll();
2256                 drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);
2257             } catch (InvalidPipeException e2) {
2258                 // Still catching the exception; we are not yet ready to
2259                 // validate the surfaceData correctly.  Fail for now and
2260                 // try again next time around.
2261             }
2262         } finally {
2263             surfaceData.markDirty();
2264         }
2265     }
2266 
2267     public void fillRoundRect(int x, int y, int w, int h, int arcW, int arcH) {
2268         try {
2269             fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);
2270         } catch (InvalidPipeException e) {
2271             try {
2272                 revalidateAll();
2273                 fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);
2274             } catch (InvalidPipeException e2) {
2275                 // Still catching the exception; we are not yet ready to
2276                 // validate the surfaceData correctly.  Fail for now and
2277                 // try again next time around.
2278             }
2279         } finally {
2280             surfaceData.markDirty();
2281         }
2282     }
2283 
2284     public void drawOval(int x, int y, int w, int h) {
2285         try {
2286             drawpipe.drawOval(this, x, y, w, h);
2287         } catch (InvalidPipeException e) {
2288             try {
2289                 revalidateAll();
2290                 drawpipe.drawOval(this, x, y, w, h);
2291             } catch (InvalidPipeException e2) {
2292                 // Still catching the exception; we are not yet ready to
2293                 // validate the surfaceData correctly.  Fail for now and
2294                 // try again next time around.
2295             }
2296         } finally {
2297             surfaceData.markDirty();
2298         }
2299     }
2300 
2301     public void fillOval(int x, int y, int w, int h) {
2302         try {
2303             fillpipe.fillOval(this, x, y, w, h);
2304         } catch (InvalidPipeException e) {
2305             try {
2306                 revalidateAll();
2307                 fillpipe.fillOval(this, x, y, w, h);
2308             } catch (InvalidPipeException e2) {
2309                 // Still catching the exception; we are not yet ready to
2310                 // validate the surfaceData correctly.  Fail for now and
2311                 // try again next time around.
2312             }
2313         } finally {
2314             surfaceData.markDirty();
2315         }
2316     }
2317 
2318     public void drawArc(int x, int y, int w, int h,
2319                         int startAngl, int arcAngl) {
2320         try {
2321             drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);
2322         } catch (InvalidPipeException e) {
2323             try {
2324                 revalidateAll();
2325                 drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);
2326             } catch (InvalidPipeException e2) {
2327                 // Still catching the exception; we are not yet ready to
2328                 // validate the surfaceData correctly.  Fail for now and
2329                 // try again next time around.
2330             }
2331         } finally {
2332             surfaceData.markDirty();
2333         }
2334     }
2335 
2336     public void fillArc(int x, int y, int w, int h,
2337                         int startAngl, int arcAngl) {
2338         try {
2339             fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);
2340         } catch (InvalidPipeException e) {
2341             try {
2342                 revalidateAll();
2343                 fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);
2344             } catch (InvalidPipeException e2) {
2345                 // Still catching the exception; we are not yet ready to
2346                 // validate the surfaceData correctly.  Fail for now and
2347                 // try again next time around.
2348             }
2349         } finally {
2350             surfaceData.markDirty();
2351         }
2352     }
2353 
2354     public void drawPolyline(int xPoints[], int yPoints[], int nPoints) {
2355         try {
2356             drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);
2357         } catch (InvalidPipeException e) {
2358             try {
2359                 revalidateAll();
2360                 drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);
2361             } catch (InvalidPipeException e2) {
2362                 // Still catching the exception; we are not yet ready to
2363                 // validate the surfaceData correctly.  Fail for now and
2364                 // try again next time around.
2365             }
2366         } finally {
2367             surfaceData.markDirty();
2368         }
2369     }
2370 
2371     public void drawPolygon(int xPoints[], int yPoints[], int nPoints) {
2372         try {
2373             drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);
2374         } catch (InvalidPipeException e) {
2375             try {
2376                 revalidateAll();
2377                 drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);
2378             } catch (InvalidPipeException e2) {
2379                 // Still catching the exception; we are not yet ready to
2380                 // validate the surfaceData correctly.  Fail for now and
2381                 // try again next time around.
2382             }
2383         } finally {
2384             surfaceData.markDirty();
2385         }
2386     }
2387 
2388     public void fillPolygon(int xPoints[], int yPoints[], int nPoints) {
2389         try {
2390             fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);
2391         } catch (InvalidPipeException e) {
2392             try {
2393                 revalidateAll();
2394                 fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);
2395             } catch (InvalidPipeException e2) {
2396                 // Still catching the exception; we are not yet ready to
2397                 // validate the surfaceData correctly.  Fail for now and
2398                 // try again next time around.
2399             }
2400         } finally {
2401             surfaceData.markDirty();
2402         }
2403     }
2404 
2405     public void drawRect (int x, int y, int w, int h) {
2406         try {
2407             drawpipe.drawRect(this, x, y, w, h);
2408         } catch (InvalidPipeException e) {
2409             try {
2410                 revalidateAll();
2411                 drawpipe.drawRect(this, x, y, w, h);
2412             } catch (InvalidPipeException e2) {
2413                 // Still catching the exception; we are not yet ready to
2414                 // validate the surfaceData correctly.  Fail for now and
2415                 // try again next time around.
2416             }
2417         } finally {
2418             surfaceData.markDirty();
2419         }
2420     }
2421 
2422     public void fillRect (int x, int y, int w, int h) {
2423         try {
2424             fillpipe.fillRect(this, x, y, w, h);
2425         } catch (InvalidPipeException e) {
2426             try {
2427                 revalidateAll();
2428                 fillpipe.fillRect(this, x, y, w, h);
2429             } catch (InvalidPipeException e2) {
2430                 // Still catching the exception; we are not yet ready to
2431                 // validate the surfaceData correctly.  Fail for now and
2432                 // try again next time around.
2433             }
2434         } finally {
2435             surfaceData.markDirty();
2436         }
2437     }
2438 
2439     private void revalidateAll() {
2440         try {
2441             // REMIND: This locking needs to be done around the
2442             // caller of this method so that the pipe stays valid
2443             // long enough to call the new primitive.
2444             // REMIND: No locking yet in screen SurfaceData objects!
2445             // surfaceData.lock();
2446             surfaceData = surfaceData.getReplacement();
2447             if (surfaceData == null) {
2448                 surfaceData = NullSurfaceData.theInstance;
2449             }
2450 
2451             invalidatePipe();
2452 
2453             // this will recalculate the composite clip
2454             setDevClip(surfaceData.getBounds());
2455 
2456             if (paintState <= PAINT_ALPHACOLOR) {
2457                 validateColor();
2458             }
2459             if (composite instanceof XORComposite) {
2460                 Color c = ((XORComposite) composite).getXorColor();
2461                 setComposite(new XORComposite(c, surfaceData));
2462             }
2463             validatePipe();
2464         } finally {
2465             // REMIND: No locking yet in screen SurfaceData objects!
2466             // surfaceData.unlock();
2467         }
2468     }
2469 
2470     public void clearRect(int x, int y, int w, int h) {
2471         // REMIND: has some "interesting" consequences if threads are
2472         // not synchronized
2473         Composite c = composite;
2474         Paint p = paint;
2475         setComposite(AlphaComposite.Src);
2476         setColor(getBackground());
2477         fillRect(x, y, w, h);
2478         setPaint(p);
2479         setComposite(c);
2480     }
2481 
2482     /**
2483      * Strokes the outline of a Path using the settings of the current
2484      * graphics state.  The rendering attributes applied include the
2485      * clip, transform, paint or color, composite and stroke attributes.
2486      * @param p The path to be drawn.
2487      * @see #setStroke
2488      * @see #setPaint
2489      * @see java.awt.Graphics#setColor
2490      * @see #transform
2491      * @see #setTransform
2492      * @see #clip
2493      * @see #setClip
2494      * @see #setComposite
2495      */
2496     public void draw(Shape s) {
2497         try {
2498             shapepipe.draw(this, s);
2499         } catch (InvalidPipeException e) {
2500             try {
2501                 revalidateAll();
2502                 shapepipe.draw(this, s);
2503             } catch (InvalidPipeException e2) {
2504                 // Still catching the exception; we are not yet ready to
2505                 // validate the surfaceData correctly.  Fail for now and
2506                 // try again next time around.
2507             }
2508         } finally {
2509             surfaceData.markDirty();
2510         }
2511     }
2512 
2513 
2514     /**
2515      * Fills the interior of a Path using the settings of the current
2516      * graphics state. The rendering attributes applied include the
2517      * clip, transform, paint or color, and composite.
2518      * @see #setPaint
2519      * @see java.awt.Graphics#setColor
2520      * @see #transform
2521      * @see #setTransform
2522      * @see #setComposite
2523      * @see #clip
2524      * @see #setClip
2525      */
2526     public void fill(Shape s) {
2527         try {
2528             shapepipe.fill(this, s);
2529         } catch (InvalidPipeException e) {
2530             try {
2531                 revalidateAll();
2532                 shapepipe.fill(this, s);
2533             } catch (InvalidPipeException e2) {
2534                 // Still catching the exception; we are not yet ready to
2535                 // validate the surfaceData correctly.  Fail for now and
2536                 // try again next time around.
2537             }
2538         } finally {
2539             surfaceData.markDirty();
2540         }
2541     }
2542 
2543     /**
2544      * Returns true if the given AffineTransform is an integer
2545      * translation.
2546      */
2547     private static boolean isIntegerTranslation(AffineTransform xform) {
2548         if (xform.isIdentity()) {
2549             return true;
2550         }
2551         if (xform.getType() == AffineTransform.TYPE_TRANSLATION) {
2552             double tx = xform.getTranslateX();
2553             double ty = xform.getTranslateY();
2554             return (tx == (int)tx && ty == (int)ty);
2555         }
2556         return false;
2557     }
2558 
2559     /**
2560      * Returns the index of the tile corresponding to the supplied position
2561      * given the tile grid offset and size along the same axis.
2562      */
2563     private static int getTileIndex(int p, int tileGridOffset, int tileSize) {
2564         p -= tileGridOffset;
2565         if (p < 0) {
2566             p += 1 - tileSize;          // force round to -infinity (ceiling)
2567         }
2568         return p/tileSize;
2569     }
2570 
2571     /**
2572      * Returns a rectangle in image coordinates that may be required
2573      * in order to draw the given image into the given clipping region
2574      * through a pair of AffineTransforms.  In addition, horizontal and
2575      * vertical padding factors for antialising and interpolation may
2576      * be used.
2577      */
2578     private static Rectangle getImageRegion(RenderedImage img,
2579                                             Region compClip,
2580                                             AffineTransform transform,
2581                                             AffineTransform xform,
2582                                             int padX, int padY) {
2583         Rectangle imageRect =
2584             new Rectangle(img.getMinX(), img.getMinY(),
2585                           img.getWidth(), img.getHeight());
2586 
2587         Rectangle result = null;
2588         try {
2589             double p[] = new double[8];
2590             p[0] = p[2] = compClip.getLoX();
2591             p[4] = p[6] = compClip.getHiX();
2592             p[1] = p[5] = compClip.getLoY();
2593             p[3] = p[7] = compClip.getHiY();
2594 
2595             // Inverse transform the output bounding rect
2596             transform.inverseTransform(p, 0, p, 0, 4);
2597             xform.inverseTransform(p, 0, p, 0, 4);
2598 
2599             // Determine a bounding box for the inverse transformed region
2600             double x0,x1,y0,y1;
2601             x0 = x1 = p[0];
2602             y0 = y1 = p[1];
2603 
2604             for (int i = 2; i < 8; ) {
2605                 double pt = p[i++];
2606                 if (pt < x0)  {
2607                     x0 = pt;
2608                 } else if (pt > x1) {
2609                     x1 = pt;
2610                 }
2611                 pt = p[i++];
2612                 if (pt < y0)  {
2613                     y0 = pt;
2614                 } else if (pt > y1) {
2615                     y1 = pt;
2616                 }
2617             }
2618 
2619             // This is padding for anti-aliasing and such.  It may
2620             // be more than is needed.
2621             int x = (int)x0 - padX;
2622             int w = (int)(x1 - x0 + 2*padX);
2623             int y = (int)y0 - padY;
2624             int h = (int)(y1 - y0 + 2*padY);
2625 
2626             Rectangle clipRect = new Rectangle(x,y,w,h);
2627             result = clipRect.intersection(imageRect);
2628         } catch (NoninvertibleTransformException nte) {
2629             // Worst case bounds are the bounds of the image.
2630             result = imageRect;
2631         }
2632 
2633         return result;
2634     }
2635 
2636     /**
2637      * Draws an image, applying a transform from image space into user space
2638      * before drawing.
2639      * The transformation from user space into device space is done with
2640      * the current transform in the Graphics2D.
2641      * The given transformation is applied to the image before the
2642      * transform attribute in the Graphics2D state is applied.
2643      * The rendering attributes applied include the clip, transform,
2644      * and composite attributes. Note that the result is
2645      * undefined, if the given transform is noninvertible.
2646      * @param img The image to be drawn. Does nothing if img is null.
2647      * @param xform The transformation from image space into user space.
2648      * @see #transform
2649      * @see #setTransform
2650      * @see #setComposite
2651      * @see #clip
2652      * @see #setClip
2653      */
2654     public void drawRenderedImage(RenderedImage img,
2655                                   AffineTransform xform) {
2656 
2657         if (img == null) {
2658             return;
2659         }
2660 
2661         // BufferedImage case: use a simple drawImage call
2662         if (img instanceof BufferedImage) {
2663             BufferedImage bufImg = (BufferedImage)img;
2664             drawImage(bufImg,xform,null);
2665             return;
2666         }
2667 
2668         // transformState tracks the state of transform and
2669         // transX, transY contain the integer casts of the
2670         // translation factors
2671         boolean isIntegerTranslate =
2672             (transformState <= TRANSFORM_INT_TRANSLATE) &&
2673             isIntegerTranslation(xform);
2674 
2675         // Include padding for interpolation/antialiasing if necessary
2676         int pad = isIntegerTranslate ? 0 : 3;
2677 
2678         Region clip;
2679         try {
2680             clip = getCompClip();
2681         } catch (InvalidPipeException e) {
2682             return;
2683         }
2684 
2685         // Determine the region of the image that may contribute to
2686         // the clipped drawing area
2687         Rectangle region = getImageRegion(img,
2688                                           clip,
2689                                           transform,
2690                                           xform,
2691                                           pad, pad);
2692         if (region.width <= 0 || region.height <= 0) {
2693             return;
2694         }
2695 
2696         // Attempt to optimize integer translation of tiled images.
2697         // Although theoretically we are O.K. if the concatenation of
2698         // the user transform and the device transform is an integer
2699         // translation, we'll play it safe and only optimize the case
2700         // where both are integer translations.
2701         if (isIntegerTranslate) {
2702             // Use optimized code
2703             // Note that drawTranslatedRenderedImage calls copyImage
2704             // which takes the user space to device space transform into
2705             // account, but we need to provide the image space to user space
2706             // translations.
2707 
2708             drawTranslatedRenderedImage(img, region,
2709                                         (int) xform.getTranslateX(),
2710                                         (int) xform.getTranslateY());
2711             return;
2712         }
2713 
2714         // General case: cobble the necessary region into a single Raster
2715         Raster raster = img.getData(region);
2716 
2717         // Make a new Raster with the same contents as raster
2718         // but starting at (0, 0).  This raster is thus in the same
2719         // coordinate system as the SampleModel of the original raster.
2720         WritableRaster wRaster =
2721               Raster.createWritableRaster(raster.getSampleModel(),
2722                                           raster.getDataBuffer(),
2723                                           null);
2724 
2725         // If the original raster was in a different coordinate
2726         // system than its SampleModel, we need to perform an
2727         // additional translation in order to get the (minX, minY)
2728         // pixel of raster to be pixel (0, 0) of wRaster.  We also
2729         // have to have the correct width and height.
2730         int minX = raster.getMinX();
2731         int minY = raster.getMinY();
2732         int width = raster.getWidth();
2733         int height = raster.getHeight();
2734         int px = minX - raster.getSampleModelTranslateX();
2735         int py = minY - raster.getSampleModelTranslateY();
2736         if (px != 0 || py != 0 || width != wRaster.getWidth() ||
2737             height != wRaster.getHeight()) {
2738             wRaster =
2739                 wRaster.createWritableChild(px,
2740                                             py,
2741                                             width,
2742                                             height,
2743                                             0, 0,
2744                                             null);
2745         }
2746 
2747         // Now we have a BufferedImage starting at (0, 0)
2748         // with the same contents that started at (minX, minY)
2749         // in raster.  So we must draw the BufferedImage with a
2750         // translation of (minX, minY).
2751         AffineTransform transXform = (AffineTransform)xform.clone();
2752         transXform.translate(minX, minY);
2753 
2754         ColorModel cm = img.getColorModel();
2755         BufferedImage bufImg = new BufferedImage(cm,
2756                                                  wRaster,
2757                                                  cm.isAlphaPremultiplied(),
2758                                                  null);
2759         drawImage(bufImg, transXform, null);
2760     }
2761 
2762     /**
2763      * Intersects <code>destRect</code> with <code>clip</code> and
2764      * overwrites <code>destRect</code> with the result.
2765      * Returns false if the intersection was empty, true otherwise.
2766      */
2767     private boolean clipTo(Rectangle destRect, Rectangle clip) {
2768         int x1 = Math.max(destRect.x, clip.x);
2769         int x2 = Math.min(destRect.x + destRect.width, clip.x + clip.width);
2770         int y1 = Math.max(destRect.y, clip.y);
2771         int y2 = Math.min(destRect.y + destRect.height, clip.y + clip.height);
2772         if (((x2 - x1) < 0) || ((y2 - y1) < 0)) {
2773             destRect.width = -1; // Set both just to be safe
2774             destRect.height = -1;
2775             return false;
2776         } else {
2777             destRect.x = x1;
2778             destRect.y = y1;
2779             destRect.width = x2 - x1;
2780             destRect.height = y2 - y1;
2781             return true;
2782         }
2783     }
2784 
2785     /**
2786      * Draw a portion of a RenderedImage tile-by-tile with a given
2787      * integer image to user space translation.  The user to
2788      * device transform must also be an integer translation.
2789      */
2790     private void drawTranslatedRenderedImage(RenderedImage img,
2791                                              Rectangle region,
2792                                              int i2uTransX,
2793                                              int i2uTransY) {
2794         // Cache tile grid info
2795         int tileGridXOffset = img.getTileGridXOffset();
2796         int tileGridYOffset = img.getTileGridYOffset();
2797         int tileWidth = img.getTileWidth();
2798         int tileHeight = img.getTileHeight();
2799 
2800         // Determine the tile index extrema in each direction
2801         int minTileX =
2802             getTileIndex(region.x, tileGridXOffset, tileWidth);
2803         int minTileY =
2804             getTileIndex(region.y, tileGridYOffset, tileHeight);
2805         int maxTileX =
2806             getTileIndex(region.x + region.width - 1,
2807                          tileGridXOffset, tileWidth);
2808         int maxTileY =
2809             getTileIndex(region.y + region.height - 1,
2810                          tileGridYOffset, tileHeight);
2811 
2812         // Create a single ColorModel to use for all BufferedImages
2813         ColorModel colorModel = img.getColorModel();
2814 
2815         // Reuse the same Rectangle for each iteration
2816         Rectangle tileRect = new Rectangle();
2817 
2818         for (int ty = minTileY; ty <= maxTileY; ty++) {
2819             for (int tx = minTileX; tx <= maxTileX; tx++) {
2820                 // Get the current tile.
2821                 Raster raster = img.getTile(tx, ty);
2822 
2823                 // Fill in tileRect with the tile bounds
2824                 tileRect.x = tx*tileWidth + tileGridXOffset;
2825                 tileRect.y = ty*tileHeight + tileGridYOffset;
2826                 tileRect.width = tileWidth;
2827                 tileRect.height = tileHeight;
2828 
2829                 // Clip the tile against the image bounds and
2830                 // backwards mapped clip region
2831                 // The result can't be empty
2832                 clipTo(tileRect, region);
2833 
2834                 // Create a WritableRaster containing the tile
2835                 WritableRaster wRaster = null;
2836                 if (raster instanceof WritableRaster) {
2837                     wRaster = (WritableRaster)raster;
2838                 } else {
2839                     // Create a WritableRaster in the same coordinate system
2840                     // as the original raster.
2841                     wRaster =
2842                         Raster.createWritableRaster(raster.getSampleModel(),
2843                                                     raster.getDataBuffer(),
2844                                                     null);
2845                 }
2846 
2847                 // Translate wRaster to start at (0, 0) and to contain
2848                 // only the relevent portion of the tile
2849                 wRaster = wRaster.createWritableChild(tileRect.x, tileRect.y,
2850                                                       tileRect.width,
2851                                                       tileRect.height,
2852                                                       0, 0,
2853                                                       null);
2854 
2855                 // Wrap wRaster in a BufferedImage
2856                 BufferedImage bufImg =
2857                     new BufferedImage(colorModel,
2858                                       wRaster,
2859                                       colorModel.isAlphaPremultiplied(),
2860                                       null);
2861                 // Now we have a BufferedImage starting at (0, 0) that
2862                 // represents data from a Raster starting at
2863                 // (tileRect.x, tileRect.y).  Additionally, it needs
2864                 // to be translated by (i2uTransX, i2uTransY).  We call
2865                 // copyImage to draw just the region of interest
2866                 // without needing to create a child image.
2867                 copyImage(bufImg, tileRect.x + i2uTransX,
2868                           tileRect.y + i2uTransY, 0, 0, tileRect.width,
2869                           tileRect.height, null, null);
2870             }
2871         }
2872     }
2873 
2874     public void drawRenderableImage(RenderableImage img,
2875                                     AffineTransform xform) {
2876 
2877         if (img == null) {
2878             return;
2879         }
2880 
2881         AffineTransform pipeTransform = transform;
2882         AffineTransform concatTransform = new AffineTransform(xform);
2883         concatTransform.concatenate(pipeTransform);
2884         AffineTransform reverseTransform;
2885 
2886         RenderContext rc = new RenderContext(concatTransform);
2887 
2888         try {
2889             reverseTransform = pipeTransform.createInverse();
2890         } catch (NoninvertibleTransformException nte) {
2891             rc = new RenderContext(pipeTransform);
2892             reverseTransform = new AffineTransform();
2893         }
2894 
2895         RenderedImage rendering = img.createRendering(rc);
2896         drawRenderedImage(rendering,reverseTransform);
2897     }
2898 
2899 
2900 
2901     /*
2902      * Transform the bounding box of the BufferedImage
2903      */
2904     protected Rectangle transformBounds(Rectangle rect,
2905                                         AffineTransform tx) {
2906         if (tx.isIdentity()) {
2907             return rect;
2908         }
2909 
2910         Shape s = transformShape(tx, rect);
2911         return s.getBounds();
2912     }
2913 
2914     // text rendering methods
2915     public void drawString(String str, int x, int 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(String str, float x, float y) {
2945         if (str == null) {
2946             throw new NullPointerException("String is null");
2947         }
2948 
2949         if (font.hasLayoutAttributes()) {
2950             if (str.length() == 0) {
2951                 return;
2952             }
2953             new TextLayout(str, font, getFontRenderContext()).draw(this, x, y);
2954             return;
2955         }
2956 
2957         try {
2958             textpipe.drawString(this, str, x, y);
2959         } catch (InvalidPipeException e) {
2960             try {
2961                 revalidateAll();
2962                 textpipe.drawString(this, str, x, y);
2963             } catch (InvalidPipeException e2) {
2964                 // Still catching the exception; we are not yet ready to
2965                 // validate the surfaceData correctly.  Fail for now and
2966                 // try again next time around.
2967             }
2968         } finally {
2969             surfaceData.markDirty();
2970         }
2971     }
2972 
2973     public void drawString(AttributedCharacterIterator iterator,
2974                            int x, int y) {
2975         if (iterator == null) {
2976             throw new NullPointerException("AttributedCharacterIterator is null");
2977         }
2978         if (iterator.getBeginIndex() == iterator.getEndIndex()) {
2979             return; /* nothing to draw */
2980         }
2981         TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2982         tl.draw(this, (float) x, (float) y);
2983     }
2984 
2985     public void drawString(AttributedCharacterIterator iterator,
2986                            float x, float y) {
2987         if (iterator == null) {
2988             throw new NullPointerException("AttributedCharacterIterator is null");
2989         }
2990         if (iterator.getBeginIndex() == iterator.getEndIndex()) {
2991             return; /* nothing to draw */
2992         }
2993         TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2994         tl.draw(this, x, y);
2995     }
2996 
2997     public void drawGlyphVector(GlyphVector gv, float x, float y)
2998     {
2999         if (gv == null) {
3000             throw new NullPointerException("GlyphVector is null");
3001         }
3002 
3003         try {
3004             textpipe.drawGlyphVector(this, gv, x, y);
3005         } catch (InvalidPipeException e) {
3006             try {
3007                 revalidateAll();
3008                 textpipe.drawGlyphVector(this, gv, x, y);
3009             } catch (InvalidPipeException e2) {
3010                 // Still catching the exception; we are not yet ready to
3011                 // validate the surfaceData correctly.  Fail for now and
3012                 // try again next time around.
3013             }
3014         } finally {
3015             surfaceData.markDirty();
3016         }
3017     }
3018 
3019     public void drawChars(char data[], int offset, int length, int x, int y) {
3020 
3021         if (data == null) {
3022             throw new NullPointerException("char data is null");
3023         }
3024         if (offset < 0 || length < 0 || offset + length > data.length) {
3025             throw new ArrayIndexOutOfBoundsException("bad offset/length");
3026         }
3027         if (font.hasLayoutAttributes()) {
3028             if (data.length == 0) {
3029                 return;
3030             }
3031             new TextLayout(new String(data, offset, length),
3032                            font, getFontRenderContext()).draw(this, x, y);
3033             return;
3034         }
3035 
3036         try {
3037             textpipe.drawChars(this, data, offset, length, x, y);
3038         } catch (InvalidPipeException e) {
3039             try {
3040                 revalidateAll();
3041                 textpipe.drawChars(this, data, offset, length, x, y);
3042             } catch (InvalidPipeException e2) {
3043                 // Still catching the exception; we are not yet ready to
3044                 // validate the surfaceData correctly.  Fail for now and
3045                 // try again next time around.
3046             }
3047         } finally {
3048             surfaceData.markDirty();
3049         }
3050     }
3051 
3052     public void drawBytes(byte data[], int offset, int length, int x, int y) {
3053         if (data == null) {
3054             throw new NullPointerException("byte data is null");
3055         }
3056         if (offset < 0 || length < 0 || offset + length > data.length) {
3057             throw new ArrayIndexOutOfBoundsException("bad offset/length");
3058         }
3059         /* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */
3060         char chData[] = new char[length];
3061         for (int i = length; i-- > 0; ) {
3062             chData[i] = (char)(data[i+offset] & 0xff);
3063         }
3064         if (font.hasLayoutAttributes()) {
3065             if (data.length == 0) {
3066                 return;
3067             }
3068             new TextLayout(new String(chData),
3069                            font, getFontRenderContext()).draw(this, x, y);
3070             return;
3071         }
3072 
3073         try {
3074             textpipe.drawChars(this, chData, 0, length, x, y);
3075         } catch (InvalidPipeException e) {
3076             try {
3077                 revalidateAll();
3078                 textpipe.drawChars(this, chData, 0, length, x, y);
3079             } catch (InvalidPipeException e2) {
3080                 // Still catching the exception; we are not yet ready to
3081                 // validate the surfaceData correctly.  Fail for now and
3082                 // try again next time around.
3083             }
3084         } finally {
3085             surfaceData.markDirty();
3086         }
3087     }
3088 // end of text rendering methods
3089 
3090     private boolean isHiDPIImage(final Image img) {
3091         return (SurfaceManager.getImageScale(img) != 1) ||
3092                (resolutionVariantHint != SunHints.INTVAL_RESOLUTION_VARIANT_OFF
3093                     && img instanceof MultiResolutionImage);
3094     }
3095 
3096     private boolean drawHiDPIImage(Image img, int dx1, int dy1, int dx2,
3097                                    int dy2, int sx1, int sy1, int sx2, int sy2,
3098                                    Color bgcolor, ImageObserver observer) {
3099 
3100         if (SurfaceManager.getImageScale(img) != 1) {  // Volatile Image
3101             final int scale = SurfaceManager.getImageScale(img);
3102             sx1 = Region.clipScale(sx1, scale);
3103             sx2 = Region.clipScale(sx2, scale);
3104             sy1 = Region.clipScale(sy1, scale);
3105             sy2 = Region.clipScale(sy2, scale);
3106         } else if (img instanceof MultiResolutionImage) {
3107             // get scaled destination image size
3108 
3109             int width = img.getWidth(observer);
3110             int height = img.getHeight(observer);
3111 
3112             Image resolutionVariant = getResolutionVariant(
3113                     (MultiResolutionImage) img, width, height,
3114                     dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2);
3115 
3116             if (resolutionVariant != img && resolutionVariant != null) {
3117                 // recalculate source region for the resolution variant
3118 
3119                 ImageObserver rvObserver = MultiResolutionToolkitImage.
3120                         getResolutionVariantObserver(img, observer,
3121                                 width, height, -1, -1);
3122 
3123                 int rvWidth = resolutionVariant.getWidth(rvObserver);
3124                 int rvHeight = resolutionVariant.getHeight(rvObserver);
3125 
3126                 if (0 < width && 0 < height && 0 < rvWidth && 0 < rvHeight) {
3127 
3128                     float widthScale = ((float) rvWidth) / width;
3129                     float heightScale = ((float) rvHeight) / height;
3130 
3131                     sx1 = Region.clipScale(sx1, widthScale);
3132                     sy1 = Region.clipScale(sy1, heightScale);
3133                     sx2 = Region.clipScale(sx2, widthScale);
3134                     sy2 = Region.clipScale(sy2, heightScale);
3135 
3136                     observer = rvObserver;
3137                     img = resolutionVariant;
3138                 }
3139             }
3140         }
3141 
3142         try {
3143             return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1,
3144                                         sx2, sy2, bgcolor, observer);
3145         } catch (InvalidPipeException e) {
3146             try {
3147                 revalidateAll();
3148                 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1,
3149                                             sy1, sx2, sy2, bgcolor, observer);
3150             } catch (InvalidPipeException e2) {
3151                 // Still catching the exception; we are not yet ready to
3152                 // validate the surfaceData correctly.  Fail for now and
3153                 // try again next time around.
3154                 return false;
3155             }
3156         } finally {
3157             surfaceData.markDirty();
3158         }
3159     }
3160 
3161     private Image getResolutionVariant(MultiResolutionImage img,
3162             int srcWidth, int srcHeight, int dx1, int dy1, int dx2, int dy2,
3163             int sx1, int sy1, int sx2, int sy2) {
3164 
3165         if (srcWidth <= 0 || srcHeight <= 0) {
3166             return null;
3167         }
3168 
3169         int sw = sx2 - sx1;
3170         int sh = sy2 - sy1;
3171 
3172         if (sw == 0 || sh == 0) {
3173             return null;
3174         }
3175 
3176         int type = transform.getType();
3177         int dw = dx2 - dx1;
3178         int dh = dy2 - dy1;
3179         double destRegionWidth;
3180         double destRegionHeight;
3181 
3182         if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP)) == 0) {
3183             destRegionWidth = dw;
3184             destRegionHeight = dh;
3185         } else if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP | TYPE_MASK_SCALE)) == 0) {
3186             destRegionWidth = dw * transform.getScaleX();
3187             destRegionHeight = dh * transform.getScaleY();
3188         } else {
3189             destRegionWidth = dw * Math.hypot(
3190                     transform.getScaleX(), transform.getShearY());
3191             destRegionHeight = dh * Math.hypot(
3192                     transform.getShearX(), transform.getScaleY());
3193         }
3194 
3195         int destImageWidth = (int) Math.abs(srcWidth * destRegionWidth / sw);
3196         int destImageHeight = (int) Math.abs(srcHeight * destRegionHeight / sh);
3197 
3198         Image resolutionVariant
3199                 = img.getResolutionVariant(destImageWidth, destImageHeight);
3200 
3201         if (resolutionVariant instanceof ToolkitImage
3202                 && ((ToolkitImage) resolutionVariant).hasError()) {
3203             return null;
3204         }
3205 
3206         return resolutionVariant;
3207     }
3208 
3209     /**
3210      * Draws an image scaled to x,y,w,h in nonblocking mode with a
3211      * callback object.
3212      */
3213     public boolean drawImage(Image img, int x, int y, int width, int height,
3214                              ImageObserver observer) {
3215         return drawImage(img, x, y, width, height, null, observer);
3216     }
3217 
3218     /**
3219      * Not part of the advertised API but a useful utility method
3220      * to call internally.  This is for the case where we are
3221      * drawing to/from given coordinates using a given width/height,
3222      * but we guarantee that the surfaceData's width/height of the src and dest
3223      * areas are equal (no scale needed). Note that this method intentionally
3224      * ignore scale factor of the source image, and copy it as is.
3225      */
3226     public boolean copyImage(Image img, int dx, int dy, int sx, int sy,
3227                              int width, int height, Color bgcolor,
3228                              ImageObserver observer) {
3229         try {
3230             return imagepipe.copyImage(this, img, dx, dy, sx, sy,
3231                                        width, height, bgcolor, observer);
3232         } catch (InvalidPipeException e) {
3233             try {
3234                 revalidateAll();
3235                 return imagepipe.copyImage(this, img, dx, dy, sx, sy,
3236                                            width, height, bgcolor, observer);
3237             } catch (InvalidPipeException e2) {
3238                 // Still catching the exception; we are not yet ready to
3239                 // validate the surfaceData correctly.  Fail for now and
3240                 // try again next time around.
3241                 return false;
3242             }
3243         } finally {
3244             surfaceData.markDirty();
3245         }
3246     }
3247 
3248     /**
3249      * Draws an image scaled to x,y,w,h in nonblocking mode with a
3250      * solid background color and a callback object.
3251      */
3252     public boolean drawImage(Image img, int x, int y, int width, int height,
3253                              Color bg, ImageObserver observer) {
3254 
3255         if (img == null) {
3256             return true;
3257         }
3258 
3259         if ((width == 0) || (height == 0)) {
3260             return true;
3261         }
3262 
3263         final int imgW = img.getWidth(null);
3264         final int imgH = img.getHeight(null);
3265         if (isHiDPIImage(img)) {
3266             return drawHiDPIImage(img, x, y, x + width, y + height, 0, 0, imgW,
3267                                   imgH, bg, observer);
3268         }
3269 
3270         if (width == imgW && height == imgH) {
3271             return copyImage(img, x, y, 0, 0, width, height, bg, observer);
3272         }
3273 
3274         try {
3275             return imagepipe.scaleImage(this, img, x, y, width, height,
3276                                         bg, observer);
3277         } catch (InvalidPipeException e) {
3278             try {
3279                 revalidateAll();
3280                 return imagepipe.scaleImage(this, img, x, y, width, height,
3281                                             bg, observer);
3282             } catch (InvalidPipeException e2) {
3283                 // Still catching the exception; we are not yet ready to
3284                 // validate the surfaceData correctly.  Fail for now and
3285                 // try again next time around.
3286                 return false;
3287             }
3288         } finally {
3289             surfaceData.markDirty();
3290         }
3291     }
3292 
3293     /**
3294      * Draws an image at x,y in nonblocking mode.
3295      */
3296     public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
3297         return drawImage(img, x, y, null, observer);
3298     }
3299 
3300     /**
3301      * Draws an image at x,y in nonblocking mode with a solid background
3302      * color and a callback object.
3303      */
3304     public boolean drawImage(Image img, int x, int y, Color bg,
3305                              ImageObserver observer) {
3306 
3307         if (img == null) {
3308             return true;
3309         }
3310 
3311         if (isHiDPIImage(img)) {
3312             final int imgW = img.getWidth(null);
3313             final int imgH = img.getHeight(null);
3314             return drawHiDPIImage(img, x, y, x + imgW, y + imgH, 0, 0, imgW,
3315                                   imgH, bg, observer);
3316         }
3317 
3318         try {
3319             return imagepipe.copyImage(this, img, x, y, bg, observer);
3320         } catch (InvalidPipeException e) {
3321             try {
3322                 revalidateAll();
3323                 return imagepipe.copyImage(this, img, x, y, bg, observer);
3324             } catch (InvalidPipeException e2) {
3325                 // Still catching the exception; we are not yet ready to
3326                 // validate the surfaceData correctly.  Fail for now and
3327                 // try again next time around.
3328                 return false;
3329             }
3330         } finally {
3331             surfaceData.markDirty();
3332         }
3333     }
3334 
3335     /**
3336      * Draws a subrectangle of an image scaled to a destination rectangle
3337      * in nonblocking mode with a callback object.
3338      */
3339     public boolean drawImage(Image img,
3340                              int dx1, int dy1, int dx2, int dy2,
3341                              int sx1, int sy1, int sx2, int sy2,
3342                              ImageObserver observer) {
3343         return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null,
3344                          observer);
3345     }
3346 
3347     /**
3348      * Draws a subrectangle of an image scaled to a destination rectangle in
3349      * nonblocking mode with a solid background color and a callback object.
3350      */
3351     public boolean drawImage(Image img,
3352                              int dx1, int dy1, int dx2, int dy2,
3353                              int sx1, int sy1, int sx2, int sy2,
3354                              Color bgcolor, ImageObserver observer) {
3355 
3356         if (img == null) {
3357             return true;
3358         }
3359 
3360         if (dx1 == dx2 || dy1 == dy2 ||
3361             sx1 == sx2 || sy1 == sy2)
3362         {
3363             return true;
3364         }
3365 
3366         if (isHiDPIImage(img)) {
3367             return drawHiDPIImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2,
3368                                   bgcolor, observer);
3369         }
3370 
3371         if (((sx2 - sx1) == (dx2 - dx1)) &&
3372             ((sy2 - sy1) == (dy2 - dy1)))
3373         {
3374             // Not a scale - forward it to a copy routine
3375             int srcX, srcY, dstX, dstY, width, height;
3376             if (sx2 > sx1) {
3377                 width = sx2 - sx1;
3378                 srcX = sx1;
3379                 dstX = dx1;
3380             } else {
3381                 width = sx1 - sx2;
3382                 srcX = sx2;
3383                 dstX = dx2;
3384             }
3385             if (sy2 > sy1) {
3386                 height = sy2-sy1;
3387                 srcY = sy1;
3388                 dstY = dy1;
3389             } else {
3390                 height = sy1-sy2;
3391                 srcY = sy2;
3392                 dstY = dy2;
3393             }
3394             return copyImage(img, dstX, dstY, srcX, srcY,
3395                              width, height, bgcolor, observer);
3396         }
3397 
3398         try {
3399             return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,
3400                                           sx1, sy1, sx2, sy2, bgcolor,
3401                                           observer);
3402         } catch (InvalidPipeException e) {
3403             try {
3404                 revalidateAll();
3405                 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,
3406                                               sx1, sy1, sx2, sy2, bgcolor,
3407                                               observer);
3408             } catch (InvalidPipeException e2) {
3409                 // Still catching the exception; we are not yet ready to
3410                 // validate the surfaceData correctly.  Fail for now and
3411                 // try again next time around.
3412                 return false;
3413             }
3414         } finally {
3415             surfaceData.markDirty();
3416         }
3417     }
3418 
3419     /**
3420      * Draw an image, applying a transform from image space into user space
3421      * before drawing.
3422      * The transformation from user space into device space is done with
3423      * the current transform in the Graphics2D.
3424      * The given transformation is applied to the image before the
3425      * transform attribute in the Graphics2D state is applied.
3426      * The rendering attributes applied include the clip, transform,
3427      * paint or color and composite attributes. Note that the result is
3428      * undefined, if the given transform is non-invertible.
3429      * @param img The image to be drawn.
3430      * @param xform The transformation from image space into user space.
3431      * @param observer The image observer to be notified on the image producing
3432      * progress.
3433      * @see #transform
3434      * @see #setComposite
3435      * @see #setClip
3436      */
3437     public boolean drawImage(Image img,
3438                              AffineTransform xform,
3439                              ImageObserver observer) {
3440 
3441         if (img == null) {
3442             return true;
3443         }
3444 
3445         if (xform == null || xform.isIdentity()) {
3446             return drawImage(img, 0, 0, null, observer);
3447         }
3448 
3449         if (isHiDPIImage(img)) {
3450             final int w = img.getWidth(null);
3451             final int h = img.getHeight(null);
3452             final AffineTransform tx = new AffineTransform(transform);
3453             transform(xform);
3454             boolean result = drawHiDPIImage(img, 0, 0, w, h, 0, 0, w, h, null,
3455                                             observer);
3456             transform.setTransform(tx);
3457             invalidateTransform();
3458             return result;
3459         }
3460 
3461         try {
3462             return imagepipe.transformImage(this, img, xform, observer);
3463         } catch (InvalidPipeException e) {
3464             try {
3465                 revalidateAll();
3466                 return imagepipe.transformImage(this, img, xform, observer);
3467             } catch (InvalidPipeException e2) {
3468                 // Still catching the exception; we are not yet ready to
3469                 // validate the surfaceData correctly.  Fail for now and
3470                 // try again next time around.
3471                 return false;
3472             }
3473         } finally {
3474             surfaceData.markDirty();
3475         }
3476     }
3477 
3478     public void drawImage(BufferedImage bImg,
3479                           BufferedImageOp op,
3480                           int x,
3481                           int y)  {
3482 
3483         if (bImg == null) {
3484             return;
3485         }
3486 
3487         try {
3488             imagepipe.transformImage(this, bImg, op, x, y);
3489         } catch (InvalidPipeException e) {
3490             try {
3491                 revalidateAll();
3492                 imagepipe.transformImage(this, bImg, op, x, y);
3493             } catch (InvalidPipeException e2) {
3494                 // Still catching the exception; we are not yet ready to
3495                 // validate the surfaceData correctly.  Fail for now and
3496                 // try again next time around.
3497             }
3498         } finally {
3499             surfaceData.markDirty();
3500         }
3501     }
3502 
3503     /**
3504     * Get the rendering context of the font
3505     * within this Graphics2D context.
3506     */
3507     public FontRenderContext getFontRenderContext() {
3508         if (cachedFRC == null) {
3509             int aahint = textAntialiasHint;
3510             if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT &&
3511                 antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
3512                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
3513             }
3514             // Translation components should be excluded from the FRC transform
3515             AffineTransform tx = null;
3516             if (transformState >= TRANSFORM_TRANSLATESCALE) {
3517                 if (transform.getTranslateX() == 0 &&
3518                     transform.getTranslateY() == 0) {
3519                     tx = transform;
3520                 } else {
3521                     tx = new AffineTransform(transform.getScaleX(),
3522                                              transform.getShearY(),
3523                                              transform.getShearX(),
3524                                              transform.getScaleY(),
3525                                              0, 0);
3526                 }
3527             }
3528             cachedFRC = new FontRenderContext(tx,
3529              SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, aahint),
3530              SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
3531                                 fractionalMetricsHint));
3532         }
3533         return cachedFRC;
3534     }
3535     private FontRenderContext cachedFRC;
3536 
3537     /**
3538      * This object has no resources to dispose of per se, but the
3539      * doc comments for the base method in java.awt.Graphics imply
3540      * that this object will not be useable after it is disposed.
3541      * So, we sabotage the object to prevent further use to prevent
3542      * developers from relying on behavior that may not work on
3543      * other, less forgiving, VMs that really need to dispose of
3544      * resources.
3545      */
3546     public void dispose() {
3547         surfaceData = NullSurfaceData.theInstance;
3548         invalidatePipe();
3549     }
3550 
3551     /**
3552      * Graphics has a finalize method that automatically calls dispose()
3553      * for subclasses.  For SunGraphics2D we do not need to be finalized
3554      * so that method simply causes us to be enqueued on the Finalizer
3555      * queues for no good reason.  Unfortunately, that method and
3556      * implementation are now considered part of the public contract
3557      * of that base class so we can not remove or gut the method.
3558      * We override it here with an empty method and the VM is smart
3559      * enough to know that if our override is empty then it should not
3560      * mark us as finalizeable.
3561      */
3562     public void finalize() {
3563         // DO NOT REMOVE THIS METHOD
3564     }
3565 
3566     /**
3567      * Returns destination that this Graphics renders to.  This could be
3568      * either an Image or a Component; subclasses of SurfaceData are
3569      * responsible for returning the appropriate object.
3570      */
3571     public Object getDestination() {
3572         return surfaceData.getDestination();
3573     }
3574 
3575     /**
3576      * {@inheritDoc}
3577      *
3578      * @see sun.java2d.DestSurfaceProvider#getDestSurface
3579      */
3580     @Override
3581     public Surface getDestSurface() {
3582         return surfaceData;
3583     }
3584 }
--- EOF ---