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