1 /*
   2  * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.java2d;
  27 
  28 import java.awt.Graphics;
  29 import java.awt.Graphics2D;
  30 import java.awt.RenderingHints;
  31 import java.awt.RenderingHints.Key;
  32 import java.awt.geom.Area;
  33 import java.awt.geom.AffineTransform;
  34 import java.awt.geom.NoninvertibleTransformException;
  35 import java.awt.AlphaComposite;
  36 import java.awt.BasicStroke;
  37 import java.awt.image.BufferedImage;
  38 import java.awt.image.BufferedImageOp;
  39 import java.awt.image.RenderedImage;
  40 import java.awt.image.renderable.RenderableImage;
  41 import java.awt.image.renderable.RenderContext;
  42 import java.awt.image.AffineTransformOp;
  43 import java.awt.image.Raster;
  44 import java.awt.image.WritableRaster;
  45 import java.awt.Image;
  46 import java.awt.Composite;
  47 import java.awt.Color;
  48 import java.awt.image.ColorModel;
  49 import java.awt.GraphicsConfiguration;
  50 import java.awt.Paint;
  51 import java.awt.GradientPaint;
  52 import java.awt.LinearGradientPaint;
  53 import java.awt.RadialGradientPaint;
  54 import java.awt.TexturePaint;
  55 import java.awt.geom.Rectangle2D;
  56 import java.awt.geom.PathIterator;
  57 import java.awt.geom.GeneralPath;
  58 import java.awt.Shape;
  59 import java.awt.Stroke;
  60 import java.awt.FontMetrics;
  61 import java.awt.Rectangle;
  62 import java.text.AttributedCharacterIterator;
  63 import java.awt.Font;
  64 import java.awt.image.ImageObserver;
  65 import java.awt.Transparency;
  66 import java.awt.font.GlyphVector;
  67 import java.awt.font.TextLayout;
  68 
  69 import sun.awt.image.SurfaceManager;
  70 import sun.font.FontDesignMetrics;
  71 import sun.font.FontUtilities;
  72 import sun.java2d.pipe.PixelDrawPipe;
  73 import sun.java2d.pipe.PixelFillPipe;
  74 import sun.java2d.pipe.ShapeDrawPipe;
  75 import sun.java2d.pipe.ValidatePipe;
  76 import sun.java2d.pipe.ShapeSpanIterator;
  77 import sun.java2d.pipe.Region;
  78 import sun.java2d.pipe.TextPipe;
  79 import sun.java2d.pipe.DrawImagePipe;
  80 import sun.java2d.pipe.LoopPipe;
  81 import sun.java2d.loops.FontInfo;
  82 import sun.java2d.loops.RenderLoops;
  83 import sun.java2d.loops.CompositeType;
  84 import sun.java2d.loops.SurfaceType;
  85 import sun.java2d.loops.Blit;
  86 import sun.java2d.loops.MaskFill;
  87 import java.awt.font.FontRenderContext;
  88 import sun.java2d.loops.XORComposite;
  89 import sun.awt.ConstrainableGraphics;
  90 import sun.awt.SunHints;
  91 import sun.awt.util.PerformanceLogger;
  92 import java.util.Map;
  93 import java.util.Iterator;
  94 
  95 import java.lang.annotation.Native;
  96 import java.awt.image.MultiResolutionImage;
  97 
  98 import static java.awt.geom.AffineTransform.TYPE_FLIP;
  99 import static java.awt.geom.AffineTransform.TYPE_MASK_SCALE;
 100 import static java.awt.geom.AffineTransform.TYPE_TRANSLATION;
 101 import java.awt.image.VolatileImage;
 102 import sun.awt.image.MultiResolutionToolkitImage;
 103 import sun.awt.image.ToolkitImage;
 104 
 105 /**
 106  * This is a the master Graphics2D superclass for all of the Sun
 107  * Graphics implementations.  This class relies on subclasses to
 108  * manage the various device information, but provides an overall
 109  * general framework for performing all of the requests in the
 110  * Graphics and Graphics2D APIs.
 111  *
 112  * @author Jim Graham
 113  */
 114 public final class SunGraphics2D
 115     extends Graphics2D
 116     implements ConstrainableGraphics, Cloneable, DestSurfaceProvider
 117 {
 118     /*
 119      * Attribute States
 120      */
 121     /* Paint */
 122     @Native
 123     public static final int PAINT_CUSTOM       = 6; /* Any other Paint object */
 124     @Native
 125     public static final int PAINT_TEXTURE      = 5; /* Tiled Image */
 126     @Native
 127     public static final int PAINT_RAD_GRADIENT = 4; /* Color RadialGradient */
 128     @Native
 129     public static final int PAINT_LIN_GRADIENT = 3; /* Color LinearGradient */
 130     @Native
 131     public static final int PAINT_GRADIENT     = 2; /* Color Gradient */
 132     @Native
 133     public static final int PAINT_ALPHACOLOR   = 1; /* Non-opaque Color */
 134     @Native
 135     public static final int PAINT_OPAQUECOLOR  = 0; /* Opaque Color */
 136 
 137     /* Composite*/
 138     @Native
 139     public static final int COMP_CUSTOM = 3;/* Custom Composite */
 140     @Native
 141     public static final int COMP_XOR    = 2;/* XOR Mode Composite */
 142     @Native
 143     public static final int COMP_ALPHA  = 1;/* AlphaComposite */
 144     @Native
 145     public static final int COMP_ISCOPY = 0;/* simple stores into destination,
 146                                              * i.e. Src, SrcOverNoEa, and other
 147                                              * alpha modes which replace
 148                                              * the destination.
 149                                              */
 150 
 151     /* Stroke */
 152     @Native
 153     public static final int STROKE_CUSTOM = 3; /* custom Stroke */
 154     @Native
 155     public static final int STROKE_WIDE   = 2; /* BasicStroke */
 156     @Native
 157     public static final int STROKE_THINDASHED   = 1; /* BasicStroke */
 158     @Native
 159     public static final int STROKE_THIN   = 0; /* BasicStroke */
 160 
 161     /* Transform */
 162     @Native
 163     public static final int TRANSFORM_GENERIC = 4; /* any 3x2 */
 164     @Native
 165     public static final int TRANSFORM_TRANSLATESCALE = 3; /* scale XY */
 166     @Native
 167     public static final int TRANSFORM_ANY_TRANSLATE = 2; /* non-int translate */
 168     @Native
 169     public static final int TRANSFORM_INT_TRANSLATE = 1; /* int translate */
 170     @Native
 171     public static final int TRANSFORM_ISIDENT = 0; /* Identity */
 172 
 173     /* Clipping */
 174     @Native
 175     public static final int CLIP_SHAPE       = 2; /* arbitrary clip */
 176     @Native
 177     public static final int CLIP_RECTANGULAR = 1; /* rectangular clip */
 178     @Native
 179     public static final int CLIP_DEVICE      = 0; /* no clipping set */
 180 
 181     /* The following fields are used when the current Paint is a Color. */
 182     public int eargb;  // ARGB value with ExtraAlpha baked in
 183     public int pixel;  // pixel value for eargb
 184 
 185     public SurfaceData surfaceData;
 186 
 187     public PixelDrawPipe drawpipe;
 188     public PixelFillPipe fillpipe;
 189     public DrawImagePipe imagepipe;
 190     public ShapeDrawPipe shapepipe;
 191     public TextPipe textpipe;
 192     public MaskFill alphafill;
 193 
 194     public RenderLoops loops;
 195 
 196     public CompositeType imageComp;     /* Image Transparency checked on fly */
 197 
 198     public int paintState;
 199     public int compositeState;
 200     public int strokeState;
 201     public int transformState;
 202     public int clipState;
 203 
 204     public Color foregroundColor;
 205     public Color backgroundColor;
 206 
 207     public AffineTransform transform;
 208     public int transX;
 209     public int transY;
 210 
 211     protected static final Stroke defaultStroke = new BasicStroke();
 212     protected static final Composite defaultComposite = AlphaComposite.SrcOver;
 213     private static final Font defaultFont =
 214         new Font(Font.DIALOG, Font.PLAIN, 12);
 215 
 216     public Paint paint;
 217     public Stroke stroke;
 218     public Composite composite;
 219     protected Font font;
 220     protected FontMetrics fontMetrics;
 221 
 222     public int renderHint;
 223     public int antialiasHint;
 224     public int textAntialiasHint;
 225     protected int fractionalMetricsHint;
 226 
 227     /* A gamma adjustment to the colour used in lcd text blitting */
 228     public int lcdTextContrast;
 229     private static int lcdTextContrastDefaultValue = 140;
 230 
 231     private int interpolationHint;      // raw value of rendering Hint
 232     public int strokeHint;
 233 
 234     public int interpolationType;       // algorithm choice based on
 235                                         // interpolation and render Hints
 236 
 237     public RenderingHints hints;
 238 
 239     public Region constrainClip;        // lightweight bounds in pixels
 240     public int constrainX;
 241     public int constrainY;
 242 
 243     public Region clipRegion;
 244     public Shape usrClip;
 245     protected Region devClip;           // Actual physical drawable in pixels
 246 
 247     private int resolutionVariantHint;
 248 
 249     // cached state for text rendering
 250     private boolean validFontInfo;
 251     private FontInfo fontInfo;
 252     private FontInfo glyphVectorFontInfo;
 253     private FontRenderContext glyphVectorFRC;
 254 
 255     private static final int slowTextTransformMask =
 256                             AffineTransform.TYPE_GENERAL_TRANSFORM
 257                         |   AffineTransform.TYPE_MASK_ROTATION
 258                         |   AffineTransform.TYPE_FLIP;
 259 
 260     static {
 261         if (PerformanceLogger.loggingEnabled()) {
 262             PerformanceLogger.setTime("SunGraphics2D static initialization");
 263         }
 264     }
 265 
 266     public SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f) {
 267         surfaceData = sd;
 268         foregroundColor = fg;
 269         backgroundColor = bg;
 270         stroke = defaultStroke;
 271         composite = defaultComposite;
 272         paint = foregroundColor;
 273 
 274         imageComp = CompositeType.SrcOverNoEa;
 275 
 276         renderHint = SunHints.INTVAL_RENDER_DEFAULT;
 277         antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
 278         textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
 279         fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
 280         lcdTextContrast = lcdTextContrastDefaultValue;
 281         interpolationHint = -1;
 282         strokeHint = SunHints.INTVAL_STROKE_DEFAULT;
 283         resolutionVariantHint = SunHints.INTVAL_RESOLUTION_VARIANT_DEFAULT;
 284 
 285         interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
 286 
 287         transform = getDefaultTransform();
 288         if (!transform.isIdentity()) {
 289             invalidateTransform();
 290         }
 291 
 292         validateColor();
 293 
 294         font = f;
 295         if (font == null) {
 296             font = defaultFont;
 297         }
 298 
 299         setDevClip(sd.getBounds());
 300         invalidatePipe();
 301     }
 302 
 303     private AffineTransform getDefaultTransform() {
 304         GraphicsConfiguration gc = getDeviceConfiguration();
 305         return (gc == null) ? new AffineTransform() : gc.getDefaultTransform();
 306     }
 307 
 308     protected Object clone() {
 309         try {
 310             SunGraphics2D g = (SunGraphics2D) super.clone();
 311             g.transform = new AffineTransform(this.transform);
 312             if (hints != null) {
 313                 g.hints = (RenderingHints) this.hints.clone();
 314             }
 315             /* FontInfos are re-used, so must be cloned too, if they
 316              * are valid, and be nulled out if invalid.
 317              * The implied trade-off is that there is more to be gained
 318              * from re-using these objects than is lost by having to
 319              * clone them when the SG2D is cloned.
 320              */
 321             if (this.fontInfo != null) {
 322                 if (this.validFontInfo) {
 323                     g.fontInfo = (FontInfo)this.fontInfo.clone();
 324                 } else {
 325                     g.fontInfo = null;
 326                 }
 327             }
 328             if (this.glyphVectorFontInfo != null) {
 329                 g.glyphVectorFontInfo =
 330                     (FontInfo)this.glyphVectorFontInfo.clone();
 331                 g.glyphVectorFRC = this.glyphVectorFRC;
 332             }
 333             //g.invalidatePipe();
 334             return g;
 335         } catch (CloneNotSupportedException e) {
 336         }
 337         return null;
 338     }
 339 
 340     /**
 341      * Create a new SunGraphics2D based on this one.
 342      */
 343     public Graphics create() {
 344         return (Graphics) clone();
 345     }
 346 
 347     public void setDevClip(int x, int y, int w, int h) {
 348         Region c = constrainClip;
 349         if (c == null) {
 350             devClip = Region.getInstanceXYWH(x, y, w, h);
 351         } else {
 352             devClip = c.getIntersectionXYWH(x, y, w, h);
 353         }
 354         validateCompClip();
 355     }
 356 
 357     public void setDevClip(Rectangle r) {
 358         setDevClip(r.x, r.y, r.width, r.height);
 359     }
 360 
 361     /**
 362      * Constrain rendering for lightweight objects.
 363      */
 364     public void constrain(int x, int y, int w, int h, Region region) {
 365         if ((x | y) != 0) {
 366             translate(x, y);
 367         }
 368         if (transformState > TRANSFORM_TRANSLATESCALE) {
 369             clipRect(0, 0, w, h);
 370             return;
 371         }
 372         // changes parameters according to the current scale and translate.
 373         final double scaleX = transform.getScaleX();
 374         final double scaleY = transform.getScaleY();
 375         x = constrainX = (int) transform.getTranslateX();
 376         y = constrainY = (int) transform.getTranslateY();
 377         w = Region.dimAdd(x, Region.clipScale(w, scaleX));
 378         h = Region.dimAdd(y, Region.clipScale(h, scaleY));
 379 
 380         Region c = constrainClip;
 381         if (c == null) {
 382             c = Region.getInstanceXYXY(x, y, w, h);
 383         } else {
 384             c = c.getIntersectionXYXY(x, y, w, h);
 385         }
 386         if (region != null) {
 387             region = region.getScaledRegion(scaleX, scaleY);
 388             region = region.getTranslatedRegion(x, y);
 389             c = c.getIntersection(region);
 390         }
 391 
 392         if (c == constrainClip) {
 393             // Common case to ignore
 394             return;
 395         }
 396 
 397         constrainClip = c;
 398         if (!devClip.isInsideQuickCheck(c)) {
 399             devClip = devClip.getIntersection(c);
 400             validateCompClip();
 401         }
 402     }
 403 
 404     /**
 405      * Constrain rendering for lightweight objects.
 406      *
 407      * REMIND: This method will back off to the "workaround"
 408      * of using translate and clipRect if the Graphics
 409      * to be constrained has a complex transform.  The
 410      * drawback of the workaround is that the resulting
 411      * clip and device origin cannot be "enforced".
 412      *
 413      * @exception IllegalStateException If the Graphics
 414      * to be constrained has a complex transform.
 415      */
 416     @Override
 417     public void constrain(int x, int y, int w, int h) {
 418         constrain(x, y, w, h, null);
 419     }
 420 
 421     protected static ValidatePipe invalidpipe = new ValidatePipe();
 422 
 423     /*
 424      * Invalidate the pipeline
 425      */
 426     protected void invalidatePipe() {
 427         drawpipe = invalidpipe;
 428         fillpipe = invalidpipe;
 429         shapepipe = invalidpipe;
 430         textpipe = invalidpipe;
 431         imagepipe = invalidpipe;
 432         loops = null;
 433     }
 434 
 435     public void validatePipe() {
 436         /* This workaround is for the situation when we update the Pipelines
 437          * for invalid SurfaceData and run further code when the current
 438          * pipeline doesn't support the type of new SurfaceData created during
 439          * the current pipeline's work (in place of the invalid SurfaceData).
 440          * Usually SurfaceData and Pipelines are repaired (through revalidateAll)
 441          * and called again in the exception handlers */
 442 
 443         if (!surfaceData.isValid()) {
 444             throw new InvalidPipeException("attempt to validate Pipe with invalid SurfaceData");
 445         }
 446 
 447         surfaceData.validatePipe(this);
 448     }
 449 
 450     /*
 451      * Intersect two Shapes by the simplest method, attempting to produce
 452      * a simplified result.
 453      * The boolean arguments keep1 and keep2 specify whether or not
 454      * the first or second shapes can be modified during the operation
 455      * or whether that shape must be "kept" unmodified.
 456      */
 457     Shape intersectShapes(Shape s1, Shape s2, boolean keep1, boolean keep2) {
 458         if (s1 instanceof Rectangle && s2 instanceof Rectangle) {
 459             return ((Rectangle) s1).intersection((Rectangle) s2);
 460         }
 461         if (s1 instanceof Rectangle2D) {
 462             return intersectRectShape((Rectangle2D) s1, s2, keep1, keep2);
 463         } else if (s2 instanceof Rectangle2D) {
 464             return intersectRectShape((Rectangle2D) s2, s1, keep2, keep1);
 465         }
 466         return intersectByArea(s1, s2, keep1, keep2);
 467     }
 468 
 469     /*
 470      * Intersect a Rectangle with a Shape by the simplest method,
 471      * attempting to produce a simplified result.
 472      * The boolean arguments keep1 and keep2 specify whether or not
 473      * the first or second shapes can be modified during the operation
 474      * or whether that shape must be "kept" unmodified.
 475      */
 476     Shape intersectRectShape(Rectangle2D r, Shape s,
 477                              boolean keep1, boolean keep2) {
 478         if (s instanceof Rectangle2D) {
 479             Rectangle2D r2 = (Rectangle2D) s;
 480             Rectangle2D outrect;
 481             if (!keep1) {
 482                 outrect = r;
 483             } else if (!keep2) {
 484                 outrect = r2;
 485             } else {
 486                 outrect = new Rectangle2D.Float();
 487             }
 488             double x1 = Math.max(r.getX(), r2.getX());
 489             double x2 = Math.min(r.getX()  + r.getWidth(),
 490                                  r2.getX() + r2.getWidth());
 491             double y1 = Math.max(r.getY(), r2.getY());
 492             double y2 = Math.min(r.getY()  + r.getHeight(),
 493                                  r2.getY() + r2.getHeight());
 494 
 495             if (((x2 - x1) < 0) || ((y2 - y1) < 0))
 496                 // Width or height is negative. No intersection.
 497                 outrect.setFrameFromDiagonal(0, 0, 0, 0);
 498             else
 499                 outrect.setFrameFromDiagonal(x1, y1, x2, y2);
 500             return outrect;
 501         }
 502         if (r.contains(s.getBounds2D())) {
 503             if (keep2) {
 504                 s = cloneShape(s);
 505             }
 506             return s;
 507         }
 508         return intersectByArea(r, s, keep1, keep2);
 509     }
 510 
 511     protected static Shape cloneShape(Shape s) {
 512         return new GeneralPath(s);
 513     }
 514 
 515     /*
 516      * Intersect two Shapes using the Area class.  Presumably other
 517      * attempts at simpler intersection methods proved fruitless.
 518      * The boolean arguments keep1 and keep2 specify whether or not
 519      * the first or second shapes can be modified during the operation
 520      * or whether that shape must be "kept" unmodified.
 521      * @see #intersectShapes
 522      * @see #intersectRectShape
 523      */
 524     Shape intersectByArea(Shape s1, Shape s2, boolean keep1, boolean keep2) {
 525         Area a1, a2;
 526 
 527         // First see if we can find an overwriteable source shape
 528         // to use as our destination area to avoid duplication.
 529         if (!keep1 && (s1 instanceof Area)) {
 530             a1 = (Area) s1;
 531         } else if (!keep2 && (s2 instanceof Area)) {
 532             a1 = (Area) s2;
 533             s2 = s1;
 534         } else {
 535             a1 = new Area(s1);
 536         }
 537 
 538         if (s2 instanceof Area) {
 539             a2 = (Area) s2;
 540         } else {
 541             a2 = new Area(s2);
 542         }
 543 
 544         a1.intersect(a2);
 545         if (a1.isRectangular()) {
 546             return a1.getBounds();
 547         }
 548 
 549         return a1;
 550     }
 551 
 552     /*
 553      * Intersect usrClip bounds and device bounds to determine the composite
 554      * rendering boundaries.
 555      */
 556     public Region getCompClip() {
 557         if (!surfaceData.isValid()) {
 558             // revalidateAll() implicitly recalculcates the composite clip
 559             revalidateAll();
 560         }
 561 
 562         return clipRegion;
 563     }
 564 
 565     public Font getFont() {
 566         if (font == null) {
 567             font = defaultFont;
 568         }
 569         return font;
 570     }
 571 
 572     private static final double[] IDENT_MATRIX = {1, 0, 0, 1};
 573     private static final AffineTransform IDENT_ATX =
 574         new AffineTransform();
 575 
 576     private static final int MINALLOCATED = 8;
 577     private static final int TEXTARRSIZE = 17;
 578     private static double[][] textTxArr = new double[TEXTARRSIZE][];
 579     private static AffineTransform[] textAtArr =
 580         new AffineTransform[TEXTARRSIZE];
 581 
 582     static {
 583         for (int i=MINALLOCATED;i<TEXTARRSIZE;i++) {
 584           textTxArr[i] = new double [] {i, 0, 0, i};
 585           textAtArr[i] = new AffineTransform( textTxArr[i]);
 586         }
 587     }
 588 
 589     // cached state for various draw[String,Char,Byte] optimizations
 590     public FontInfo checkFontInfo(FontInfo info, Font font,
 591                                   FontRenderContext frc) {
 592         /* Do not create a FontInfo object as part of construction of an
 593          * SG2D as its possible it may never be needed - ie if no text
 594          * is drawn using this SG2D.
 595          */
 596         if (info == null) {
 597             info = new FontInfo();
 598         }
 599 
 600         float ptSize = font.getSize2D();
 601         int txFontType;
 602         AffineTransform devAt, textAt=null;
 603         if (font.isTransformed()) {
 604             textAt = font.getTransform();
 605             textAt.scale(ptSize, ptSize);
 606             txFontType = textAt.getType();
 607             info.originX = (float)textAt.getTranslateX();
 608             info.originY = (float)textAt.getTranslateY();
 609             textAt.translate(-info.originX, -info.originY);
 610             if (transformState >= TRANSFORM_TRANSLATESCALE) {
 611                 transform.getMatrix(info.devTx = new double[4]);
 612                 devAt = new AffineTransform(info.devTx);
 613                 textAt.preConcatenate(devAt);
 614             } else {
 615                 info.devTx = IDENT_MATRIX;
 616                 devAt = IDENT_ATX;
 617             }
 618             textAt.getMatrix(info.glyphTx = new double[4]);
 619             double shearx = textAt.getShearX();
 620             double scaley = textAt.getScaleY();
 621             if (shearx != 0) {
 622                 scaley = Math.sqrt(shearx * shearx + scaley * scaley);
 623             }
 624             info.pixelHeight = (int)(Math.abs(scaley)+0.5);
 625         } else {
 626             txFontType = AffineTransform.TYPE_IDENTITY;
 627             info.originX = info.originY = 0;
 628             if (transformState >= TRANSFORM_TRANSLATESCALE) {
 629                 transform.getMatrix(info.devTx = new double[4]);
 630                 devAt = new AffineTransform(info.devTx);
 631                 info.glyphTx = new double[4];
 632                 for (int i = 0; i < 4; i++) {
 633                     info.glyphTx[i] = info.devTx[i] * ptSize;
 634                 }
 635                 textAt = new AffineTransform(info.glyphTx);
 636                 double shearx = transform.getShearX();
 637                 double scaley = transform.getScaleY();
 638                 if (shearx != 0) {
 639                     scaley = Math.sqrt(shearx * shearx + scaley * scaley);
 640                 }
 641                 info.pixelHeight = (int)(Math.abs(scaley * ptSize)+0.5);
 642             } else {
 643                 /* If the double represents a common integral, we
 644                  * may have pre-allocated objects.
 645                  * A "sparse" array be seems to be as fast as a switch
 646                  * even for 3 or 4 pt sizes, and is more flexible.
 647                  * This should perform comparably in single-threaded
 648                  * rendering to the old code which synchronized on the
 649                  * class and scale better on MP systems.
 650                  */
 651                 int pszInt = (int)ptSize;
 652                 if (ptSize == pszInt &&
 653                     pszInt >= MINALLOCATED && pszInt < TEXTARRSIZE) {
 654                     info.glyphTx = textTxArr[pszInt];
 655                     textAt = textAtArr[pszInt];
 656                     info.pixelHeight = pszInt;
 657                 } else {
 658                     info.pixelHeight = (int)(ptSize+0.5);
 659                 }
 660                 if (textAt == null) {
 661                     info.glyphTx = new double[] {ptSize, 0, 0, ptSize};
 662                     textAt = new AffineTransform(info.glyphTx);
 663                 }
 664 
 665                 info.devTx = IDENT_MATRIX;
 666                 devAt = IDENT_ATX;
 667             }
 668         }
 669 
 670         info.nonInvertibleTx =
 671             (Math.abs(textAt.getDeterminant()) <= Double.MIN_VALUE);
 672 
 673         info.font2D = FontUtilities.getFont2D(font);
 674 
 675         int fmhint = fractionalMetricsHint;
 676         if (fmhint == SunHints.INTVAL_FRACTIONALMETRICS_DEFAULT) {
 677             fmhint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
 678         }
 679         info.lcdSubPixPos = false; // conditionally set true in LCD mode.
 680 
 681         /* The text anti-aliasing hints that are set by the client need
 682          * to be interpreted for the current state and stored in the
 683          * FontInfo.aahint which is what will actually be used and
 684          * will be one of OFF, ON, LCD_HRGB or LCD_VRGB.
 685          * This is what pipe selection code should typically refer to, not
 686          * textAntialiasHint. This means we are now evaluating the meaning
 687          * of "default" here. Any pipe that really cares about that will
 688          * also need to consult that variable.
 689          * Otherwise these are being used only as args to getStrike,
 690          * and are encapsulated in that object which is part of the
 691          * FontInfo, so we do not need to store them directly as fields
 692          * in the FontInfo object.
 693          * That could change if FontInfo's were more selectively
 694          * revalidated when graphics state changed. Presently this
 695          * method re-evaluates all fields in the fontInfo.
 696          * The strike doesn't need to know the RGB subpixel order. Just
 697          * if its H or V orientation, so if an LCD option is specified we
 698          * always pass in the RGB hint to the strike.
 699          * frc is non-null only if this is a GlyphVector. For reasons
 700          * which are probably a historical mistake the AA hint in a GV
 701          * is honoured when we render, overriding the Graphics setting.
 702          */
 703         int aahint;
 704         if (frc == null) {
 705             aahint = textAntialiasHint;
 706         } else {
 707             aahint = ((SunHints.Value)frc.getAntiAliasingHint()).getIndex();
 708         }
 709         if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT) {
 710             if (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
 711                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 712             } else {
 713                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
 714             }
 715         } else {
 716             /* If we are in checkFontInfo because a rendering hint has been
 717              * set then all pipes are revalidated. But we can also
 718              * be here because setFont() has been called when the 'gasp'
 719              * hint is set, as then the font size determines the text pipe.
 720              * See comments in SunGraphics2d.setFont(Font).
 721              */
 722             if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP) {
 723                 if (info.font2D.useAAForPtSize(info.pixelHeight)) {
 724                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 725                 } else {
 726                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
 727                 }
 728             } else if (aahint >= SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB) {
 729                 /* loops for default rendering modes are installed in the SG2D
 730                  * constructor. If there are none this will be null.
 731                  * Not all compositing modes update the render loops, so
 732                  * we also test that this is a mode we know should support
 733                  * this. One minor issue is that the loops aren't necessarily
 734                  * installed for a new rendering mode until after this
 735                  * method is called during pipeline validation. So it is
 736                  * theoretically possible that it was set to null for a
 737                  * compositing mode, the composite is then set back to Src,
 738                  * but the loop is still null when this is called and AA=ON
 739                  * is installed instead of an LCD mode.
 740                  * However this is done in the right order in SurfaceData.java
 741                  * so this is not likely to be a problem - but not
 742                  * guaranteed.
 743                  */
 744                 if (
 745                     !surfaceData.canRenderLCDText(this)
 746 //                    loops.drawGlyphListLCDLoop == null ||
 747 //                    compositeState > COMP_ISCOPY ||
 748 //                    paintState > PAINT_ALPHACOLOR
 749                       ) {
 750                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 751                 } else {
 752                     info.lcdRGBOrder = true;
 753                     /* Collapse these into just HRGB or VRGB.
 754                      * Pipe selection code needs only to test for these two.
 755                      * Since these both select the same pipe anyway its
 756                      * tempting to collapse into one value. But they are
 757                      * different strikes (glyph caches) so the distinction
 758                      * needs to be made for that purpose.
 759                      */
 760                     if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HBGR) {
 761                         aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
 762                         info.lcdRGBOrder = false;
 763                     } else if
 764                         (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VBGR) {
 765                         aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB;
 766                         info.lcdRGBOrder = false;
 767                     }
 768                     /* Support subpixel positioning only for the case in
 769                      * which the horizontal resolution is increased
 770                      */
 771                     info.lcdSubPixPos =
 772                         fmhint == SunHints.INTVAL_FRACTIONALMETRICS_ON &&
 773                         aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
 774                 }
 775             }
 776         }
 777         if (FontUtilities.isMacOSX14 &&
 778             (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_OFF))
 779         {
 780              aahint =  SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 781         }
 782         info.aaHint = aahint;
 783         info.fontStrike = info.font2D.getStrike(font, devAt, textAt,
 784                                                 aahint, fmhint);
 785         return info;
 786     }
 787 
 788     public static boolean isRotated(double [] mtx) {
 789         if ((mtx[0] == mtx[3]) &&
 790             (mtx[1] == 0.0) &&
 791             (mtx[2] == 0.0) &&
 792             (mtx[0] > 0.0))
 793         {
 794             return false;
 795         }
 796 
 797         return true;
 798     }
 799 
 800     public void setFont(Font font) {
 801         /* replacing the reference equality test font != this.font with
 802          * !font.equals(this.font) did not yield any measurable difference
 803          * in testing, but there may be yet to be identified cases where it
 804          * is beneficial.
 805          */
 806         if (font != null && font!=this.font/*!font.equals(this.font)*/) {
 807             /* In the GASP AA case the textpipe depends on the glyph size
 808              * as determined by graphics and font transforms as well as the
 809              * font size, and information in the font. But we may invalidate
 810              * the pipe only to find that it made no difference.
 811              * Deferring pipe invalidation to checkFontInfo won't work because
 812              * when called we may already be rendering to the wrong pipe.
 813              * So, if the font is transformed, or the graphics has more than
 814              * a simple scale, we'll take that as enough of a hint to
 815              * revalidate everything. But if they aren't we will
 816              * use the font's point size to query the gasp table and see if
 817              * what it says matches what's currently being used, in which
 818              * case there's no need to invalidate the textpipe.
 819              * This should be sufficient for all typical uses cases.
 820              */
 821             if (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP &&
 822                 textpipe != invalidpipe &&
 823                 (transformState > TRANSFORM_ANY_TRANSLATE ||
 824                  font.isTransformed() ||
 825                  fontInfo == null || // Precaution, if true shouldn't get here
 826                  (fontInfo.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) !=
 827                      FontUtilities.getFont2D(font).
 828                          useAAForPtSize(font.getSize()))) {
 829                 textpipe = invalidpipe;
 830             }
 831             this.font = font;
 832             this.fontMetrics = null;
 833             this.validFontInfo = false;
 834         }
 835     }
 836 
 837     public FontInfo getFontInfo() {
 838         if (!validFontInfo) {
 839             this.fontInfo = checkFontInfo(this.fontInfo, font, null);
 840             validFontInfo = true;
 841         }
 842         return this.fontInfo;
 843     }
 844 
 845     /* Used by drawGlyphVector which specifies its own font. */
 846     public FontInfo getGVFontInfo(Font font, FontRenderContext frc) {
 847         if (glyphVectorFontInfo != null &&
 848             glyphVectorFontInfo.font == font &&
 849             glyphVectorFRC == frc) {
 850             return glyphVectorFontInfo;
 851         } else {
 852             glyphVectorFRC = frc;
 853             return glyphVectorFontInfo =
 854                 checkFontInfo(glyphVectorFontInfo, font, frc);
 855         }
 856     }
 857 
 858     public FontMetrics getFontMetrics() {
 859         if (this.fontMetrics != null) {
 860             return this.fontMetrics;
 861         }
 862         /* NB the constructor and the setter disallow "font" being null */
 863         return this.fontMetrics =
 864            FontDesignMetrics.getMetrics(font, getFontRenderContext());
 865     }
 866 
 867     public FontMetrics getFontMetrics(Font font) {
 868         if ((this.fontMetrics != null) && (font == this.font)) {
 869             return this.fontMetrics;
 870         }
 871         FontMetrics fm =
 872           FontDesignMetrics.getMetrics(font, getFontRenderContext());
 873 
 874         if (this.font == font) {
 875             this.fontMetrics = fm;
 876         }
 877         return fm;
 878     }
 879 
 880     /**
 881      * Checks to see if a Path intersects the specified Rectangle in device
 882      * space.  The rendering attributes taken into account include the
 883      * clip, transform, and stroke attributes.
 884      * @param rect The area in device space to check for a hit.
 885      * @param s The path to check for a hit.
 886      * @param onStroke Flag to choose between testing the stroked or
 887      * the filled path.
 888      * @return True if there is a hit, false otherwise.
 889      * @see #setStroke
 890      * @see #fill(Shape)
 891      * @see #draw(Shape)
 892      * @see #transform
 893      * @see #setTransform
 894      * @see #clip
 895      * @see #setClip
 896      */
 897     public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
 898         if (onStroke) {
 899             s = stroke.createStrokedShape(s);
 900         }
 901 
 902         s = transformShape(s);
 903         if ((constrainX|constrainY) != 0) {
 904             rect = new Rectangle(rect);
 905             rect.translate(constrainX, constrainY);
 906         }
 907 
 908         return s.intersects(rect);
 909     }
 910 
 911     /**
 912      * Return the ColorModel associated with this Graphics2D.
 913      */
 914     public ColorModel getDeviceColorModel() {
 915         return surfaceData.getColorModel();
 916     }
 917 
 918     /**
 919      * Return the device configuration associated with this Graphics2D.
 920      */
 921     public GraphicsConfiguration getDeviceConfiguration() {
 922         return surfaceData.getDeviceConfiguration();
 923     }
 924 
 925     /**
 926      * Return the SurfaceData object assigned to manage the destination
 927      * drawable surface of this Graphics2D.
 928      */
 929     public SurfaceData getSurfaceData() {
 930         return surfaceData;
 931     }
 932 
 933     /**
 934      * Sets the Composite in the current graphics state. Composite is used
 935      * in all drawing methods such as drawImage, drawString, drawPath,
 936      * and fillPath.  It specifies how new pixels are to be combined with
 937      * the existing pixels on the graphics device in the rendering process.
 938      * @param comp The Composite object to be used for drawing.
 939      * @see java.awt.Graphics#setXORMode
 940      * @see java.awt.Graphics#setPaintMode
 941      * @see AlphaComposite
 942      */
 943     public void setComposite(Composite comp) {
 944         if (composite == comp) {
 945             return;
 946         }
 947         int newCompState;
 948         CompositeType newCompType;
 949         if (comp instanceof AlphaComposite) {
 950             AlphaComposite alphacomp = (AlphaComposite) comp;
 951             newCompType = CompositeType.forAlphaComposite(alphacomp);
 952             if (newCompType == CompositeType.SrcOverNoEa) {
 953                 if (paintState == PAINT_OPAQUECOLOR ||
 954                     (paintState > PAINT_ALPHACOLOR &&
 955                      paint.getTransparency() == Transparency.OPAQUE))
 956                 {
 957                     newCompState = COMP_ISCOPY;
 958                 } else {
 959                     newCompState = COMP_ALPHA;
 960                 }
 961             } else if (newCompType == CompositeType.SrcNoEa ||
 962                        newCompType == CompositeType.Src ||
 963                        newCompType == CompositeType.Clear)
 964             {
 965                 newCompState = COMP_ISCOPY;
 966             } else if (surfaceData.getTransparency() == Transparency.OPAQUE &&
 967                        newCompType == CompositeType.SrcIn)
 968             {
 969                 newCompState = COMP_ISCOPY;
 970             } else {
 971                 newCompState = COMP_ALPHA;
 972             }
 973         } else if (comp instanceof XORComposite) {
 974             newCompState = COMP_XOR;
 975             newCompType = CompositeType.Xor;
 976         } else if (comp == null) {
 977             throw new IllegalArgumentException("null Composite");
 978         } else {
 979             surfaceData.checkCustomComposite();
 980             newCompState = COMP_CUSTOM;
 981             newCompType = CompositeType.General;
 982         }
 983         if (compositeState != newCompState ||
 984             imageComp != newCompType)
 985         {
 986             compositeState = newCompState;
 987             imageComp = newCompType;
 988             invalidatePipe();
 989             validFontInfo = false;
 990         }
 991         composite = comp;
 992         if (paintState <= PAINT_ALPHACOLOR) {
 993             validateColor();
 994         }
 995     }
 996 
 997     /**
 998      * Sets the Paint in the current graphics state.
 999      * @param paint The Paint object to be used to generate color in
1000      * the rendering process.
1001      * @see java.awt.Graphics#setColor
1002      * @see GradientPaint
1003      * @see TexturePaint
1004      */
1005     public void setPaint(Paint paint) {
1006         if (paint instanceof Color) {
1007             setColor((Color) paint);
1008             return;
1009         }
1010         if (paint == null || this.paint == paint) {
1011             return;
1012         }
1013         this.paint = paint;
1014         if (imageComp == CompositeType.SrcOverNoEa) {
1015             // special case where compState depends on opacity of paint
1016             if (paint.getTransparency() == Transparency.OPAQUE) {
1017                 if (compositeState != COMP_ISCOPY) {
1018                     compositeState = COMP_ISCOPY;
1019                 }
1020             } else {
1021                 if (compositeState == COMP_ISCOPY) {
1022                     compositeState = COMP_ALPHA;
1023                 }
1024             }
1025         }
1026         Class<? extends Paint> paintClass = paint.getClass();
1027         if (paintClass == GradientPaint.class) {
1028             paintState = PAINT_GRADIENT;
1029         } else if (paintClass == LinearGradientPaint.class) {
1030             paintState = PAINT_LIN_GRADIENT;
1031         } else if (paintClass == RadialGradientPaint.class) {
1032             paintState = PAINT_RAD_GRADIENT;
1033         } else if (paintClass == TexturePaint.class) {
1034             paintState = PAINT_TEXTURE;
1035         } else {
1036             paintState = PAINT_CUSTOM;
1037         }
1038         validFontInfo = false;
1039         invalidatePipe();
1040     }
1041 
1042     static final int NON_UNIFORM_SCALE_MASK =
1043         (AffineTransform.TYPE_GENERAL_TRANSFORM |
1044          AffineTransform.TYPE_GENERAL_SCALE);
1045     public static final double MinPenSizeAA =
1046         sun.java2d.pipe.RenderingEngine.getInstance().getMinimumAAPenSize();
1047     public static final double MinPenSizeAASquared =
1048         (MinPenSizeAA * MinPenSizeAA);
1049     // Since inaccuracies in the trig package can cause us to
1050     // calculated a rotated pen width of just slightly greater
1051     // than 1.0, we add a fudge factor to our comparison value
1052     // here so that we do not misclassify single width lines as
1053     // wide lines under certain rotations.
1054     public static final double MinPenSizeSquared = 1.000000001;
1055 
1056     private void validateBasicStroke(BasicStroke bs) {
1057         boolean aa = (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON);
1058         if (transformState < TRANSFORM_TRANSLATESCALE) {
1059             if (aa) {
1060                 if (bs.getLineWidth() <= MinPenSizeAA) {
1061                     if (bs.getDashArray() == null) {
1062                         strokeState = STROKE_THIN;
1063                     } else {
1064                         strokeState = STROKE_THINDASHED;
1065                     }
1066                 } else {
1067                     strokeState = STROKE_WIDE;
1068                 }
1069             } else {
1070                 if (bs == defaultStroke) {
1071                     strokeState = STROKE_THIN;
1072                 } else if (bs.getLineWidth() <= 1.0f) {
1073                     if (bs.getDashArray() == null) {
1074                         strokeState = STROKE_THIN;
1075                     } else {
1076                         strokeState = STROKE_THINDASHED;
1077                     }
1078                 } else {
1079                     strokeState = STROKE_WIDE;
1080                 }
1081             }
1082         } else {
1083             double widthsquared;
1084             if ((transform.getType() & NON_UNIFORM_SCALE_MASK) == 0) {
1085                 /* sqrt omitted, compare to squared limits below. */
1086                 widthsquared = Math.abs(transform.getDeterminant());
1087             } else {
1088                 /* First calculate the "maximum scale" of this transform. */
1089                 double A = transform.getScaleX();       // m00
1090                 double C = transform.getShearX();       // m01
1091                 double B = transform.getShearY();       // m10
1092                 double D = transform.getScaleY();       // m11
1093 
1094                 /*
1095                  * Given a 2 x 2 affine matrix [ A B ] such that
1096                  *                             [ C D ]
1097                  * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
1098                  * find the maximum magnitude (norm) of the vector v'
1099                  * with the constraint (x^2 + y^2 = 1).
1100                  * The equation to maximize is
1101                  *     |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
1102                  * or  |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
1103                  * Since sqrt is monotonic we can maximize |v'|^2
1104                  * instead and plug in the substitution y = sqrt(1 - x^2).
1105                  * Trigonometric equalities can then be used to get
1106                  * rid of most of the sqrt terms.
1107                  */
1108                 double EA = A*A + B*B;          // x^2 coefficient
1109                 double EB = 2*(A*C + B*D);      // xy coefficient
1110                 double EC = C*C + D*D;          // y^2 coefficient
1111 
1112                 /*
1113                  * There is a lot of calculus omitted here.
1114                  *
1115                  * Conceptually, in the interests of understanding the
1116                  * terms that the calculus produced we can consider
1117                  * that EA and EC end up providing the lengths along
1118                  * the major axes and the hypot term ends up being an
1119                  * adjustment for the additional length along the off-axis
1120                  * angle of rotated or sheared ellipses as well as an
1121                  * adjustment for the fact that the equation below
1122                  * averages the two major axis lengths.  (Notice that
1123                  * the hypot term contains a part which resolves to the
1124                  * difference of these two axis lengths in the absence
1125                  * of rotation.)
1126                  *
1127                  * In the calculus, the ratio of the EB and (EA-EC) terms
1128                  * ends up being the tangent of 2*theta where theta is
1129                  * the angle that the long axis of the ellipse makes
1130                  * with the horizontal axis.  Thus, this equation is
1131                  * calculating the length of the hypotenuse of a triangle
1132                  * along that axis.
1133                  */
1134                 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
1135 
1136                 /* sqrt omitted, compare to squared limits below. */
1137                 widthsquared = ((EA + EC + hypot)/2.0);
1138             }
1139             if (bs != defaultStroke) {
1140                 widthsquared *= bs.getLineWidth() * bs.getLineWidth();
1141             }
1142             if (widthsquared <=
1143                 (aa ? MinPenSizeAASquared : MinPenSizeSquared))
1144             {
1145                 if (bs.getDashArray() == null) {
1146                     strokeState = STROKE_THIN;
1147                 } else {
1148                     strokeState = STROKE_THINDASHED;
1149                 }
1150             } else {
1151                 strokeState = STROKE_WIDE;
1152             }
1153         }
1154     }
1155 
1156     /*
1157      * Sets the Stroke in the current graphics state.
1158      * @param s The Stroke object to be used to stroke a Path in
1159      * the rendering process.
1160      * @see BasicStroke
1161      */
1162     public void setStroke(Stroke s) {
1163         if (s == null) {
1164             throw new IllegalArgumentException("null Stroke");
1165         }
1166         int saveStrokeState = strokeState;
1167         stroke = s;
1168         if (s instanceof BasicStroke) {
1169             validateBasicStroke((BasicStroke) s);
1170         } else {
1171             strokeState = STROKE_CUSTOM;
1172         }
1173         if (strokeState != saveStrokeState) {
1174             invalidatePipe();
1175         }
1176     }
1177 
1178     /**
1179      * Sets the preferences for the rendering algorithms.
1180      * Hint categories include controls for rendering quality and
1181      * overall time/quality trade-off in the rendering process.
1182      * @param hintKey The key of hint to be set. The strings are
1183      * defined in the RenderingHints class.
1184      * @param hintValue The value indicating preferences for the specified
1185      * hint category. These strings are defined in the RenderingHints
1186      * class.
1187      * @see RenderingHints
1188      */
1189     public void setRenderingHint(Key hintKey, Object hintValue) {
1190         // If we recognize the key, we must recognize the value
1191         //     otherwise throw an IllegalArgumentException
1192         //     and do not change the Hints object
1193         // If we do not recognize the key, just pass it through
1194         //     to the Hints object untouched
1195         if (!hintKey.isCompatibleValue(hintValue)) {
1196             throw new IllegalArgumentException
1197                 (hintValue+" is not compatible with "+hintKey);
1198         }
1199         if (hintKey instanceof SunHints.Key) {
1200             boolean stateChanged;
1201             boolean textStateChanged = false;
1202             boolean recognized = true;
1203             SunHints.Key sunKey = (SunHints.Key) hintKey;
1204             int newHint;
1205             if (sunKey == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST) {
1206                 newHint = ((Integer)hintValue).intValue();
1207             } else {
1208                 newHint = ((SunHints.Value) hintValue).getIndex();
1209             }
1210             switch (sunKey.getIndex()) {
1211             case SunHints.INTKEY_RENDERING:
1212                 stateChanged = (renderHint != newHint);
1213                 if (stateChanged) {
1214                     renderHint = newHint;
1215                     if (interpolationHint == -1) {
1216                         interpolationType =
1217                             (newHint == SunHints.INTVAL_RENDER_QUALITY
1218                              ? AffineTransformOp.TYPE_BILINEAR
1219                              : AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
1220                     }
1221                 }
1222                 break;
1223             case SunHints.INTKEY_ANTIALIASING:
1224                 stateChanged = (antialiasHint != newHint);
1225                 antialiasHint = newHint;
1226                 if (stateChanged) {
1227                     textStateChanged =
1228                         (textAntialiasHint ==
1229                          SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT);
1230                     if (strokeState != STROKE_CUSTOM) {
1231                         validateBasicStroke((BasicStroke) stroke);
1232                     }
1233                 }
1234                 break;
1235             case SunHints.INTKEY_TEXT_ANTIALIASING:
1236                 stateChanged = (textAntialiasHint != newHint);
1237                 textStateChanged = stateChanged;
1238                 textAntialiasHint = newHint;
1239                 break;
1240             case SunHints.INTKEY_FRACTIONALMETRICS:
1241                 stateChanged = (fractionalMetricsHint != newHint);
1242                 textStateChanged = stateChanged;
1243                 fractionalMetricsHint = newHint;
1244                 break;
1245             case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1246                 stateChanged = false;
1247                 /* Already have validated it is an int 100 <= newHint <= 250 */
1248                 lcdTextContrast = newHint;
1249                 break;
1250             case SunHints.INTKEY_INTERPOLATION:
1251                 interpolationHint = newHint;
1252                 switch (newHint) {
1253                 case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1254                     newHint = AffineTransformOp.TYPE_BICUBIC;
1255                     break;
1256                 case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1257                     newHint = AffineTransformOp.TYPE_BILINEAR;
1258                     break;
1259                 default:
1260                 case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1261                     newHint = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1262                     break;
1263                 }
1264                 stateChanged = (interpolationType != newHint);
1265                 interpolationType = newHint;
1266                 break;
1267             case SunHints.INTKEY_STROKE_CONTROL:
1268                 stateChanged = (strokeHint != newHint);
1269                 strokeHint = newHint;
1270                 break;
1271             case SunHints.INTKEY_RESOLUTION_VARIANT:
1272                 stateChanged = (resolutionVariantHint != newHint);
1273                 resolutionVariantHint = newHint;
1274                 break;
1275             default:
1276                 recognized = false;
1277                 stateChanged = false;
1278                 break;
1279             }
1280             if (recognized) {
1281                 if (stateChanged) {
1282                     invalidatePipe();
1283                     if (textStateChanged) {
1284                         fontMetrics = null;
1285                         this.cachedFRC = null;
1286                         validFontInfo = false;
1287                         this.glyphVectorFontInfo = null;
1288                     }
1289                 }
1290                 if (hints != null) {
1291                     hints.put(hintKey, hintValue);
1292                 }
1293                 return;
1294             }
1295         }
1296         // Nothing we recognize so none of "our state" has changed
1297         if (hints == null) {
1298             hints = makeHints(null);
1299         }
1300         hints.put(hintKey, hintValue);
1301     }
1302 
1303 
1304     /**
1305      * Returns the preferences for the rendering algorithms.
1306      * @param hintKey The category of hint to be set. The strings
1307      * are defined in the RenderingHints class.
1308      * @return The preferences for rendering algorithms. The strings
1309      * are defined in the RenderingHints class.
1310      * @see RenderingHints
1311      */
1312     public Object getRenderingHint(Key hintKey) {
1313         if (hints != null) {
1314             return hints.get(hintKey);
1315         }
1316         if (!(hintKey instanceof SunHints.Key)) {
1317             return null;
1318         }
1319         int keyindex = ((SunHints.Key)hintKey).getIndex();
1320         switch (keyindex) {
1321         case SunHints.INTKEY_RENDERING:
1322             return SunHints.Value.get(SunHints.INTKEY_RENDERING,
1323                                       renderHint);
1324         case SunHints.INTKEY_ANTIALIASING:
1325             return SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,
1326                                       antialiasHint);
1327         case SunHints.INTKEY_TEXT_ANTIALIASING:
1328             return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
1329                                       textAntialiasHint);
1330         case SunHints.INTKEY_FRACTIONALMETRICS:
1331             return SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
1332                                       fractionalMetricsHint);
1333         case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1334             return lcdTextContrast;
1335         case SunHints.INTKEY_INTERPOLATION:
1336             switch (interpolationHint) {
1337             case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1338                 return SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1339             case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1340                 return SunHints.VALUE_INTERPOLATION_BILINEAR;
1341             case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1342                 return SunHints.VALUE_INTERPOLATION_BICUBIC;
1343             }
1344             return null;
1345         case SunHints.INTKEY_STROKE_CONTROL:
1346             return SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,
1347                                       strokeHint);
1348         case SunHints.INTKEY_RESOLUTION_VARIANT:
1349             return SunHints.Value.get(SunHints.INTKEY_RESOLUTION_VARIANT,
1350                                       resolutionVariantHint);
1351         }
1352         return null;
1353     }
1354 
1355     /**
1356      * Sets the preferences for the rendering algorithms.
1357      * Hint categories include controls for rendering quality and
1358      * overall time/quality trade-off in the rendering process.
1359      * @param hints The rendering hints to be set
1360      * @see RenderingHints
1361      */
1362     public void setRenderingHints(Map<?,?> hints) {
1363         this.hints = null;
1364         renderHint = SunHints.INTVAL_RENDER_DEFAULT;
1365         antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
1366         textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
1367         fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
1368         lcdTextContrast = lcdTextContrastDefaultValue;
1369         interpolationHint = -1;
1370         interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1371         boolean customHintPresent = false;
1372         Iterator<?> iter = hints.keySet().iterator();
1373         while (iter.hasNext()) {
1374             Object key = iter.next();
1375             if (key == SunHints.KEY_RENDERING ||
1376                 key == SunHints.KEY_ANTIALIASING ||
1377                 key == SunHints.KEY_TEXT_ANTIALIASING ||
1378                 key == SunHints.KEY_FRACTIONALMETRICS ||
1379                 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||
1380                 key == SunHints.KEY_STROKE_CONTROL ||
1381                 key == SunHints.KEY_INTERPOLATION)
1382             {
1383                 setRenderingHint((Key) key, hints.get(key));
1384             } else {
1385                 customHintPresent = true;
1386             }
1387         }
1388         if (customHintPresent) {
1389             this.hints = makeHints(hints);
1390         }
1391         invalidatePipe();
1392     }
1393 
1394     /**
1395      * Adds a number of preferences for the rendering algorithms.
1396      * Hint categories include controls for rendering quality and
1397      * overall time/quality trade-off in the rendering process.
1398      * @param hints The rendering hints to be set
1399      * @see RenderingHints
1400      */
1401     public void addRenderingHints(Map<?,?> hints) {
1402         boolean customHintPresent = false;
1403         Iterator<?> iter = hints.keySet().iterator();
1404         while (iter.hasNext()) {
1405             Object key = iter.next();
1406             if (key == SunHints.KEY_RENDERING ||
1407                 key == SunHints.KEY_ANTIALIASING ||
1408                 key == SunHints.KEY_TEXT_ANTIALIASING ||
1409                 key == SunHints.KEY_FRACTIONALMETRICS ||
1410                 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||
1411                 key == SunHints.KEY_STROKE_CONTROL ||
1412                 key == SunHints.KEY_INTERPOLATION)
1413             {
1414                 setRenderingHint((Key) key, hints.get(key));
1415             } else {
1416                 customHintPresent = true;
1417             }
1418         }
1419         if (customHintPresent) {
1420             if (this.hints == null) {
1421                 this.hints = makeHints(hints);
1422             } else {
1423                 this.hints.putAll(hints);
1424             }
1425         }
1426     }
1427 
1428     /**
1429      * Gets the preferences for the rendering algorithms.
1430      * Hint categories include controls for rendering quality and
1431      * overall time/quality trade-off in the rendering process.
1432      * @see RenderingHints
1433      */
1434     public RenderingHints getRenderingHints() {
1435         if (hints == null) {
1436             return makeHints(null);
1437         } else {
1438             return (RenderingHints) hints.clone();
1439         }
1440     }
1441 
1442     RenderingHints makeHints(Map<?,?> hints) {
1443         RenderingHints model = new RenderingHints(null);
1444         if (hints != null) {
1445             model.putAll(hints);
1446         }
1447         model.put(SunHints.KEY_RENDERING,
1448                   SunHints.Value.get(SunHints.INTKEY_RENDERING,
1449                                      renderHint));
1450         model.put(SunHints.KEY_ANTIALIASING,
1451                   SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,
1452                                      antialiasHint));
1453         model.put(SunHints.KEY_TEXT_ANTIALIASING,
1454                   SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
1455                                      textAntialiasHint));
1456         model.put(SunHints.KEY_FRACTIONALMETRICS,
1457                   SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
1458                                      fractionalMetricsHint));
1459         model.put(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST,
1460                   Integer.valueOf(lcdTextContrast));
1461         Object value;
1462         switch (interpolationHint) {
1463         case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1464             value = SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1465             break;
1466         case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1467             value = SunHints.VALUE_INTERPOLATION_BILINEAR;
1468             break;
1469         case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1470             value = SunHints.VALUE_INTERPOLATION_BICUBIC;
1471             break;
1472         default:
1473             value = null;
1474             break;
1475         }
1476         if (value != null) {
1477             model.put(SunHints.KEY_INTERPOLATION, value);
1478         }
1479         model.put(SunHints.KEY_STROKE_CONTROL,
1480                   SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,
1481                                      strokeHint));
1482         return model;
1483     }
1484 
1485     /**
1486      * Concatenates the current transform of this Graphics2D with a
1487      * translation transformation.
1488      * This is equivalent to calling transform(T), where T is an
1489      * AffineTransform represented by the following matrix:
1490      * <pre>
1491      *          [   1    0    tx  ]
1492      *          [   0    1    ty  ]
1493      *          [   0    0    1   ]
1494      * </pre>
1495      */
1496     public void translate(double tx, double ty) {
1497         transform.translate(tx, ty);
1498         invalidateTransform();
1499     }
1500 
1501     /**
1502      * Concatenates the current transform of this Graphics2D with a
1503      * rotation transformation.
1504      * This is equivalent to calling transform(R), where R is an
1505      * AffineTransform represented by the following matrix:
1506      * <pre>
1507      *          [   cos(theta)    -sin(theta)    0   ]
1508      *          [   sin(theta)     cos(theta)    0   ]
1509      *          [       0              0         1   ]
1510      * </pre>
1511      * Rotating with a positive angle theta rotates points on the positive
1512      * x axis toward the positive y axis.
1513      * @param theta The angle of rotation in radians.
1514      */
1515     public void rotate(double theta) {
1516         transform.rotate(theta);
1517         invalidateTransform();
1518     }
1519 
1520     /**
1521      * Concatenates the current transform of this Graphics2D with a
1522      * translated rotation transformation.
1523      * This is equivalent to the following sequence of calls:
1524      * <pre>
1525      *          translate(x, y);
1526      *          rotate(theta);
1527      *          translate(-x, -y);
1528      * </pre>
1529      * Rotating with a positive angle theta rotates points on the positive
1530      * x axis toward the positive y axis.
1531      * @param theta The angle of rotation in radians.
1532      * @param x The x coordinate of the origin of the rotation
1533      * @param y The x coordinate of the origin of the rotation
1534      */
1535     public void rotate(double theta, double x, double y) {
1536         transform.rotate(theta, x, y);
1537         invalidateTransform();
1538     }
1539 
1540     /**
1541      * Concatenates the current transform of this Graphics2D with a
1542      * scaling transformation.
1543      * This is equivalent to calling transform(S), where S is an
1544      * AffineTransform represented by the following matrix:
1545      * <pre>
1546      *          [   sx   0    0   ]
1547      *          [   0    sy   0   ]
1548      *          [   0    0    1   ]
1549      * </pre>
1550      */
1551     public void scale(double sx, double sy) {
1552         transform.scale(sx, sy);
1553         invalidateTransform();
1554     }
1555 
1556     /**
1557      * Concatenates the current transform of this Graphics2D with a
1558      * shearing transformation.
1559      * This is equivalent to calling transform(SH), where SH is an
1560      * AffineTransform represented by the following matrix:
1561      * <pre>
1562      *          [   1   shx   0   ]
1563      *          [  shy   1    0   ]
1564      *          [   0    0    1   ]
1565      * </pre>
1566      * @param shx The factor by which coordinates are shifted towards the
1567      * positive X axis direction according to their Y coordinate
1568      * @param shy The factor by which coordinates are shifted towards the
1569      * positive Y axis direction according to their X coordinate
1570      */
1571     public void shear(double shx, double shy) {
1572         transform.shear(shx, shy);
1573         invalidateTransform();
1574     }
1575 
1576     /**
1577      * Composes a Transform object with the transform in this
1578      * Graphics2D according to the rule last-specified-first-applied.
1579      * If the currrent transform is Cx, the result of composition
1580      * with Tx is a new transform Cx'.  Cx' becomes the current
1581      * transform for this Graphics2D.
1582      * Transforming a point p by the updated transform Cx' is
1583      * equivalent to first transforming p by Tx and then transforming
1584      * the result by the original transform Cx.  In other words,
1585      * Cx'(p) = Cx(Tx(p)).
1586      * A copy of the Tx is made, if necessary, so further
1587      * modifications to Tx do not affect rendering.
1588      * @param xform The Transform object to be composed with the current
1589      * transform.
1590      * @see #setTransform
1591      * @see AffineTransform
1592      */
1593     public void transform(AffineTransform xform) {
1594         this.transform.concatenate(xform);
1595         invalidateTransform();
1596     }
1597 
1598     /**
1599      * Translate
1600      */
1601     public void translate(int x, int y) {
1602         transform.translate(x, y);
1603         if (transformState <= TRANSFORM_INT_TRANSLATE) {
1604             transX += x;
1605             transY += y;
1606             transformState = (((transX | transY) == 0) ?
1607                               TRANSFORM_ISIDENT : TRANSFORM_INT_TRANSLATE);
1608         } else {
1609             invalidateTransform();
1610         }
1611     }
1612 
1613     /**
1614      * Sets the Transform in the current graphics state.
1615      * @param Tx The Transform object to be used in the rendering process.
1616      * @see #transform
1617      * @see AffineTransform
1618      */
1619     @Override
1620     public void setTransform(AffineTransform Tx) {
1621         if ((constrainX | constrainY) == 0) {
1622             transform.setTransform(Tx);
1623         } else {
1624             transform.setToTranslation(constrainX, constrainY);
1625             transform.concatenate(Tx);
1626         }
1627         invalidateTransform();
1628     }
1629 
1630     protected void invalidateTransform() {
1631         int type = transform.getType();
1632         int origTransformState = transformState;
1633         if (type == AffineTransform.TYPE_IDENTITY) {
1634             transformState = TRANSFORM_ISIDENT;
1635             transX = transY = 0;
1636         } else if (type == AffineTransform.TYPE_TRANSLATION) {
1637             double dtx = transform.getTranslateX();
1638             double dty = transform.getTranslateY();
1639             transX = (int) Math.floor(dtx + 0.5);
1640             transY = (int) Math.floor(dty + 0.5);
1641             if (dtx == transX && dty == transY) {
1642                 transformState = TRANSFORM_INT_TRANSLATE;
1643             } else {
1644                 transformState = TRANSFORM_ANY_TRANSLATE;
1645             }
1646         } else if ((type & (AffineTransform.TYPE_FLIP |
1647                             AffineTransform.TYPE_MASK_ROTATION |
1648                             AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0)
1649         {
1650             transformState = TRANSFORM_TRANSLATESCALE;
1651             transX = transY = 0;
1652         } else {
1653             transformState = TRANSFORM_GENERIC;
1654             transX = transY = 0;
1655         }
1656 
1657         if (transformState >= TRANSFORM_TRANSLATESCALE ||
1658             origTransformState >= TRANSFORM_TRANSLATESCALE)
1659         {
1660             /* Its only in this case that the previous or current transform
1661              * was more than a translate that font info is invalidated
1662              */
1663             cachedFRC = null;
1664             this.validFontInfo = false;
1665             this.fontMetrics = null;
1666             this.glyphVectorFontInfo = null;
1667 
1668             if (transformState != origTransformState) {
1669                 invalidatePipe();
1670             }
1671         }
1672         if (strokeState != STROKE_CUSTOM) {
1673             validateBasicStroke((BasicStroke) stroke);
1674         }
1675     }
1676 
1677     /**
1678      * Returns the current Transform in the Graphics2D state.
1679      * @see #transform
1680      * @see #setTransform
1681      */
1682     @Override
1683     public AffineTransform getTransform() {
1684         if ((constrainX | constrainY) == 0) {
1685             return new AffineTransform(transform);
1686         }
1687         AffineTransform tx
1688                 = AffineTransform.getTranslateInstance(-constrainX, -constrainY);
1689         tx.concatenate(transform);
1690         return tx;
1691     }
1692 
1693     /**
1694      * Returns the current Transform ignoring the "constrain"
1695      * rectangle.
1696      */
1697     public AffineTransform cloneTransform() {
1698         return new AffineTransform(transform);
1699     }
1700 
1701     /**
1702      * Returns the current Paint in the Graphics2D state.
1703      * @see #setPaint
1704      * @see java.awt.Graphics#setColor
1705      */
1706     public Paint getPaint() {
1707         return paint;
1708     }
1709 
1710     /**
1711      * Returns the current Composite in the Graphics2D state.
1712      * @see #setComposite
1713      */
1714     public Composite getComposite() {
1715         return composite;
1716     }
1717 
1718     public Color getColor() {
1719         return foregroundColor;
1720     }
1721 
1722     /*
1723      * Validate the eargb and pixel fields against the current color.
1724      *
1725      * The eargb field must take into account the extraAlpha
1726      * value of an AlphaComposite.  It may also take into account
1727      * the Fsrc Porter-Duff blending function if such a function is
1728      * a constant (see handling of Clear mode below).  For instance,
1729      * by factoring in the (Fsrc == 0) state of the Clear mode we can
1730      * use a SrcNoEa loop just as easily as a general Alpha loop
1731      * since the math will be the same in both cases.
1732      *
1733      * The pixel field will always be the best pixel data choice for
1734      * the final result of all calculations applied to the eargb field.
1735      *
1736      * Note that this method is only necessary under the following
1737      * conditions:
1738      *     (paintState <= PAINT_ALPHA_COLOR &&
1739      *      compositeState <= COMP_CUSTOM)
1740      * though nothing bad will happen if it is run in other states.
1741      */
1742     void validateColor() {
1743         int eargb;
1744         if (imageComp == CompositeType.Clear) {
1745             eargb = 0;
1746         } else {
1747             eargb = foregroundColor.getRGB();
1748             if (compositeState <= COMP_ALPHA &&
1749                 imageComp != CompositeType.SrcNoEa &&
1750                 imageComp != CompositeType.SrcOverNoEa)
1751             {
1752                 AlphaComposite alphacomp = (AlphaComposite) composite;
1753                 int a = Math.round(alphacomp.getAlpha() * (eargb >>> 24));
1754                 eargb = (eargb & 0x00ffffff) | (a << 24);
1755             }
1756         }
1757         this.eargb = eargb;
1758         this.pixel = surfaceData.pixelFor(eargb);
1759     }
1760 
1761     public void setColor(Color color) {
1762         if (color == null || color == paint) {
1763             return;
1764         }
1765         this.paint = foregroundColor = color;
1766         validateColor();
1767         if ((eargb >> 24) == -1) {
1768             if (paintState == PAINT_OPAQUECOLOR) {
1769                 return;
1770             }
1771             paintState = PAINT_OPAQUECOLOR;
1772             if (imageComp == CompositeType.SrcOverNoEa) {
1773                 // special case where compState depends on opacity of paint
1774                 compositeState = COMP_ISCOPY;
1775             }
1776         } else {
1777             if (paintState == PAINT_ALPHACOLOR) {
1778                 return;
1779             }
1780             paintState = PAINT_ALPHACOLOR;
1781             if (imageComp == CompositeType.SrcOverNoEa) {
1782                 // special case where compState depends on opacity of paint
1783                 compositeState = COMP_ALPHA;
1784             }
1785         }
1786         validFontInfo = false;
1787         invalidatePipe();
1788     }
1789 
1790     /**
1791      * Sets the background color in this context used for clearing a region.
1792      * When Graphics2D is constructed for a component, the backgroung color is
1793      * inherited from the component. Setting the background color in the
1794      * Graphics2D context only affects the subsequent clearRect() calls and
1795      * not the background color of the component. To change the background
1796      * of the component, use appropriate methods of the component.
1797      * @param color The background color that should be used in
1798      * subsequent calls to clearRect().
1799      * @see #getBackground
1800      * @see Graphics#clearRect
1801      */
1802     public void setBackground(Color color) {
1803         backgroundColor = color;
1804     }
1805 
1806     /**
1807      * Returns the background color used for clearing a region.
1808      * @see #setBackground
1809      */
1810     public Color getBackground() {
1811         return backgroundColor;
1812     }
1813 
1814     /**
1815      * Returns the current Stroke in the Graphics2D state.
1816      * @see #setStroke
1817      */
1818     public Stroke getStroke() {
1819         return stroke;
1820     }
1821 
1822     public Rectangle getClipBounds() {
1823         if (clipState == CLIP_DEVICE) {
1824             return null;
1825         }
1826         return getClipBounds(new Rectangle());
1827     }
1828 
1829     public Rectangle getClipBounds(Rectangle r) {
1830         if (clipState != CLIP_DEVICE) {
1831             if (transformState <= TRANSFORM_INT_TRANSLATE) {
1832                 if (usrClip instanceof Rectangle) {
1833                     r.setBounds((Rectangle) usrClip);
1834                 } else {
1835                     r.setFrame(usrClip.getBounds2D());
1836                 }
1837                 r.translate(-transX, -transY);
1838             } else {
1839                 r.setFrame(getClip().getBounds2D());
1840             }
1841         } else if (r == null) {
1842             throw new NullPointerException("null rectangle parameter");
1843         }
1844         return r;
1845     }
1846 
1847     public boolean hitClip(int x, int y, int width, int height) {
1848         if (width <= 0 || height <= 0) {
1849             return false;
1850         }
1851         if (transformState > TRANSFORM_INT_TRANSLATE) {
1852             // Note: Technically the most accurate test would be to
1853             // raster scan the parallelogram of the transformed rectangle
1854             // and do a span for span hit test against the clip, but for
1855             // speed we approximate the test with a bounding box of the
1856             // transformed rectangle.  The cost of rasterizing the
1857             // transformed rectangle is probably high enough that it is
1858             // not worth doing so to save the caller from having to call
1859             // a rendering method where we will end up discovering the
1860             // same answer in about the same amount of time anyway.
1861             // This logic breaks down if this hit test is being performed
1862             // on the bounds of a group of shapes in which case it might
1863             // be beneficial to be a little more accurate to avoid lots
1864             // of subsequent rendering calls.  In either case, this relaxed
1865             // test should not be significantly less accurate than the
1866             // optimal test for most transforms and so the conservative
1867             // answer should not cause too much extra work.
1868 
1869             double[] d = {
1870                 x, y,
1871                 x+width, y,
1872                 x, y+height,
1873                 x+width, y+height
1874             };
1875             transform.transform(d, 0, d, 0, 4);
1876             x = (int) Math.floor(Math.min(Math.min(d[0], d[2]),
1877                                           Math.min(d[4], d[6])));
1878             y = (int) Math.floor(Math.min(Math.min(d[1], d[3]),
1879                                           Math.min(d[5], d[7])));
1880             width = (int) Math.ceil(Math.max(Math.max(d[0], d[2]),
1881                                              Math.max(d[4], d[6])));
1882             height = (int) Math.ceil(Math.max(Math.max(d[1], d[3]),
1883                                               Math.max(d[5], d[7])));
1884         } else {
1885             x += transX;
1886             y += transY;
1887             width += x;
1888             height += y;
1889         }
1890 
1891         try {
1892             if (!getCompClip().intersectsQuickCheckXYXY(x, y, width, height)) {
1893                 return false;
1894             }
1895         } catch (InvalidPipeException e) {
1896             return false;
1897         }
1898         // REMIND: We could go one step further here and examine the
1899         // non-rectangular clip shape more closely if there is one.
1900         // Since the clip has already been rasterized, the performance
1901         // penalty of doing the scan is probably still within the bounds
1902         // of a good tradeoff between speed and quality of the answer.
1903         return true;
1904     }
1905 
1906     protected void validateCompClip() {
1907         int origClipState = clipState;
1908         if (usrClip == null) {
1909             clipState = CLIP_DEVICE;
1910             clipRegion = devClip;
1911         } else if (usrClip instanceof Rectangle2D) {
1912             clipState = CLIP_RECTANGULAR;
1913             clipRegion = devClip.getIntersection((Rectangle2D) usrClip);
1914         } else {
1915             PathIterator cpi = usrClip.getPathIterator(null);
1916             int[] box = new int[4];
1917             ShapeSpanIterator sr = LoopPipe.getFillSSI(this);
1918             try {
1919                 sr.setOutputArea(devClip);
1920                 sr.appendPath(cpi);
1921                 sr.getPathBox(box);
1922                 Region r = Region.getInstance(box, sr);
1923                 clipRegion = r;
1924                 clipState =
1925                     r.isRectangular() ? CLIP_RECTANGULAR : CLIP_SHAPE;
1926             } finally {
1927                 sr.dispose();
1928             }
1929         }
1930         if (origClipState != clipState &&
1931             (clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE))
1932         {
1933             validFontInfo = false;
1934             invalidatePipe();
1935         }
1936     }
1937 
1938     static final int NON_RECTILINEAR_TRANSFORM_MASK =
1939         (AffineTransform.TYPE_GENERAL_TRANSFORM |
1940          AffineTransform.TYPE_GENERAL_ROTATION);
1941 
1942     protected Shape transformShape(Shape s) {
1943         if (s == null) {
1944             return null;
1945         }
1946         if (transformState > TRANSFORM_INT_TRANSLATE) {
1947             return transformShape(transform, s);
1948         } else {
1949             return transformShape(transX, transY, s);
1950         }
1951     }
1952 
1953     public Shape untransformShape(Shape s) {
1954         if (s == null) {
1955             return null;
1956         }
1957         if (transformState > TRANSFORM_INT_TRANSLATE) {
1958             try {
1959                 return transformShape(transform.createInverse(), s);
1960             } catch (NoninvertibleTransformException e) {
1961                 return null;
1962             }
1963         } else {
1964             return transformShape(-transX, -transY, s);
1965         }
1966     }
1967 
1968     protected static Shape transformShape(int tx, int ty, Shape s) {
1969         if (s == null) {
1970             return null;
1971         }
1972 
1973         if (s instanceof Rectangle) {
1974             Rectangle r = s.getBounds();
1975             r.translate(tx, ty);
1976             return r;
1977         }
1978         if (s instanceof Rectangle2D) {
1979             Rectangle2D rect = (Rectangle2D) s;
1980             return new Rectangle2D.Double(rect.getX() + tx,
1981                                           rect.getY() + ty,
1982                                           rect.getWidth(),
1983                                           rect.getHeight());
1984         }
1985 
1986         if (tx == 0 && ty == 0) {
1987             return cloneShape(s);
1988         }
1989 
1990         AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty);
1991         return mat.createTransformedShape(s);
1992     }
1993 
1994     protected static Shape transformShape(AffineTransform tx, Shape clip) {
1995         if (clip == null) {
1996             return null;
1997         }
1998 
1999         if (clip instanceof Rectangle2D &&
2000             (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0)
2001         {
2002             Rectangle2D rect = (Rectangle2D) clip;
2003             double[] matrix = new double[4];
2004             matrix[0] = rect.getX();
2005             matrix[1] = rect.getY();
2006             matrix[2] = matrix[0] + rect.getWidth();
2007             matrix[3] = matrix[1] + rect.getHeight();
2008             tx.transform(matrix, 0, matrix, 0, 2);
2009             fixRectangleOrientation(matrix, rect);
2010             return new Rectangle2D.Double(matrix[0], matrix[1],
2011                                           matrix[2] - matrix[0],
2012                                           matrix[3] - matrix[1]);
2013         }
2014 
2015         if (tx.isIdentity()) {
2016             return cloneShape(clip);
2017         }
2018 
2019         return tx.createTransformedShape(clip);
2020     }
2021 
2022     /**
2023      * Sets orientation of the rectangle according to the clip.
2024      */
2025     private static void fixRectangleOrientation(double[] m, Rectangle2D clip) {
2026         if (clip.getWidth() > 0 != (m[2] - m[0] > 0)) {
2027             double t = m[0];
2028             m[0] = m[2];
2029             m[2] = t;
2030         }
2031         if (clip.getHeight() > 0 != (m[3] - m[1] > 0)) {
2032             double t = m[1];
2033             m[1] = m[3];
2034             m[3] = t;
2035         }
2036     }
2037 
2038     public void clipRect(int x, int y, int w, int h) {
2039         clip(new Rectangle(x, y, w, h));
2040     }
2041 
2042     public void setClip(int x, int y, int w, int h) {
2043         setClip(new Rectangle(x, y, w, h));
2044     }
2045 
2046     public Shape getClip() {
2047         return untransformShape(usrClip);
2048     }
2049 
2050     public void setClip(Shape sh) {
2051         usrClip = transformShape(sh);
2052         validateCompClip();
2053     }
2054 
2055     /**
2056      * Intersects the current clip with the specified Path and sets the
2057      * current clip to the resulting intersection. The clip is transformed
2058      * with the current transform in the Graphics2D state before being
2059      * intersected with the current clip. This method is used to make the
2060      * current clip smaller. To make the clip larger, use any setClip method.
2061      * @param s The Path to be intersected with the current clip.
2062      */
2063     public void clip(Shape s) {
2064         s = transformShape(s);
2065         if (usrClip != null) {
2066             s = intersectShapes(usrClip, s, true, true);
2067         }
2068         usrClip = s;
2069         validateCompClip();
2070     }
2071 
2072     public void setPaintMode() {
2073         setComposite(AlphaComposite.SrcOver);
2074     }
2075 
2076     public void setXORMode(Color c) {
2077         if (c == null) {
2078             throw new IllegalArgumentException("null XORColor");
2079         }
2080         setComposite(new XORComposite(c, surfaceData));
2081     }
2082 
2083     Blit lastCAblit;
2084     Composite lastCAcomp;
2085 
2086     public void copyArea(int x, int y, int w, int h, int dx, int dy) {
2087         try {
2088             doCopyArea(x, y, w, h, dx, dy);
2089         } catch (InvalidPipeException e) {
2090             try {
2091                 revalidateAll();
2092                 doCopyArea(x, y, w, h, dx, dy);
2093             } catch (InvalidPipeException e2) {
2094                 // Still catching the exception; we are not yet ready to
2095                 // validate the surfaceData correctly.  Fail for now and
2096                 // try again next time around.
2097             }
2098         } finally {
2099             surfaceData.markDirty();
2100         }
2101     }
2102 
2103     private void doCopyArea(int x, int y, int w, int h, int dx, int dy) {
2104         if (w <= 0 || h <= 0) {
2105             return;
2106         }
2107 
2108         if (transformState == SunGraphics2D.TRANSFORM_ISIDENT) {
2109             // do nothing
2110         } else if (transformState <= SunGraphics2D.TRANSFORM_ANY_TRANSLATE) {
2111             x += transX;
2112             y += transY;
2113         } else if (transformState == SunGraphics2D.TRANSFORM_TRANSLATESCALE) {
2114             final double[] coords = {x, y, x + w, y + h, x + dx, y + dy};
2115             transform.transform(coords, 0, coords, 0, 3);
2116             x = (int) Math.ceil(coords[0] - 0.5);
2117             y = (int) Math.ceil(coords[1] - 0.5);
2118             w = ((int) Math.ceil(coords[2] - 0.5)) - x;
2119             h = ((int) Math.ceil(coords[3] - 0.5)) - y;
2120             dx = ((int) Math.ceil(coords[4] - 0.5)) - x;
2121             dy = ((int) Math.ceil(coords[5] - 0.5)) - y;
2122             // In case of negative scale transform, reflect the rect coords.
2123             if (w < 0) {
2124                 w = -w;
2125                 x -= w;
2126             }
2127             if (h < 0) {
2128                 h = -h;
2129                 y -= h;
2130             }
2131         } else {
2132             throw new InternalError("transformed copyArea not implemented yet");
2133         }
2134 
2135         SurfaceData theData = surfaceData;
2136         if (theData.copyArea(this, x, y, w, h, dx, dy)) {
2137             return;
2138         }
2139 
2140         // REMIND: This method does not deal with missing data from the
2141         // source object (i.e. it does not send exposure events...)
2142 
2143         Region clip = getCompClip();
2144 
2145         Composite comp = composite;
2146         if (lastCAcomp != comp) {
2147             SurfaceType dsttype = theData.getSurfaceType();
2148             CompositeType comptype = imageComp;
2149             if (CompositeType.SrcOverNoEa.equals(comptype) &&
2150                 theData.getTransparency() == Transparency.OPAQUE)
2151             {
2152                 comptype = CompositeType.SrcNoEa;
2153             }
2154             lastCAblit = Blit.locate(dsttype, comptype, dsttype);
2155             lastCAcomp = comp;
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 s 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} with {@code clip} and
2772      * overwrites {@code destRect} 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 < length ||
3033             offset + length > data.length) {
3034             throw new ArrayIndexOutOfBoundsException("bad offset/length");
3035         }
3036         if (font.hasLayoutAttributes()) {
3037             if (data.length == 0) {
3038                 return;
3039             }
3040             new TextLayout(new String(data, offset, length),
3041                            font, getFontRenderContext()).draw(this, x, y);
3042             return;
3043         }
3044 
3045         try {
3046             textpipe.drawChars(this, data, offset, length, x, y);
3047         } catch (InvalidPipeException e) {
3048             try {
3049                 revalidateAll();
3050                 textpipe.drawChars(this, data, offset, length, x, y);
3051             } catch (InvalidPipeException e2) {
3052                 // Still catching the exception; we are not yet ready to
3053                 // validate the surfaceData correctly.  Fail for now and
3054                 // try again next time around.
3055             }
3056         } finally {
3057             surfaceData.markDirty();
3058         }
3059     }
3060 
3061     public void drawBytes(byte[] data, int offset, int length, int x, int y) {
3062         if (data == null) {
3063             throw new NullPointerException("byte data is null");
3064         }
3065         if (offset < 0 || length < 0 || offset + length < length ||
3066             offset + length > data.length) {
3067             throw new ArrayIndexOutOfBoundsException("bad offset/length");
3068         }
3069         /* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */
3070         char[] chData = new char[length];
3071         for (int i = length; i-- > 0; ) {
3072             chData[i] = (char)(data[i+offset] & 0xff);
3073         }
3074         if (font.hasLayoutAttributes()) {
3075             if (data.length == 0) {
3076                 return;
3077             }
3078             new TextLayout(new String(chData),
3079                            font, getFontRenderContext()).draw(this, x, y);
3080             return;
3081         }
3082 
3083         try {
3084             textpipe.drawChars(this, chData, 0, length, x, y);
3085         } catch (InvalidPipeException e) {
3086             try {
3087                 revalidateAll();
3088                 textpipe.drawChars(this, chData, 0, length, x, y);
3089             } catch (InvalidPipeException e2) {
3090                 // Still catching the exception; we are not yet ready to
3091                 // validate the surfaceData correctly.  Fail for now and
3092                 // try again next time around.
3093             }
3094         } finally {
3095             surfaceData.markDirty();
3096         }
3097     }
3098 // end of text rendering methods
3099 
3100     private Boolean drawHiDPIImage(Image img,
3101                                    int dx1, int dy1, int dx2, int dy2,
3102                                    int sx1, int sy1, int sx2, int sy2,
3103                                    Color bgcolor, ImageObserver observer,
3104                                    AffineTransform xform) {
3105 
3106         if (img instanceof VolatileImage) {
3107             final SurfaceData sd = SurfaceManager.getManager(img)
3108                     .getPrimarySurfaceData();
3109             final double scaleX = sd.getDefaultScaleX();
3110             final double scaleY = sd.getDefaultScaleY();
3111             if (scaleX == 1 && scaleY == 1) {
3112                 return null;
3113             }
3114             sx1 = Region.clipRound(sx1 * scaleX);
3115             sx2 = Region.clipRound(sx2 * scaleX);
3116             sy1 = Region.clipRound(sy1 * scaleY);
3117             sy2 = Region.clipRound(sy2 * scaleY);
3118 
3119             AffineTransform tx = null;
3120             if (xform != null) {
3121                 tx = new AffineTransform(transform);
3122                 transform(xform);
3123             }
3124             boolean result = scaleImage(img, dx1, dy1, dx2, dy2,
3125                                         sx1, sy1, sx2, sy2,
3126                                         bgcolor, observer);
3127             if (tx != null) {
3128                 transform.setTransform(tx);
3129                 invalidateTransform();
3130             }
3131             return result;
3132         } else if (img instanceof MultiResolutionImage) {
3133             // get scaled destination image size
3134 
3135             int width = img.getWidth(observer);
3136             int height = img.getHeight(observer);
3137 
3138             MultiResolutionImage mrImage = (MultiResolutionImage) img;
3139             Image resolutionVariant = getResolutionVariant(mrImage, width, height,
3140                                                            dx1, dy1, dx2, dy2,
3141                                                            sx1, sy1, sx2, sy2,
3142                                                            xform);
3143 
3144             if (resolutionVariant != img && resolutionVariant != null) {
3145                 // recalculate source region for the resolution variant
3146 
3147                 ImageObserver rvObserver = MultiResolutionToolkitImage.
3148                         getResolutionVariantObserver(img, observer,
3149                                 width, height, -1, -1);
3150 
3151                 int rvWidth = resolutionVariant.getWidth(rvObserver);
3152                 int rvHeight = resolutionVariant.getHeight(rvObserver);
3153 
3154                 if (rvWidth < 0 || rvHeight < 0) {
3155                     // The resolution variant is not loaded yet, try to use default resolution
3156                     resolutionVariant = mrImage.getResolutionVariant(width, height);
3157                     rvWidth = resolutionVariant.getWidth(rvObserver);
3158                     rvHeight = resolutionVariant.getHeight(rvObserver);
3159                 }
3160 
3161                 if (0 < width && 0 < height && 0 < rvWidth && 0 < rvHeight) {
3162 
3163                     double widthScale = ((double) rvWidth) / width;
3164                     double heightScale = ((double) rvHeight) / height;
3165 
3166                     if (resolutionVariant instanceof VolatileImage) {
3167                         SurfaceData sd = SurfaceManager
3168                                 .getManager(resolutionVariant)
3169                                 .getPrimarySurfaceData();
3170                         widthScale *= sd.getDefaultScaleX();
3171                         heightScale *= sd.getDefaultScaleY();
3172                     }
3173 
3174                     sx1 = Region.clipScale(sx1, widthScale);
3175                     sy1 = Region.clipScale(sy1, heightScale);
3176                     sx2 = Region.clipScale(sx2, widthScale);
3177                     sy2 = Region.clipScale(sy2, heightScale);
3178 
3179                     observer = rvObserver;
3180                     img = resolutionVariant;
3181 
3182                     if (xform != null) {
3183                         assert dx1 == 0 && dy1 == 0;
3184                         assert dx2 == img.getWidth(observer);
3185                         assert dy2 == img.getHeight(observer);
3186                         AffineTransform renderTX = new AffineTransform(xform);
3187                         renderTX.scale(1 / widthScale, 1 / heightScale);
3188                         return transformImage(img, renderTX, observer);
3189                     }
3190 
3191                     return scaleImage(img, dx1, dy1, dx2, dy2,
3192                                       sx1, sy1, sx2, sy2,
3193                                       bgcolor, observer);
3194                 } else {
3195                     return false; // Image variant is not initialized yet
3196                 }
3197             }
3198         }
3199         return null;
3200     }
3201 
3202     private boolean scaleImage(Image img, int dx1, int dy1, int dx2, int dy2,
3203                                int sx1, int sy1, int sx2, int sy2,
3204                                Color bgcolor, ImageObserver observer)
3205     {
3206         try {
3207             return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1,
3208                                         sx2, sy2, bgcolor, observer);
3209         } catch (InvalidPipeException e) {
3210             try {
3211                 revalidateAll();
3212                 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1,
3213                                             sy1, sx2, sy2, bgcolor, observer);
3214             } catch (InvalidPipeException e2) {
3215                 // Still catching the exception; we are not yet ready to
3216                 // validate the surfaceData correctly.  Fail for now and
3217                 // try again next time around.
3218                 return false;
3219             }
3220         } finally {
3221             surfaceData.markDirty();
3222         }
3223     }
3224 
3225     private boolean transformImage(Image img,
3226                                    AffineTransform xform,
3227                                    ImageObserver observer)
3228     {
3229         try {
3230             return imagepipe.transformImage(this, img, xform, observer);
3231         } catch (InvalidPipeException e) {
3232             try {
3233                 revalidateAll();
3234                 return imagepipe.transformImage(this, img, xform, observer);
3235             } catch (InvalidPipeException e2) {
3236                 // Still catching the exception; we are not yet ready to
3237                 // validate the surfaceData correctly.  Fail for now and
3238                 // try again next time around.
3239                 return false;
3240             }
3241         } finally {
3242             surfaceData.markDirty();
3243         }
3244     }
3245 
3246     private Image getResolutionVariant(MultiResolutionImage img,
3247             int srcWidth, int srcHeight, int dx1, int dy1, int dx2, int dy2,
3248             int sx1, int sy1, int sx2, int sy2, AffineTransform xform) {
3249 
3250         if (srcWidth <= 0 || srcHeight <= 0) {
3251             return null;
3252         }
3253 
3254         int sw = sx2 - sx1;
3255         int sh = sy2 - sy1;
3256 
3257         if (sw == 0 || sh == 0) {
3258             return null;
3259         }
3260 
3261         AffineTransform tx;
3262 
3263         if (xform == null) {
3264             tx = transform;
3265         } else {
3266             tx = new AffineTransform(transform);
3267             tx.concatenate(xform);
3268         }
3269 
3270         int type = tx.getType();
3271         int dw = dx2 - dx1;
3272         int dh = dy2 - dy1;
3273 
3274         double destImageWidth;
3275         double destImageHeight;
3276 
3277         if (resolutionVariantHint == SunHints.INTVAL_RESOLUTION_VARIANT_BASE) {
3278             destImageWidth = srcWidth;
3279             destImageHeight = srcHeight;
3280         } else if (resolutionVariantHint == SunHints.INTVAL_RESOLUTION_VARIANT_DPI_FIT) {
3281             AffineTransform configTransform = getDefaultTransform();
3282             if (configTransform.isIdentity()) {
3283                 destImageWidth = srcWidth;
3284                 destImageHeight = srcHeight;
3285             } else {
3286                 destImageWidth = srcWidth * configTransform.getScaleX();
3287                 destImageHeight = srcHeight * configTransform.getScaleY();
3288             }
3289         } else {
3290             double destRegionWidth;
3291             double destRegionHeight;
3292 
3293             if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP)) == 0) {
3294                 destRegionWidth = dw;
3295                 destRegionHeight = dh;
3296             } else if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP | TYPE_MASK_SCALE)) == 0) {
3297                 destRegionWidth = dw * tx.getScaleX();
3298                 destRegionHeight = dh * tx.getScaleY();
3299             } else {
3300                 destRegionWidth = dw * Math.hypot(
3301                         tx.getScaleX(), tx.getShearY());
3302                 destRegionHeight = dh * Math.hypot(
3303                         tx.getShearX(), tx.getScaleY());
3304             }
3305             destImageWidth = Math.abs(srcWidth * destRegionWidth / sw);
3306             destImageHeight = Math.abs(srcHeight * destRegionHeight / sh);
3307         }
3308 
3309         Image resolutionVariant
3310                 = img.getResolutionVariant(destImageWidth, destImageHeight);
3311 
3312         if (resolutionVariant instanceof ToolkitImage
3313                 && ((ToolkitImage) resolutionVariant).hasError()) {
3314             return null;
3315         }
3316 
3317         return resolutionVariant;
3318     }
3319 
3320     /**
3321      * Draws an image scaled to x,y,w,h in nonblocking mode with a
3322      * callback object.
3323      */
3324     public boolean drawImage(Image img, int x, int y, int width, int height,
3325                              ImageObserver observer) {
3326         return drawImage(img, x, y, width, height, null, observer);
3327     }
3328 
3329     /**
3330      * Not part of the advertised API but a useful utility method
3331      * to call internally.  This is for the case where we are
3332      * drawing to/from given coordinates using a given width/height,
3333      * but we guarantee that the surfaceData's width/height of the src and dest
3334      * areas are equal (no scale needed). Note that this method intentionally
3335      * ignore scale factor of the source image, and copy it as is.
3336      */
3337     public boolean copyImage(Image img, int dx, int dy, int sx, int sy,
3338                              int width, int height, Color bgcolor,
3339                              ImageObserver observer) {
3340         try {
3341             return imagepipe.copyImage(this, img, dx, dy, sx, sy,
3342                                        width, height, bgcolor, observer);
3343         } catch (InvalidPipeException e) {
3344             try {
3345                 revalidateAll();
3346                 return imagepipe.copyImage(this, img, dx, dy, sx, sy,
3347                                            width, height, bgcolor, observer);
3348             } catch (InvalidPipeException e2) {
3349                 // Still catching the exception; we are not yet ready to
3350                 // validate the surfaceData correctly.  Fail for now and
3351                 // try again next time around.
3352                 return false;
3353             }
3354         } finally {
3355             surfaceData.markDirty();
3356         }
3357     }
3358 
3359     /**
3360      * Draws an image scaled to x,y,w,h in nonblocking mode with a
3361      * solid background color and a callback object.
3362      */
3363     public boolean drawImage(Image img, int x, int y, int width, int height,
3364                              Color bg, ImageObserver observer) {
3365 
3366         if (img == null) {
3367             return true;
3368         }
3369 
3370         if ((width == 0) || (height == 0)) {
3371             return true;
3372         }
3373 
3374         final int imgW = img.getWidth(null);
3375         final int imgH = img.getHeight(null);
3376         Boolean hidpiImageDrawn = drawHiDPIImage(img, x, y, x + width, y + height,
3377                                                  0, 0, imgW, imgH, bg, observer,
3378                                                  null);
3379         if (hidpiImageDrawn != null) {
3380             return hidpiImageDrawn;
3381         }
3382 
3383         if (width == imgW && height == imgH) {
3384             return copyImage(img, x, y, 0, 0, width, height, bg, observer);
3385         }
3386 
3387         try {
3388             return imagepipe.scaleImage(this, img, x, y, width, height,
3389                                         bg, observer);
3390         } catch (InvalidPipeException e) {
3391             try {
3392                 revalidateAll();
3393                 return imagepipe.scaleImage(this, img, x, y, width, height,
3394                                             bg, observer);
3395             } catch (InvalidPipeException e2) {
3396                 // Still catching the exception; we are not yet ready to
3397                 // validate the surfaceData correctly.  Fail for now and
3398                 // try again next time around.
3399                 return false;
3400             }
3401         } finally {
3402             surfaceData.markDirty();
3403         }
3404     }
3405 
3406     /**
3407      * Draws an image at x,y in nonblocking mode.
3408      */
3409     public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
3410         return drawImage(img, x, y, null, observer);
3411     }
3412 
3413     /**
3414      * Draws an image at x,y in nonblocking mode with a solid background
3415      * color and a callback object.
3416      */
3417     public boolean drawImage(Image img, int x, int y, Color bg,
3418                              ImageObserver observer) {
3419 
3420         if (img == null) {
3421             return true;
3422         }
3423 
3424         final int imgW = img.getWidth(null);
3425         final int imgH = img.getHeight(null);
3426         Boolean hidpiImageDrawn = drawHiDPIImage(img, x, y, x + imgW, y + imgH,
3427                                                  0, 0, imgW, imgH, bg, observer,
3428                                                  null);
3429         if (hidpiImageDrawn != null) {
3430             return hidpiImageDrawn;
3431         }
3432 
3433         try {
3434             return imagepipe.copyImage(this, img, x, y, bg, observer);
3435         } catch (InvalidPipeException e) {
3436             try {
3437                 revalidateAll();
3438                 return imagepipe.copyImage(this, img, x, y, bg, observer);
3439             } catch (InvalidPipeException e2) {
3440                 // Still catching the exception; we are not yet ready to
3441                 // validate the surfaceData correctly.  Fail for now and
3442                 // try again next time around.
3443                 return false;
3444             }
3445         } finally {
3446             surfaceData.markDirty();
3447         }
3448     }
3449 
3450     /**
3451      * Draws a subrectangle of an image scaled to a destination rectangle
3452      * in nonblocking mode with a callback object.
3453      */
3454     public boolean drawImage(Image img,
3455                              int dx1, int dy1, int dx2, int dy2,
3456                              int sx1, int sy1, int sx2, int sy2,
3457                              ImageObserver observer) {
3458         return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null,
3459                          observer);
3460     }
3461 
3462     /**
3463      * Draws a subrectangle of an image scaled to a destination rectangle in
3464      * nonblocking mode with a solid background color and a callback object.
3465      */
3466     public boolean drawImage(Image img,
3467                              int dx1, int dy1, int dx2, int dy2,
3468                              int sx1, int sy1, int sx2, int sy2,
3469                              Color bgcolor, ImageObserver observer) {
3470 
3471         if (img == null) {
3472             return true;
3473         }
3474 
3475         if (dx1 == dx2 || dy1 == dy2 ||
3476             sx1 == sx2 || sy1 == sy2)
3477         {
3478             return true;
3479         }
3480 
3481         Boolean hidpiImageDrawn = drawHiDPIImage(img, dx1, dy1, dx2, dy2,
3482                                                  sx1, sy1, sx2, sy2,
3483                                                  bgcolor, observer, null);
3484 
3485         if (hidpiImageDrawn != null) {
3486             return hidpiImageDrawn;
3487         }
3488 
3489         if (((sx2 - sx1) == (dx2 - dx1)) &&
3490             ((sy2 - sy1) == (dy2 - dy1)))
3491         {
3492             // Not a scale - forward it to a copy routine
3493             int srcX, srcY, dstX, dstY, width, height;
3494             if (sx2 > sx1) {
3495                 width = sx2 - sx1;
3496                 srcX = sx1;
3497                 dstX = dx1;
3498             } else {
3499                 width = sx1 - sx2;
3500                 srcX = sx2;
3501                 dstX = dx2;
3502             }
3503             if (sy2 > sy1) {
3504                 height = sy2-sy1;
3505                 srcY = sy1;
3506                 dstY = dy1;
3507             } else {
3508                 height = sy1-sy2;
3509                 srcY = sy2;
3510                 dstY = dy2;
3511             }
3512             return copyImage(img, dstX, dstY, srcX, srcY,
3513                              width, height, bgcolor, observer);
3514         }
3515 
3516         try {
3517             return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,
3518                                           sx1, sy1, sx2, sy2, bgcolor,
3519                                           observer);
3520         } catch (InvalidPipeException e) {
3521             try {
3522                 revalidateAll();
3523                 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,
3524                                               sx1, sy1, sx2, sy2, bgcolor,
3525                                               observer);
3526             } catch (InvalidPipeException e2) {
3527                 // Still catching the exception; we are not yet ready to
3528                 // validate the surfaceData correctly.  Fail for now and
3529                 // try again next time around.
3530                 return false;
3531             }
3532         } finally {
3533             surfaceData.markDirty();
3534         }
3535     }
3536 
3537     /**
3538      * Draw an image, applying a transform from image space into user space
3539      * before drawing.
3540      * The transformation from user space into device space is done with
3541      * the current transform in the Graphics2D.
3542      * The given transformation is applied to the image before the
3543      * transform attribute in the Graphics2D state is applied.
3544      * The rendering attributes applied include the clip, transform,
3545      * paint or color and composite attributes. Note that the result is
3546      * undefined, if the given transform is non-invertible.
3547      * @param img The image to be drawn.
3548      * @param xform The transformation from image space into user space.
3549      * @param observer The image observer to be notified on the image producing
3550      * progress.
3551      * @see #transform
3552      * @see #setComposite
3553      * @see #setClip
3554      */
3555     public boolean drawImage(Image img,
3556                              AffineTransform xform,
3557                              ImageObserver observer) {
3558 
3559         if (img == null) {
3560             return true;
3561         }
3562 
3563         if (xform == null || xform.isIdentity()) {
3564             return drawImage(img, 0, 0, null, observer);
3565         }
3566 
3567         final int w = img.getWidth(null);
3568         final int h = img.getHeight(null);
3569         Boolean hidpiImageDrawn = drawHiDPIImage(img, 0, 0, w, h, 0, 0, w, h,
3570                                                  null, observer, xform);
3571 
3572         if (hidpiImageDrawn != null) {
3573             return hidpiImageDrawn;
3574         }
3575 
3576         return transformImage(img, xform, observer);
3577     }
3578 
3579     public void drawImage(BufferedImage bImg,
3580                           BufferedImageOp op,
3581                           int x,
3582                           int y)  {
3583 
3584         if (bImg == null) {
3585             return;
3586         }
3587 
3588         try {
3589             imagepipe.transformImage(this, bImg, op, x, y);
3590         } catch (InvalidPipeException e) {
3591             try {
3592                 revalidateAll();
3593                 imagepipe.transformImage(this, bImg, op, x, y);
3594             } catch (InvalidPipeException e2) {
3595                 // Still catching the exception; we are not yet ready to
3596                 // validate the surfaceData correctly.  Fail for now and
3597                 // try again next time around.
3598             }
3599         } finally {
3600             surfaceData.markDirty();
3601         }
3602     }
3603 
3604     /**
3605     * Get the rendering context of the font
3606     * within this Graphics2D context.
3607     */
3608     public FontRenderContext getFontRenderContext() {
3609         if (cachedFRC == null) {
3610             int aahint = textAntialiasHint;
3611             if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT &&
3612                 antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
3613                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
3614             }
3615             // Translation components should be excluded from the FRC transform
3616             AffineTransform tx = null;
3617             if (transformState >= TRANSFORM_TRANSLATESCALE) {
3618                 if (transform.getTranslateX() == 0 &&
3619                     transform.getTranslateY() == 0) {
3620                     tx = transform;
3621                 } else {
3622                     tx = new AffineTransform(transform.getScaleX(),
3623                                              transform.getShearY(),
3624                                              transform.getShearX(),
3625                                              transform.getScaleY(),
3626                                              0, 0);
3627                 }
3628             }
3629             cachedFRC = new FontRenderContext(tx,
3630              SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, aahint),
3631              SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
3632                                 fractionalMetricsHint));
3633         }
3634         return cachedFRC;
3635     }
3636     private FontRenderContext cachedFRC;
3637 
3638     /**
3639      * This object has no resources to dispose of per se, but the
3640      * doc comments for the base method in java.awt.Graphics imply
3641      * that this object will not be useable after it is disposed.
3642      * So, we sabotage the object to prevent further use to prevent
3643      * developers from relying on behavior that may not work on
3644      * other, less forgiving, VMs that really need to dispose of
3645      * resources.
3646      */
3647     public void dispose() {
3648         surfaceData = NullSurfaceData.theInstance;
3649         invalidatePipe();
3650     }
3651 
3652     /**
3653      * Graphics has a finalize method that automatically calls dispose()
3654      * for subclasses.  For SunGraphics2D we do not need to be finalized
3655      * so that method simply causes us to be enqueued on the Finalizer
3656      * queues for no good reason.  Unfortunately, that method and
3657      * implementation are now considered part of the public contract
3658      * of that base class so we can not remove or gut the method.
3659      * We override it here with an empty method and the VM is smart
3660      * enough to know that if our override is empty then it should not
3661      * mark us as finalizeable.
3662      */
3663     @SuppressWarnings("deprecation")
3664     public void finalize() {
3665         // DO NOT REMOVE THIS METHOD
3666     }
3667 
3668     /**
3669      * Returns destination that this Graphics renders to.  This could be
3670      * either an Image or a Component; subclasses of SurfaceData are
3671      * responsible for returning the appropriate object.
3672      */
3673     public Object getDestination() {
3674         return surfaceData.getDestination();
3675     }
3676 
3677     /**
3678      * {@inheritDoc}
3679      *
3680      * @see sun.java2d.DestSurfaceProvider#getDestSurface
3681      */
3682     @Override
3683     public Surface getDestSurface() {
3684         return surfaceData;
3685     }
3686 }