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