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