SunGraphics2D.java revision 13452:bc135ab66df9
1/*
2 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
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            if (usrClip instanceof Rectangle) {
1906                clipRegion = devClip.getIntersection((Rectangle)usrClip);
1907            } else {
1908                clipRegion = devClip.getIntersection(usrClip.getBounds());
1909            }
1910        } else {
1911            PathIterator cpi = usrClip.getPathIterator(null);
1912            int box[] = new int[4];
1913            ShapeSpanIterator sr = LoopPipe.getFillSSI(this);
1914            try {
1915                sr.setOutputArea(devClip);
1916                sr.appendPath(cpi);
1917                sr.getPathBox(box);
1918                Region r = Region.getInstance(box);
1919                r.appendSpans(sr);
1920                clipRegion = r;
1921                clipState =
1922                    r.isRectangular() ? CLIP_RECTANGULAR : CLIP_SHAPE;
1923            } finally {
1924                sr.dispose();
1925            }
1926        }
1927        if (origClipState != clipState &&
1928            (clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE))
1929        {
1930            validFontInfo = false;
1931            invalidatePipe();
1932        }
1933    }
1934
1935    static final int NON_RECTILINEAR_TRANSFORM_MASK =
1936        (AffineTransform.TYPE_GENERAL_TRANSFORM |
1937         AffineTransform.TYPE_GENERAL_ROTATION);
1938
1939    protected Shape transformShape(Shape s) {
1940        if (s == null) {
1941            return null;
1942        }
1943        if (transformState > TRANSFORM_INT_TRANSLATE) {
1944            return transformShape(transform, s);
1945        } else {
1946            return transformShape(transX, transY, s);
1947        }
1948    }
1949
1950    public Shape untransformShape(Shape s) {
1951        if (s == null) {
1952            return null;
1953        }
1954        if (transformState > TRANSFORM_INT_TRANSLATE) {
1955            try {
1956                return transformShape(transform.createInverse(), s);
1957            } catch (NoninvertibleTransformException e) {
1958                return null;
1959            }
1960        } else {
1961            return transformShape(-transX, -transY, s);
1962        }
1963    }
1964
1965    protected static Shape transformShape(int tx, int ty, Shape s) {
1966        if (s == null) {
1967            return null;
1968        }
1969
1970        if (s instanceof Rectangle) {
1971            Rectangle r = s.getBounds();
1972            r.translate(tx, ty);
1973            return r;
1974        }
1975        if (s instanceof Rectangle2D) {
1976            Rectangle2D rect = (Rectangle2D) s;
1977            return new Rectangle2D.Double(rect.getX() + tx,
1978                                          rect.getY() + ty,
1979                                          rect.getWidth(),
1980                                          rect.getHeight());
1981        }
1982
1983        if (tx == 0 && ty == 0) {
1984            return cloneShape(s);
1985        }
1986
1987        AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty);
1988        return mat.createTransformedShape(s);
1989    }
1990
1991    protected static Shape transformShape(AffineTransform tx, Shape clip) {
1992        if (clip == null) {
1993            return null;
1994        }
1995
1996        if (clip instanceof Rectangle2D &&
1997            (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0)
1998        {
1999            Rectangle2D rect = (Rectangle2D) clip;
2000            double matrix[] = new double[4];
2001            matrix[0] = rect.getX();
2002            matrix[1] = rect.getY();
2003            matrix[2] = matrix[0] + rect.getWidth();
2004            matrix[3] = matrix[1] + rect.getHeight();
2005            tx.transform(matrix, 0, matrix, 0, 2);
2006            fixRectangleOrientation(matrix, rect);
2007            return new Rectangle2D.Double(matrix[0], matrix[1],
2008                                          matrix[2] - matrix[0],
2009                                          matrix[3] - matrix[1]);
2010        }
2011
2012        if (tx.isIdentity()) {
2013            return cloneShape(clip);
2014        }
2015
2016        return tx.createTransformedShape(clip);
2017    }
2018
2019    /**
2020     * Sets orientation of the rectangle according to the clip.
2021     */
2022    private static void fixRectangleOrientation(double[] m, Rectangle2D clip) {
2023        if (clip.getWidth() > 0 != (m[2] - m[0] > 0)) {
2024            double t = m[0];
2025            m[0] = m[2];
2026            m[2] = t;
2027        }
2028        if (clip.getHeight() > 0 != (m[3] - m[1] > 0)) {
2029            double t = m[1];
2030            m[1] = m[3];
2031            m[3] = t;
2032        }
2033    }
2034
2035    public void clipRect(int x, int y, int w, int h) {
2036        clip(new Rectangle(x, y, w, h));
2037    }
2038
2039    public void setClip(int x, int y, int w, int h) {
2040        setClip(new Rectangle(x, y, w, h));
2041    }
2042
2043    public Shape getClip() {
2044        return untransformShape(usrClip);
2045    }
2046
2047    public void setClip(Shape sh) {
2048        usrClip = transformShape(sh);
2049        validateCompClip();
2050    }
2051
2052    /**
2053     * Intersects the current clip with the specified Path and sets the
2054     * current clip to the resulting intersection. The clip is transformed
2055     * with the current transform in the Graphics2D state before being
2056     * intersected with the current clip. This method is used to make the
2057     * current clip smaller. To make the clip larger, use any setClip method.
2058     * @param s The Path to be intersected with the current clip.
2059     */
2060    public void clip(Shape s) {
2061        s = transformShape(s);
2062        if (usrClip != null) {
2063            s = intersectShapes(usrClip, s, true, true);
2064        }
2065        usrClip = s;
2066        validateCompClip();
2067    }
2068
2069    public void setPaintMode() {
2070        setComposite(AlphaComposite.SrcOver);
2071    }
2072
2073    public void setXORMode(Color c) {
2074        if (c == null) {
2075            throw new IllegalArgumentException("null XORColor");
2076        }
2077        setComposite(new XORComposite(c, surfaceData));
2078    }
2079
2080    Blit lastCAblit;
2081    Composite lastCAcomp;
2082
2083    public void copyArea(int x, int y, int w, int h, int dx, int dy) {
2084        try {
2085            doCopyArea(x, y, w, h, dx, dy);
2086        } catch (InvalidPipeException e) {
2087            try {
2088                revalidateAll();
2089                doCopyArea(x, y, w, h, dx, dy);
2090            } catch (InvalidPipeException e2) {
2091                // Still catching the exception; we are not yet ready to
2092                // validate the surfaceData correctly.  Fail for now and
2093                // try again next time around.
2094            }
2095        } finally {
2096            surfaceData.markDirty();
2097        }
2098    }
2099
2100    private void doCopyArea(int x, int y, int w, int h, int dx, int dy) {
2101        if (w <= 0 || h <= 0) {
2102            return;
2103        }
2104        SurfaceData theData = surfaceData;
2105        if (theData.copyArea(this, x, y, w, h, dx, dy)) {
2106            return;
2107        }
2108        if (transformState > TRANSFORM_TRANSLATESCALE) {
2109            throw new InternalError("transformed copyArea not implemented yet");
2110        }
2111        // REMIND: This method does not deal with missing data from the
2112        // source object (i.e. it does not send exposure events...)
2113
2114        Region clip = getCompClip();
2115
2116        Composite comp = composite;
2117        if (lastCAcomp != comp) {
2118            SurfaceType dsttype = theData.getSurfaceType();
2119            CompositeType comptype = imageComp;
2120            if (CompositeType.SrcOverNoEa.equals(comptype) &&
2121                theData.getTransparency() == Transparency.OPAQUE)
2122            {
2123                comptype = CompositeType.SrcNoEa;
2124            }
2125            lastCAblit = Blit.locate(dsttype, comptype, dsttype);
2126            lastCAcomp = comp;
2127        }
2128
2129        double[] coords = {x, y, x + w, y + h, x + dx, y + dy};
2130        transform.transform(coords, 0, coords, 0, 3);
2131
2132        x = (int)Math.ceil(coords[0] - 0.5);
2133        y = (int)Math.ceil(coords[1] - 0.5);
2134        w = ((int)Math.ceil(coords[2] - 0.5)) - x;
2135        h = ((int)Math.ceil(coords[3] - 0.5)) - y;
2136        dx = ((int)Math.ceil(coords[4] - 0.5)) - x;
2137        dy = ((int)Math.ceil(coords[5] - 0.5)) - y;
2138
2139        // In case of negative scale transform, reflect the rect coords.
2140        if (w < 0) {
2141            w *= -1;
2142            x -= w;
2143        }
2144        if (h < 0) {
2145            h *= -1;
2146            y -= h;
2147        }
2148
2149        Blit ob = lastCAblit;
2150        if (dy == 0 && dx > 0 && dx < w) {
2151            while (w > 0) {
2152                int partW = Math.min(w, dx);
2153                w -= partW;
2154                int sx = x + w;
2155                ob.Blit(theData, theData, comp, clip,
2156                        sx, y, sx+dx, y+dy, partW, h);
2157            }
2158            return;
2159        }
2160        if (dy > 0 && dy < h && dx > -w && dx < w) {
2161            while (h > 0) {
2162                int partH = Math.min(h, dy);
2163                h -= partH;
2164                int sy = y + h;
2165                ob.Blit(theData, theData, comp, clip,
2166                        x, sy, x+dx, sy+dy, w, partH);
2167            }
2168            return;
2169        }
2170        ob.Blit(theData, theData, comp, clip, x, y, x+dx, y+dy, w, h);
2171    }
2172
2173    /*
2174    public void XcopyArea(int x, int y, int w, int h, int dx, int dy) {
2175        Rectangle rect = new Rectangle(x, y, w, h);
2176        rect = transformBounds(rect, transform);
2177        Point2D    point = new Point2D.Float(dx, dy);
2178        Point2D    root  = new Point2D.Float(0, 0);
2179        point = transform.transform(point, point);
2180        root  = transform.transform(root, root);
2181        int fdx = (int)(point.getX()-root.getX());
2182        int fdy = (int)(point.getY()-root.getY());
2183
2184        Rectangle r = getCompBounds().intersection(rect.getBounds());
2185
2186        if (r.isEmpty()) {
2187            return;
2188        }
2189
2190        // Begin Rasterizer for Clip Shape
2191        boolean skipClip = true;
2192        byte[] clipAlpha = null;
2193
2194        if (clipState == CLIP_SHAPE) {
2195
2196            int box[] = new int[4];
2197
2198            clipRegion.getBounds(box);
2199            Rectangle devR = new Rectangle(box[0], box[1],
2200                                           box[2] - box[0],
2201                                           box[3] - box[1]);
2202            if (!devR.isEmpty()) {
2203                OutputManager mgr = getOutputManager();
2204                RegionIterator ri = clipRegion.getIterator();
2205                while (ri.nextYRange(box)) {
2206                    int spany = box[1];
2207                    int spanh = box[3] - spany;
2208                    while (ri.nextXBand(box)) {
2209                        int spanx = box[0];
2210                        int spanw = box[2] - spanx;
2211                        mgr.copyArea(this, null,
2212                                     spanw, 0,
2213                                     spanx, spany,
2214                                     spanw, spanh,
2215                                     fdx, fdy,
2216                                     null);
2217                    }
2218                }
2219            }
2220            return;
2221        }
2222        // End Rasterizer for Clip Shape
2223
2224        getOutputManager().copyArea(this, null,
2225                                    r.width, 0,
2226                                    r.x, r.y, r.width,
2227                                    r.height, fdx, fdy,
2228                                    null);
2229    }
2230    */
2231
2232    public void drawLine(int x1, int y1, int x2, int y2) {
2233        try {
2234            drawpipe.drawLine(this, x1, y1, x2, y2);
2235        } catch (InvalidPipeException e) {
2236            try {
2237                revalidateAll();
2238                drawpipe.drawLine(this, x1, y1, x2, y2);
2239            } catch (InvalidPipeException e2) {
2240                // Still catching the exception; we are not yet ready to
2241                // validate the surfaceData correctly.  Fail for now and
2242                // try again next time around.
2243            }
2244        } finally {
2245            surfaceData.markDirty();
2246        }
2247    }
2248
2249    public void drawRoundRect(int x, int y, int w, int h, int arcW, int arcH) {
2250        try {
2251            drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);
2252        } catch (InvalidPipeException e) {
2253            try {
2254                revalidateAll();
2255                drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);
2256            } catch (InvalidPipeException e2) {
2257                // Still catching the exception; we are not yet ready to
2258                // validate the surfaceData correctly.  Fail for now and
2259                // try again next time around.
2260            }
2261        } finally {
2262            surfaceData.markDirty();
2263        }
2264    }
2265
2266    public void fillRoundRect(int x, int y, int w, int h, int arcW, int arcH) {
2267        try {
2268            fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);
2269        } catch (InvalidPipeException e) {
2270            try {
2271                revalidateAll();
2272                fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);
2273            } catch (InvalidPipeException e2) {
2274                // Still catching the exception; we are not yet ready to
2275                // validate the surfaceData correctly.  Fail for now and
2276                // try again next time around.
2277            }
2278        } finally {
2279            surfaceData.markDirty();
2280        }
2281    }
2282
2283    public void drawOval(int x, int y, int w, int h) {
2284        try {
2285            drawpipe.drawOval(this, x, y, w, h);
2286        } catch (InvalidPipeException e) {
2287            try {
2288                revalidateAll();
2289                drawpipe.drawOval(this, x, y, w, h);
2290            } catch (InvalidPipeException e2) {
2291                // Still catching the exception; we are not yet ready to
2292                // validate the surfaceData correctly.  Fail for now and
2293                // try again next time around.
2294            }
2295        } finally {
2296            surfaceData.markDirty();
2297        }
2298    }
2299
2300    public void fillOval(int x, int y, int w, int h) {
2301        try {
2302            fillpipe.fillOval(this, x, y, w, h);
2303        } catch (InvalidPipeException e) {
2304            try {
2305                revalidateAll();
2306                fillpipe.fillOval(this, x, y, w, h);
2307            } catch (InvalidPipeException e2) {
2308                // Still catching the exception; we are not yet ready to
2309                // validate the surfaceData correctly.  Fail for now and
2310                // try again next time around.
2311            }
2312        } finally {
2313            surfaceData.markDirty();
2314        }
2315    }
2316
2317    public void drawArc(int x, int y, int w, int h,
2318                        int startAngl, int arcAngl) {
2319        try {
2320            drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);
2321        } catch (InvalidPipeException e) {
2322            try {
2323                revalidateAll();
2324                drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);
2325            } catch (InvalidPipeException e2) {
2326                // Still catching the exception; we are not yet ready to
2327                // validate the surfaceData correctly.  Fail for now and
2328                // try again next time around.
2329            }
2330        } finally {
2331            surfaceData.markDirty();
2332        }
2333    }
2334
2335    public void fillArc(int x, int y, int w, int h,
2336                        int startAngl, int arcAngl) {
2337        try {
2338            fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);
2339        } catch (InvalidPipeException e) {
2340            try {
2341                revalidateAll();
2342                fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);
2343            } catch (InvalidPipeException e2) {
2344                // Still catching the exception; we are not yet ready to
2345                // validate the surfaceData correctly.  Fail for now and
2346                // try again next time around.
2347            }
2348        } finally {
2349            surfaceData.markDirty();
2350        }
2351    }
2352
2353    public void drawPolyline(int xPoints[], int yPoints[], int nPoints) {
2354        try {
2355            drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);
2356        } catch (InvalidPipeException e) {
2357            try {
2358                revalidateAll();
2359                drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);
2360            } catch (InvalidPipeException e2) {
2361                // Still catching the exception; we are not yet ready to
2362                // validate the surfaceData correctly.  Fail for now and
2363                // try again next time around.
2364            }
2365        } finally {
2366            surfaceData.markDirty();
2367        }
2368    }
2369
2370    public void drawPolygon(int xPoints[], int yPoints[], int nPoints) {
2371        try {
2372            drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);
2373        } catch (InvalidPipeException e) {
2374            try {
2375                revalidateAll();
2376                drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);
2377            } catch (InvalidPipeException e2) {
2378                // Still catching the exception; we are not yet ready to
2379                // validate the surfaceData correctly.  Fail for now and
2380                // try again next time around.
2381            }
2382        } finally {
2383            surfaceData.markDirty();
2384        }
2385    }
2386
2387    public void fillPolygon(int xPoints[], int yPoints[], int nPoints) {
2388        try {
2389            fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);
2390        } catch (InvalidPipeException e) {
2391            try {
2392                revalidateAll();
2393                fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);
2394            } catch (InvalidPipeException e2) {
2395                // Still catching the exception; we are not yet ready to
2396                // validate the surfaceData correctly.  Fail for now and
2397                // try again next time around.
2398            }
2399        } finally {
2400            surfaceData.markDirty();
2401        }
2402    }
2403
2404    public void drawRect (int x, int y, int w, int h) {
2405        try {
2406            drawpipe.drawRect(this, x, y, w, h);
2407        } catch (InvalidPipeException e) {
2408            try {
2409                revalidateAll();
2410                drawpipe.drawRect(this, x, y, w, h);
2411            } catch (InvalidPipeException e2) {
2412                // Still catching the exception; we are not yet ready to
2413                // validate the surfaceData correctly.  Fail for now and
2414                // try again next time around.
2415            }
2416        } finally {
2417            surfaceData.markDirty();
2418        }
2419    }
2420
2421    public void fillRect (int x, int y, int w, int h) {
2422        try {
2423            fillpipe.fillRect(this, x, y, w, h);
2424        } catch (InvalidPipeException e) {
2425            try {
2426                revalidateAll();
2427                fillpipe.fillRect(this, x, y, w, h);
2428            } catch (InvalidPipeException e2) {
2429                // Still catching the exception; we are not yet ready to
2430                // validate the surfaceData correctly.  Fail for now and
2431                // try again next time around.
2432            }
2433        } finally {
2434            surfaceData.markDirty();
2435        }
2436    }
2437
2438    private void revalidateAll() {
2439        try {
2440            // REMIND: This locking needs to be done around the
2441            // caller of this method so that the pipe stays valid
2442            // long enough to call the new primitive.
2443            // REMIND: No locking yet in screen SurfaceData objects!
2444            // surfaceData.lock();
2445            surfaceData = surfaceData.getReplacement();
2446            if (surfaceData == null) {
2447                surfaceData = NullSurfaceData.theInstance;
2448            }
2449
2450            invalidatePipe();
2451
2452            // this will recalculate the composite clip
2453            setDevClip(surfaceData.getBounds());
2454
2455            if (paintState <= PAINT_ALPHACOLOR) {
2456                validateColor();
2457            }
2458            if (composite instanceof XORComposite) {
2459                Color c = ((XORComposite) composite).getXorColor();
2460                setComposite(new XORComposite(c, surfaceData));
2461            }
2462            validatePipe();
2463        } finally {
2464            // REMIND: No locking yet in screen SurfaceData objects!
2465            // surfaceData.unlock();
2466        }
2467    }
2468
2469    public void clearRect(int x, int y, int w, int h) {
2470        // REMIND: has some "interesting" consequences if threads are
2471        // not synchronized
2472        Composite c = composite;
2473        Paint p = paint;
2474        setComposite(AlphaComposite.Src);
2475        setColor(getBackground());
2476        fillRect(x, y, w, h);
2477        setPaint(p);
2478        setComposite(c);
2479    }
2480
2481    /**
2482     * Strokes the outline of a Path using the settings of the current
2483     * graphics state.  The rendering attributes applied include the
2484     * clip, transform, paint or color, composite and stroke attributes.
2485     * @param s The path to be drawn.
2486     * @see #setStroke
2487     * @see #setPaint
2488     * @see java.awt.Graphics#setColor
2489     * @see #transform
2490     * @see #setTransform
2491     * @see #clip
2492     * @see #setClip
2493     * @see #setComposite
2494     */
2495    public void draw(Shape s) {
2496        try {
2497            shapepipe.draw(this, s);
2498        } catch (InvalidPipeException e) {
2499            try {
2500                revalidateAll();
2501                shapepipe.draw(this, s);
2502            } catch (InvalidPipeException e2) {
2503                // Still catching the exception; we are not yet ready to
2504                // validate the surfaceData correctly.  Fail for now and
2505                // try again next time around.
2506            }
2507        } finally {
2508            surfaceData.markDirty();
2509        }
2510    }
2511
2512
2513    /**
2514     * Fills the interior of a Path using the settings of the current
2515     * graphics state. The rendering attributes applied include the
2516     * clip, transform, paint or color, and composite.
2517     * @see #setPaint
2518     * @see java.awt.Graphics#setColor
2519     * @see #transform
2520     * @see #setTransform
2521     * @see #setComposite
2522     * @see #clip
2523     * @see #setClip
2524     */
2525    public void fill(Shape s) {
2526        try {
2527            shapepipe.fill(this, s);
2528        } catch (InvalidPipeException e) {
2529            try {
2530                revalidateAll();
2531                shapepipe.fill(this, s);
2532            } catch (InvalidPipeException e2) {
2533                // Still catching the exception; we are not yet ready to
2534                // validate the surfaceData correctly.  Fail for now and
2535                // try again next time around.
2536            }
2537        } finally {
2538            surfaceData.markDirty();
2539        }
2540    }
2541
2542    /**
2543     * Returns true if the given AffineTransform is an integer
2544     * translation.
2545     */
2546    private static boolean isIntegerTranslation(AffineTransform xform) {
2547        if (xform.isIdentity()) {
2548            return true;
2549        }
2550        if (xform.getType() == AffineTransform.TYPE_TRANSLATION) {
2551            double tx = xform.getTranslateX();
2552            double ty = xform.getTranslateY();
2553            return (tx == (int)tx && ty == (int)ty);
2554        }
2555        return false;
2556    }
2557
2558    /**
2559     * Returns the index of the tile corresponding to the supplied position
2560     * given the tile grid offset and size along the same axis.
2561     */
2562    private static int getTileIndex(int p, int tileGridOffset, int tileSize) {
2563        p -= tileGridOffset;
2564        if (p < 0) {
2565            p += 1 - tileSize;          // force round to -infinity (ceiling)
2566        }
2567        return p/tileSize;
2568    }
2569
2570    /**
2571     * Returns a rectangle in image coordinates that may be required
2572     * in order to draw the given image into the given clipping region
2573     * through a pair of AffineTransforms.  In addition, horizontal and
2574     * vertical padding factors for antialising and interpolation may
2575     * be used.
2576     */
2577    private static Rectangle getImageRegion(RenderedImage img,
2578                                            Region compClip,
2579                                            AffineTransform transform,
2580                                            AffineTransform xform,
2581                                            int padX, int padY) {
2582        Rectangle imageRect =
2583            new Rectangle(img.getMinX(), img.getMinY(),
2584                          img.getWidth(), img.getHeight());
2585
2586        Rectangle result = null;
2587        try {
2588            double p[] = new double[8];
2589            p[0] = p[2] = compClip.getLoX();
2590            p[4] = p[6] = compClip.getHiX();
2591            p[1] = p[5] = compClip.getLoY();
2592            p[3] = p[7] = compClip.getHiY();
2593
2594            // Inverse transform the output bounding rect
2595            transform.inverseTransform(p, 0, p, 0, 4);
2596            xform.inverseTransform(p, 0, p, 0, 4);
2597
2598            // Determine a bounding box for the inverse transformed region
2599            double x0,x1,y0,y1;
2600            x0 = x1 = p[0];
2601            y0 = y1 = p[1];
2602
2603            for (int i = 2; i < 8; ) {
2604                double pt = p[i++];
2605                if (pt < x0)  {
2606                    x0 = pt;
2607                } else if (pt > x1) {
2608                    x1 = pt;
2609                }
2610                pt = p[i++];
2611                if (pt < y0)  {
2612                    y0 = pt;
2613                } else if (pt > y1) {
2614                    y1 = pt;
2615                }
2616            }
2617
2618            // This is padding for anti-aliasing and such.  It may
2619            // be more than is needed.
2620            int x = (int)x0 - padX;
2621            int w = (int)(x1 - x0 + 2*padX);
2622            int y = (int)y0 - padY;
2623            int h = (int)(y1 - y0 + 2*padY);
2624
2625            Rectangle clipRect = new Rectangle(x,y,w,h);
2626            result = clipRect.intersection(imageRect);
2627        } catch (NoninvertibleTransformException nte) {
2628            // Worst case bounds are the bounds of the image.
2629            result = imageRect;
2630        }
2631
2632        return result;
2633    }
2634
2635    /**
2636     * Draws an image, applying a transform from image space into user space
2637     * before drawing.
2638     * The transformation from user space into device space is done with
2639     * the current transform in the Graphics2D.
2640     * The given transformation is applied to the image before the
2641     * transform attribute in the Graphics2D state is applied.
2642     * The rendering attributes applied include the clip, transform,
2643     * and composite attributes. Note that the result is
2644     * undefined, if the given transform is noninvertible.
2645     * @param img The image to be drawn. Does nothing if img is null.
2646     * @param xform The transformation from image space into user space.
2647     * @see #transform
2648     * @see #setTransform
2649     * @see #setComposite
2650     * @see #clip
2651     * @see #setClip
2652     */
2653    public void drawRenderedImage(RenderedImage img,
2654                                  AffineTransform xform) {
2655
2656        if (img == null) {
2657            return;
2658        }
2659
2660        // BufferedImage case: use a simple drawImage call
2661        if (img instanceof BufferedImage) {
2662            BufferedImage bufImg = (BufferedImage)img;
2663            drawImage(bufImg,xform,null);
2664            return;
2665        }
2666
2667        // transformState tracks the state of transform and
2668        // transX, transY contain the integer casts of the
2669        // translation factors
2670        boolean isIntegerTranslate =
2671            (transformState <= TRANSFORM_INT_TRANSLATE) &&
2672            isIntegerTranslation(xform);
2673
2674        // Include padding for interpolation/antialiasing if necessary
2675        int pad = isIntegerTranslate ? 0 : 3;
2676
2677        Region clip;
2678        try {
2679            clip = getCompClip();
2680        } catch (InvalidPipeException e) {
2681            return;
2682        }
2683
2684        // Determine the region of the image that may contribute to
2685        // the clipped drawing area
2686        Rectangle region = getImageRegion(img,
2687                                          clip,
2688                                          transform,
2689                                          xform,
2690                                          pad, pad);
2691        if (region.width <= 0 || region.height <= 0) {
2692            return;
2693        }
2694
2695        // Attempt to optimize integer translation of tiled images.
2696        // Although theoretically we are O.K. if the concatenation of
2697        // the user transform and the device transform is an integer
2698        // translation, we'll play it safe and only optimize the case
2699        // where both are integer translations.
2700        if (isIntegerTranslate) {
2701            // Use optimized code
2702            // Note that drawTranslatedRenderedImage calls copyImage
2703            // which takes the user space to device space transform into
2704            // account, but we need to provide the image space to user space
2705            // translations.
2706
2707            drawTranslatedRenderedImage(img, region,
2708                                        (int) xform.getTranslateX(),
2709                                        (int) xform.getTranslateY());
2710            return;
2711        }
2712
2713        // General case: cobble the necessary region into a single Raster
2714        Raster raster = img.getData(region);
2715
2716        // Make a new Raster with the same contents as raster
2717        // but starting at (0, 0).  This raster is thus in the same
2718        // coordinate system as the SampleModel of the original raster.
2719        WritableRaster wRaster =
2720              Raster.createWritableRaster(raster.getSampleModel(),
2721                                          raster.getDataBuffer(),
2722                                          null);
2723
2724        // If the original raster was in a different coordinate
2725        // system than its SampleModel, we need to perform an
2726        // additional translation in order to get the (minX, minY)
2727        // pixel of raster to be pixel (0, 0) of wRaster.  We also
2728        // have to have the correct width and height.
2729        int minX = raster.getMinX();
2730        int minY = raster.getMinY();
2731        int width = raster.getWidth();
2732        int height = raster.getHeight();
2733        int px = minX - raster.getSampleModelTranslateX();
2734        int py = minY - raster.getSampleModelTranslateY();
2735        if (px != 0 || py != 0 || width != wRaster.getWidth() ||
2736            height != wRaster.getHeight()) {
2737            wRaster =
2738                wRaster.createWritableChild(px,
2739                                            py,
2740                                            width,
2741                                            height,
2742                                            0, 0,
2743                                            null);
2744        }
2745
2746        // Now we have a BufferedImage starting at (0, 0)
2747        // with the same contents that started at (minX, minY)
2748        // in raster.  So we must draw the BufferedImage with a
2749        // translation of (minX, minY).
2750        AffineTransform transXform = (AffineTransform)xform.clone();
2751        transXform.translate(minX, minY);
2752
2753        ColorModel cm = img.getColorModel();
2754        BufferedImage bufImg = new BufferedImage(cm,
2755                                                 wRaster,
2756                                                 cm.isAlphaPremultiplied(),
2757                                                 null);
2758        drawImage(bufImg, transXform, null);
2759    }
2760
2761    /**
2762     * Intersects <code>destRect</code> with <code>clip</code> and
2763     * overwrites <code>destRect</code> with the result.
2764     * Returns false if the intersection was empty, true otherwise.
2765     */
2766    private boolean clipTo(Rectangle destRect, Rectangle clip) {
2767        int x1 = Math.max(destRect.x, clip.x);
2768        int x2 = Math.min(destRect.x + destRect.width, clip.x + clip.width);
2769        int y1 = Math.max(destRect.y, clip.y);
2770        int y2 = Math.min(destRect.y + destRect.height, clip.y + clip.height);
2771        if (((x2 - x1) < 0) || ((y2 - y1) < 0)) {
2772            destRect.width = -1; // Set both just to be safe
2773            destRect.height = -1;
2774            return false;
2775        } else {
2776            destRect.x = x1;
2777            destRect.y = y1;
2778            destRect.width = x2 - x1;
2779            destRect.height = y2 - y1;
2780            return true;
2781        }
2782    }
2783
2784    /**
2785     * Draw a portion of a RenderedImage tile-by-tile with a given
2786     * integer image to user space translation.  The user to
2787     * device transform must also be an integer translation.
2788     */
2789    private void drawTranslatedRenderedImage(RenderedImage img,
2790                                             Rectangle region,
2791                                             int i2uTransX,
2792                                             int i2uTransY) {
2793        // Cache tile grid info
2794        int tileGridXOffset = img.getTileGridXOffset();
2795        int tileGridYOffset = img.getTileGridYOffset();
2796        int tileWidth = img.getTileWidth();
2797        int tileHeight = img.getTileHeight();
2798
2799        // Determine the tile index extrema in each direction
2800        int minTileX =
2801            getTileIndex(region.x, tileGridXOffset, tileWidth);
2802        int minTileY =
2803            getTileIndex(region.y, tileGridYOffset, tileHeight);
2804        int maxTileX =
2805            getTileIndex(region.x + region.width - 1,
2806                         tileGridXOffset, tileWidth);
2807        int maxTileY =
2808            getTileIndex(region.y + region.height - 1,
2809                         tileGridYOffset, tileHeight);
2810
2811        // Create a single ColorModel to use for all BufferedImages
2812        ColorModel colorModel = img.getColorModel();
2813
2814        // Reuse the same Rectangle for each iteration
2815        Rectangle tileRect = new Rectangle();
2816
2817        for (int ty = minTileY; ty <= maxTileY; ty++) {
2818            for (int tx = minTileX; tx <= maxTileX; tx++) {
2819                // Get the current tile.
2820                Raster raster = img.getTile(tx, ty);
2821
2822                // Fill in tileRect with the tile bounds
2823                tileRect.x = tx*tileWidth + tileGridXOffset;
2824                tileRect.y = ty*tileHeight + tileGridYOffset;
2825                tileRect.width = tileWidth;
2826                tileRect.height = tileHeight;
2827
2828                // Clip the tile against the image bounds and
2829                // backwards mapped clip region
2830                // The result can't be empty
2831                clipTo(tileRect, region);
2832
2833                // Create a WritableRaster containing the tile
2834                WritableRaster wRaster = null;
2835                if (raster instanceof WritableRaster) {
2836                    wRaster = (WritableRaster)raster;
2837                } else {
2838                    // Create a WritableRaster in the same coordinate system
2839                    // as the original raster.
2840                    wRaster =
2841                        Raster.createWritableRaster(raster.getSampleModel(),
2842                                                    raster.getDataBuffer(),
2843                                                    null);
2844                }
2845
2846                // Translate wRaster to start at (0, 0) and to contain
2847                // only the relevent portion of the tile
2848                wRaster = wRaster.createWritableChild(tileRect.x, tileRect.y,
2849                                                      tileRect.width,
2850                                                      tileRect.height,
2851                                                      0, 0,
2852                                                      null);
2853
2854                // Wrap wRaster in a BufferedImage
2855                BufferedImage bufImg =
2856                    new BufferedImage(colorModel,
2857                                      wRaster,
2858                                      colorModel.isAlphaPremultiplied(),
2859                                      null);
2860                // Now we have a BufferedImage starting at (0, 0) that
2861                // represents data from a Raster starting at
2862                // (tileRect.x, tileRect.y).  Additionally, it needs
2863                // to be translated by (i2uTransX, i2uTransY).  We call
2864                // copyImage to draw just the region of interest
2865                // without needing to create a child image.
2866                copyImage(bufImg, tileRect.x + i2uTransX,
2867                          tileRect.y + i2uTransY, 0, 0, tileRect.width,
2868                          tileRect.height, null, null);
2869            }
2870        }
2871    }
2872
2873    public void drawRenderableImage(RenderableImage img,
2874                                    AffineTransform xform) {
2875
2876        if (img == null) {
2877            return;
2878        }
2879
2880        AffineTransform pipeTransform = transform;
2881        AffineTransform concatTransform = new AffineTransform(xform);
2882        concatTransform.concatenate(pipeTransform);
2883        AffineTransform reverseTransform;
2884
2885        RenderContext rc = new RenderContext(concatTransform);
2886
2887        try {
2888            reverseTransform = pipeTransform.createInverse();
2889        } catch (NoninvertibleTransformException nte) {
2890            rc = new RenderContext(pipeTransform);
2891            reverseTransform = new AffineTransform();
2892        }
2893
2894        RenderedImage rendering = img.createRendering(rc);
2895        drawRenderedImage(rendering,reverseTransform);
2896    }
2897
2898
2899
2900    /*
2901     * Transform the bounding box of the BufferedImage
2902     */
2903    protected Rectangle transformBounds(Rectangle rect,
2904                                        AffineTransform tx) {
2905        if (tx.isIdentity()) {
2906            return rect;
2907        }
2908
2909        Shape s = transformShape(tx, rect);
2910        return s.getBounds();
2911    }
2912
2913    // text rendering methods
2914    public void drawString(String str, int x, int y) {
2915        if (str == null) {
2916            throw new NullPointerException("String is null");
2917        }
2918
2919        if (font.hasLayoutAttributes()) {
2920            if (str.length() == 0) {
2921                return;
2922            }
2923            new TextLayout(str, font, getFontRenderContext()).draw(this, x, y);
2924            return;
2925        }
2926
2927        try {
2928            textpipe.drawString(this, str, x, y);
2929        } catch (InvalidPipeException e) {
2930            try {
2931                revalidateAll();
2932                textpipe.drawString(this, str, x, y);
2933            } catch (InvalidPipeException e2) {
2934                // Still catching the exception; we are not yet ready to
2935                // validate the surfaceData correctly.  Fail for now and
2936                // try again next time around.
2937            }
2938        } finally {
2939            surfaceData.markDirty();
2940        }
2941    }
2942
2943    public void drawString(String str, float x, float y) {
2944        if (str == null) {
2945            throw new NullPointerException("String is null");
2946        }
2947
2948        if (font.hasLayoutAttributes()) {
2949            if (str.length() == 0) {
2950                return;
2951            }
2952            new TextLayout(str, font, getFontRenderContext()).draw(this, x, y);
2953            return;
2954        }
2955
2956        try {
2957            textpipe.drawString(this, str, x, y);
2958        } catch (InvalidPipeException e) {
2959            try {
2960                revalidateAll();
2961                textpipe.drawString(this, str, x, y);
2962            } catch (InvalidPipeException e2) {
2963                // Still catching the exception; we are not yet ready to
2964                // validate the surfaceData correctly.  Fail for now and
2965                // try again next time around.
2966            }
2967        } finally {
2968            surfaceData.markDirty();
2969        }
2970    }
2971
2972    public void drawString(AttributedCharacterIterator iterator,
2973                           int x, int y) {
2974        if (iterator == null) {
2975            throw new NullPointerException("AttributedCharacterIterator is null");
2976        }
2977        if (iterator.getBeginIndex() == iterator.getEndIndex()) {
2978            return; /* nothing to draw */
2979        }
2980        TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2981        tl.draw(this, (float) x, (float) y);
2982    }
2983
2984    public void drawString(AttributedCharacterIterator iterator,
2985                           float x, float y) {
2986        if (iterator == null) {
2987            throw new NullPointerException("AttributedCharacterIterator is null");
2988        }
2989        if (iterator.getBeginIndex() == iterator.getEndIndex()) {
2990            return; /* nothing to draw */
2991        }
2992        TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2993        tl.draw(this, x, y);
2994    }
2995
2996    public void drawGlyphVector(GlyphVector gv, float x, float y)
2997    {
2998        if (gv == null) {
2999            throw new NullPointerException("GlyphVector is null");
3000        }
3001
3002        try {
3003            textpipe.drawGlyphVector(this, gv, x, y);
3004        } catch (InvalidPipeException e) {
3005            try {
3006                revalidateAll();
3007                textpipe.drawGlyphVector(this, gv, x, y);
3008            } catch (InvalidPipeException e2) {
3009                // Still catching the exception; we are not yet ready to
3010                // validate the surfaceData correctly.  Fail for now and
3011                // try again next time around.
3012            }
3013        } finally {
3014            surfaceData.markDirty();
3015        }
3016    }
3017
3018    public void drawChars(char data[], int offset, int length, int x, int y) {
3019
3020        if (data == null) {
3021            throw new NullPointerException("char data is null");
3022        }
3023        if (offset < 0 || length < 0 || offset + length > data.length) {
3024            throw new ArrayIndexOutOfBoundsException("bad offset/length");
3025        }
3026        if (font.hasLayoutAttributes()) {
3027            if (data.length == 0) {
3028                return;
3029            }
3030            new TextLayout(new String(data, offset, length),
3031                           font, getFontRenderContext()).draw(this, x, y);
3032            return;
3033        }
3034
3035        try {
3036            textpipe.drawChars(this, data, offset, length, x, y);
3037        } catch (InvalidPipeException e) {
3038            try {
3039                revalidateAll();
3040                textpipe.drawChars(this, data, offset, length, x, y);
3041            } catch (InvalidPipeException e2) {
3042                // Still catching the exception; we are not yet ready to
3043                // validate the surfaceData correctly.  Fail for now and
3044                // try again next time around.
3045            }
3046        } finally {
3047            surfaceData.markDirty();
3048        }
3049    }
3050
3051    public void drawBytes(byte data[], int offset, int length, int x, int y) {
3052        if (data == null) {
3053            throw new NullPointerException("byte data is null");
3054        }
3055        if (offset < 0 || length < 0 || offset + length > data.length) {
3056            throw new ArrayIndexOutOfBoundsException("bad offset/length");
3057        }
3058        /* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */
3059        char chData[] = new char[length];
3060        for (int i = length; i-- > 0; ) {
3061            chData[i] = (char)(data[i+offset] & 0xff);
3062        }
3063        if (font.hasLayoutAttributes()) {
3064            if (data.length == 0) {
3065                return;
3066            }
3067            new TextLayout(new String(chData),
3068                           font, getFontRenderContext()).draw(this, x, y);
3069            return;
3070        }
3071
3072        try {
3073            textpipe.drawChars(this, chData, 0, length, x, y);
3074        } catch (InvalidPipeException e) {
3075            try {
3076                revalidateAll();
3077                textpipe.drawChars(this, chData, 0, length, x, y);
3078            } catch (InvalidPipeException e2) {
3079                // Still catching the exception; we are not yet ready to
3080                // validate the surfaceData correctly.  Fail for now and
3081                // try again next time around.
3082            }
3083        } finally {
3084            surfaceData.markDirty();
3085        }
3086    }
3087// end of text rendering methods
3088
3089    private Boolean drawHiDPIImage(Image img,
3090                                   int dx1, int dy1, int dx2, int dy2,
3091                                   int sx1, int sy1, int sx2, int sy2,
3092                                   Color bgcolor, ImageObserver observer,
3093                                   AffineTransform xform) {
3094
3095        if (img instanceof VolatileImage) {
3096            final SurfaceData sd = SurfaceManager.getManager(img)
3097                    .getPrimarySurfaceData();
3098            final double scaleX = sd.getDefaultScaleX();
3099            final double scaleY = sd.getDefaultScaleY();
3100            if (scaleX == 1 && scaleY == 1) {
3101                return null;
3102            }
3103            sx1 = Region.clipScale(sx1, scaleX);
3104            sx2 = Region.clipScale(sx2, scaleX);
3105            sy1 = Region.clipScale(sy1, scaleY);
3106            sy2 = Region.clipScale(sy2, scaleY);
3107
3108            AffineTransform tx = null;
3109            if (xform != null) {
3110                tx = new AffineTransform(transform);
3111                transform(xform);
3112            }
3113            boolean result = scaleImage(img, dx1, dy1, dx2, dy2,
3114                                        sx1, sy1, sx2, sy2,
3115                                        bgcolor, observer);
3116            if (tx != null) {
3117                transform.setTransform(tx);
3118                invalidateTransform();
3119            }
3120            return result;
3121        } else if (resolutionVariantHint != SunHints.INTVAL_RESOLUTION_VARIANT_BASE
3122                   && (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    public void finalize() {
3637        // DO NOT REMOVE THIS METHOD
3638    }
3639
3640    /**
3641     * Returns destination that this Graphics renders to.  This could be
3642     * either an Image or a Component; subclasses of SurfaceData are
3643     * responsible for returning the appropriate object.
3644     */
3645    public Object getDestination() {
3646        return surfaceData.getDestination();
3647    }
3648
3649    /**
3650     * {@inheritDoc}
3651     *
3652     * @see sun.java2d.DestSurfaceProvider#getDestSurface
3653     */
3654    @Override
3655    public Surface getDestSurface() {
3656        return surfaceData;
3657    }
3658}
3659