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

Save This Page
Home » openjdk-7 » sun » java2d » [javadoc | source]