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