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