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