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