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