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