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