Path: blob/master/src/java.desktop/share/classes/sun/java2d/SunGraphics2D.java
41152 views
/*1* Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.java2d;2627import java.awt.AlphaComposite;28import java.awt.BasicStroke;29import java.awt.Color;30import java.awt.Composite;31import java.awt.Font;32import java.awt.FontMetrics;33import java.awt.GradientPaint;34import java.awt.Graphics;35import java.awt.Graphics2D;36import java.awt.GraphicsConfiguration;37import java.awt.Image;38import java.awt.LinearGradientPaint;39import java.awt.Paint;40import java.awt.RadialGradientPaint;41import java.awt.Rectangle;42import java.awt.RenderingHints;43import java.awt.RenderingHints.Key;44import java.awt.Shape;45import java.awt.Stroke;46import java.awt.TexturePaint;47import java.awt.Transparency;48import java.awt.font.FontRenderContext;49import java.awt.font.GlyphVector;50import java.awt.font.TextLayout;51import java.awt.geom.AffineTransform;52import java.awt.geom.Area;53import java.awt.geom.GeneralPath;54import java.awt.geom.NoninvertibleTransformException;55import java.awt.geom.PathIterator;56import java.awt.geom.Rectangle2D;57import java.awt.image.AffineTransformOp;58import java.awt.image.BufferedImage;59import java.awt.image.BufferedImageOp;60import java.awt.image.ColorModel;61import java.awt.image.ImageObserver;62import java.awt.image.MultiResolutionImage;63import java.awt.image.Raster;64import java.awt.image.RenderedImage;65import java.awt.image.VolatileImage;66import java.awt.image.WritableRaster;67import java.awt.image.renderable.RenderContext;68import java.awt.image.renderable.RenderableImage;69import java.lang.annotation.Native;70import java.text.AttributedCharacterIterator;71import java.util.Iterator;72import java.util.Map;7374import sun.awt.ConstrainableGraphics;75import sun.awt.SunHints;76import sun.awt.image.MultiResolutionToolkitImage;77import sun.awt.image.SurfaceManager;78import sun.awt.image.ToolkitImage;79import sun.awt.util.PerformanceLogger;80import sun.font.FontDesignMetrics;81import sun.font.FontUtilities;82import sun.java2d.loops.Blit;83import sun.java2d.loops.CompositeType;84import sun.java2d.loops.FontInfo;85import sun.java2d.loops.MaskFill;86import sun.java2d.loops.RenderLoops;87import sun.java2d.loops.SurfaceType;88import sun.java2d.loops.XORComposite;89import sun.java2d.pipe.DrawImagePipe;90import sun.java2d.pipe.LoopPipe;91import sun.java2d.pipe.PixelDrawPipe;92import sun.java2d.pipe.PixelFillPipe;93import sun.java2d.pipe.Region;94import sun.java2d.pipe.ShapeDrawPipe;95import sun.java2d.pipe.ShapeSpanIterator;96import sun.java2d.pipe.TextPipe;97import sun.java2d.pipe.ValidatePipe;9899import static java.awt.geom.AffineTransform.TYPE_FLIP;100import static java.awt.geom.AffineTransform.TYPE_MASK_SCALE;101import static java.awt.geom.AffineTransform.TYPE_TRANSLATION;102103/**104* This is a the master Graphics2D superclass for all of the Sun105* Graphics implementations. This class relies on subclasses to106* manage the various device information, but provides an overall107* general framework for performing all of the requests in the108* Graphics and Graphics2D APIs.109*110* @author Jim Graham111*/112public final class SunGraphics2D113extends Graphics2D114implements ConstrainableGraphics, Cloneable, DestSurfaceProvider115{116/*117* Attribute States118*/119/* Paint */120@Native121public static final int PAINT_CUSTOM = 6; /* Any other Paint object */122@Native123public static final int PAINT_TEXTURE = 5; /* Tiled Image */124@Native125public static final int PAINT_RAD_GRADIENT = 4; /* Color RadialGradient */126@Native127public static final int PAINT_LIN_GRADIENT = 3; /* Color LinearGradient */128@Native129public static final int PAINT_GRADIENT = 2; /* Color Gradient */130@Native131public static final int PAINT_ALPHACOLOR = 1; /* Non-opaque Color */132@Native133public static final int PAINT_OPAQUECOLOR = 0; /* Opaque Color */134135/* Composite*/136@Native137public static final int COMP_CUSTOM = 3;/* Custom Composite */138@Native139public static final int COMP_XOR = 2;/* XOR Mode Composite */140@Native141public static final int COMP_ALPHA = 1;/* AlphaComposite */142@Native143public static final int COMP_ISCOPY = 0;/* simple stores into destination,144* i.e. Src, SrcOverNoEa, and other145* alpha modes which replace146* the destination.147*/148149/* Stroke */150@Native151public static final int STROKE_CUSTOM = 3; /* custom Stroke */152@Native153public static final int STROKE_WIDE = 2; /* BasicStroke */154@Native155public static final int STROKE_THINDASHED = 1; /* BasicStroke */156@Native157public static final int STROKE_THIN = 0; /* BasicStroke */158159/* Transform */160@Native161public static final int TRANSFORM_GENERIC = 4; /* any 3x2 */162@Native163public static final int TRANSFORM_TRANSLATESCALE = 3; /* scale XY */164@Native165public static final int TRANSFORM_ANY_TRANSLATE = 2; /* non-int translate */166@Native167public static final int TRANSFORM_INT_TRANSLATE = 1; /* int translate */168@Native169public static final int TRANSFORM_ISIDENT = 0; /* Identity */170171/* Clipping */172@Native173public static final int CLIP_SHAPE = 2; /* arbitrary clip */174@Native175public static final int CLIP_RECTANGULAR = 1; /* rectangular clip */176@Native177public static final int CLIP_DEVICE = 0; /* no clipping set */178179/* The following fields are used when the current Paint is a Color. */180public int eargb; // ARGB value with ExtraAlpha baked in181public int pixel; // pixel value for eargb182183public SurfaceData surfaceData;184185public PixelDrawPipe drawpipe;186public PixelFillPipe fillpipe;187public DrawImagePipe imagepipe;188public ShapeDrawPipe shapepipe;189public TextPipe textpipe;190public MaskFill alphafill;191192public RenderLoops loops;193194public CompositeType imageComp; /* Image Transparency checked on fly */195196public int paintState;197public int compositeState;198public int strokeState;199public int transformState;200public int clipState;201202public Color foregroundColor;203public Color backgroundColor;204205public AffineTransform transform;206public int transX;207public int transY;208209protected static final Stroke defaultStroke = new BasicStroke();210protected static final Composite defaultComposite = AlphaComposite.SrcOver;211private static final Font defaultFont =212new Font(Font.DIALOG, Font.PLAIN, 12);213214public Paint paint;215public Stroke stroke;216public Composite composite;217protected Font font;218protected FontMetrics fontMetrics;219220public int renderHint;221public int antialiasHint;222public int textAntialiasHint;223protected int fractionalMetricsHint;224225/* A gamma adjustment to the colour used in lcd text blitting */226public int lcdTextContrast;227private static int lcdTextContrastDefaultValue = 140;228229private int interpolationHint; // raw value of rendering Hint230public int strokeHint;231232public int interpolationType; // algorithm choice based on233// interpolation and render Hints234235public RenderingHints hints;236237public Region constrainClip; // lightweight bounds in pixels238public int constrainX;239public int constrainY;240241public Region clipRegion;242public Shape usrClip;243protected Region devClip; // Actual physical drawable in pixels244245private int resolutionVariantHint;246247// cached state for text rendering248private boolean validFontInfo;249private FontInfo fontInfo;250private FontInfo glyphVectorFontInfo;251private FontRenderContext glyphVectorFRC;252253private static final int slowTextTransformMask =254AffineTransform.TYPE_GENERAL_TRANSFORM255| AffineTransform.TYPE_MASK_ROTATION256| AffineTransform.TYPE_FLIP;257258static {259if (PerformanceLogger.loggingEnabled()) {260PerformanceLogger.setTime("SunGraphics2D static initialization");261}262}263264public SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f) {265surfaceData = sd;266foregroundColor = fg;267backgroundColor = bg;268stroke = defaultStroke;269composite = defaultComposite;270paint = foregroundColor;271272imageComp = CompositeType.SrcOverNoEa;273274renderHint = SunHints.INTVAL_RENDER_DEFAULT;275antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;276textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;277fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;278lcdTextContrast = lcdTextContrastDefaultValue;279interpolationHint = -1;280strokeHint = SunHints.INTVAL_STROKE_DEFAULT;281resolutionVariantHint = SunHints.INTVAL_RESOLUTION_VARIANT_DEFAULT;282283interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;284285transform = getDefaultTransform();286if (!transform.isIdentity()) {287invalidateTransform();288}289290validateColor();291292font = f;293if (font == null) {294font = defaultFont;295}296297setDevClip(sd.getBounds());298invalidatePipe();299}300301private AffineTransform getDefaultTransform() {302GraphicsConfiguration gc = getDeviceConfiguration();303return (gc == null) ? new AffineTransform() : gc.getDefaultTransform();304}305306protected Object clone() {307try {308SunGraphics2D g = (SunGraphics2D) super.clone();309g.transform = new AffineTransform(this.transform);310if (hints != null) {311g.hints = (RenderingHints) this.hints.clone();312}313/* FontInfos are re-used, so must be cloned too, if they314* are valid, and be nulled out if invalid.315* The implied trade-off is that there is more to be gained316* from re-using these objects than is lost by having to317* clone them when the SG2D is cloned.318*/319if (this.fontInfo != null) {320if (this.validFontInfo) {321g.fontInfo = (FontInfo)this.fontInfo.clone();322} else {323g.fontInfo = null;324}325}326if (this.glyphVectorFontInfo != null) {327g.glyphVectorFontInfo =328(FontInfo)this.glyphVectorFontInfo.clone();329g.glyphVectorFRC = this.glyphVectorFRC;330}331//g.invalidatePipe();332return g;333} catch (CloneNotSupportedException e) {334}335return null;336}337338/**339* Create a new SunGraphics2D based on this one.340*/341public Graphics create() {342return (Graphics) clone();343}344345public void setDevClip(int x, int y, int w, int h) {346Region c = constrainClip;347if (c == null) {348devClip = Region.getInstanceXYWH(x, y, w, h);349} else {350devClip = c.getIntersectionXYWH(x, y, w, h);351}352validateCompClip();353}354355public void setDevClip(Rectangle r) {356setDevClip(r.x, r.y, r.width, r.height);357}358359/**360* Constrain rendering for lightweight objects.361*/362public void constrain(int x, int y, int w, int h, Region region) {363if ((x | y) != 0) {364translate(x, y);365}366if (transformState > TRANSFORM_TRANSLATESCALE) {367clipRect(0, 0, w, h);368return;369}370// changes parameters according to the current scale and translate.371final double scaleX = transform.getScaleX();372final double scaleY = transform.getScaleY();373x = constrainX = (int) transform.getTranslateX();374y = constrainY = (int) transform.getTranslateY();375w = Region.dimAdd(x, Region.clipScale(w, scaleX));376h = Region.dimAdd(y, Region.clipScale(h, scaleY));377378Region c = constrainClip;379if (c == null) {380c = Region.getInstanceXYXY(x, y, w, h);381} else {382c = c.getIntersectionXYXY(x, y, w, h);383}384if (region != null) {385region = region.getScaledRegion(scaleX, scaleY);386region = region.getTranslatedRegion(x, y);387c = c.getIntersection(region);388}389390if (c == constrainClip) {391// Common case to ignore392return;393}394395constrainClip = c;396if (!devClip.isInsideQuickCheck(c)) {397devClip = devClip.getIntersection(c);398validateCompClip();399}400}401402/**403* Constrain rendering for lightweight objects.404*405* REMIND: This method will back off to the "workaround"406* of using translate and clipRect if the Graphics407* to be constrained has a complex transform. The408* drawback of the workaround is that the resulting409* clip and device origin cannot be "enforced".410*411* @exception IllegalStateException If the Graphics412* to be constrained has a complex transform.413*/414@Override415public void constrain(int x, int y, int w, int h) {416constrain(x, y, w, h, null);417}418419protected static ValidatePipe invalidpipe = new ValidatePipe();420421/*422* Invalidate the pipeline423*/424protected void invalidatePipe() {425drawpipe = invalidpipe;426fillpipe = invalidpipe;427shapepipe = invalidpipe;428textpipe = invalidpipe;429imagepipe = invalidpipe;430loops = null;431}432433public void validatePipe() {434/* This workaround is for the situation when we update the Pipelines435* for invalid SurfaceData and run further code when the current436* pipeline doesn't support the type of new SurfaceData created during437* the current pipeline's work (in place of the invalid SurfaceData).438* Usually SurfaceData and Pipelines are repaired (through revalidateAll)439* and called again in the exception handlers */440441if (!surfaceData.isValid()) {442throw new InvalidPipeException("attempt to validate Pipe with invalid SurfaceData");443}444445surfaceData.validatePipe(this);446}447448/*449* Intersect two Shapes by the simplest method, attempting to produce450* a simplified result.451* The boolean arguments keep1 and keep2 specify whether or not452* the first or second shapes can be modified during the operation453* or whether that shape must be "kept" unmodified.454*/455Shape intersectShapes(Shape s1, Shape s2, boolean keep1, boolean keep2) {456if (s1 instanceof Rectangle && s2 instanceof Rectangle) {457return ((Rectangle) s1).intersection((Rectangle) s2);458}459if (s1 instanceof Rectangle2D) {460return intersectRectShape((Rectangle2D) s1, s2, keep1, keep2);461} else if (s2 instanceof Rectangle2D) {462return intersectRectShape((Rectangle2D) s2, s1, keep2, keep1);463}464return intersectByArea(s1, s2, keep1, keep2);465}466467/*468* Intersect a Rectangle with a Shape by the simplest method,469* attempting to produce a simplified result.470* The boolean arguments keep1 and keep2 specify whether or not471* the first or second shapes can be modified during the operation472* or whether that shape must be "kept" unmodified.473*/474Shape intersectRectShape(Rectangle2D r, Shape s,475boolean keep1, boolean keep2) {476if (s instanceof Rectangle2D) {477Rectangle2D r2 = (Rectangle2D) s;478Rectangle2D outrect;479if (!keep1) {480outrect = r;481} else if (!keep2) {482outrect = r2;483} else {484outrect = new Rectangle2D.Float();485}486double x1 = Math.max(r.getX(), r2.getX());487double x2 = Math.min(r.getX() + r.getWidth(),488r2.getX() + r2.getWidth());489double y1 = Math.max(r.getY(), r2.getY());490double y2 = Math.min(r.getY() + r.getHeight(),491r2.getY() + r2.getHeight());492493if (((x2 - x1) < 0) || ((y2 - y1) < 0))494// Width or height is negative. No intersection.495outrect.setFrameFromDiagonal(0, 0, 0, 0);496else497outrect.setFrameFromDiagonal(x1, y1, x2, y2);498return outrect;499}500if (r.contains(s.getBounds2D())) {501if (keep2) {502s = cloneShape(s);503}504return s;505}506return intersectByArea(r, s, keep1, keep2);507}508509protected static Shape cloneShape(Shape s) {510return new GeneralPath(s);511}512513/*514* Intersect two Shapes using the Area class. Presumably other515* attempts at simpler intersection methods proved fruitless.516* The boolean arguments keep1 and keep2 specify whether or not517* the first or second shapes can be modified during the operation518* or whether that shape must be "kept" unmodified.519* @see #intersectShapes520* @see #intersectRectShape521*/522Shape intersectByArea(Shape s1, Shape s2, boolean keep1, boolean keep2) {523Area a1, a2;524525// First see if we can find an overwriteable source shape526// to use as our destination area to avoid duplication.527if (!keep1 && (s1 instanceof Area)) {528a1 = (Area) s1;529} else if (!keep2 && (s2 instanceof Area)) {530a1 = (Area) s2;531s2 = s1;532} else {533a1 = new Area(s1);534}535536if (s2 instanceof Area) {537a2 = (Area) s2;538} else {539a2 = new Area(s2);540}541542a1.intersect(a2);543if (a1.isRectangular()) {544return a1.getBounds();545}546547return a1;548}549550/*551* Intersect usrClip bounds and device bounds to determine the composite552* rendering boundaries.553*/554public Region getCompClip() {555if (!surfaceData.isValid()) {556// revalidateAll() implicitly recalculcates the composite clip557revalidateAll();558}559560return clipRegion;561}562563public Font getFont() {564if (font == null) {565font = defaultFont;566}567return font;568}569570private static final double[] IDENT_MATRIX = {1, 0, 0, 1};571private static final AffineTransform IDENT_ATX =572new AffineTransform();573574private static final int MINALLOCATED = 8;575private static final int TEXTARRSIZE = 17;576private static double[][] textTxArr = new double[TEXTARRSIZE][];577private static AffineTransform[] textAtArr =578new AffineTransform[TEXTARRSIZE];579580static {581for (int i=MINALLOCATED;i<TEXTARRSIZE;i++) {582textTxArr[i] = new double [] {i, 0, 0, i};583textAtArr[i] = new AffineTransform( textTxArr[i]);584}585}586587// cached state for various draw[String,Char,Byte] optimizations588public FontInfo checkFontInfo(FontInfo info, Font font,589FontRenderContext frc) {590/* Do not create a FontInfo object as part of construction of an591* SG2D as its possible it may never be needed - ie if no text592* is drawn using this SG2D.593*/594if (info == null) {595info = new FontInfo();596}597598float ptSize = font.getSize2D();599int txFontType;600AffineTransform devAt, textAt=null;601if (font.isTransformed()) {602textAt = font.getTransform();603textAt.scale(ptSize, ptSize);604txFontType = textAt.getType();605info.originX = (float)textAt.getTranslateX();606info.originY = (float)textAt.getTranslateY();607textAt.translate(-info.originX, -info.originY);608if (transformState >= TRANSFORM_TRANSLATESCALE) {609transform.getMatrix(info.devTx = new double[4]);610devAt = new AffineTransform(info.devTx);611textAt.preConcatenate(devAt);612} else {613info.devTx = IDENT_MATRIX;614devAt = IDENT_ATX;615}616textAt.getMatrix(info.glyphTx = new double[4]);617double shearx = textAt.getShearX();618double scaley = textAt.getScaleY();619if (shearx != 0) {620scaley = Math.sqrt(shearx * shearx + scaley * scaley);621}622info.pixelHeight = (int)(Math.abs(scaley)+0.5);623} else {624txFontType = AffineTransform.TYPE_IDENTITY;625info.originX = info.originY = 0;626if (transformState >= TRANSFORM_TRANSLATESCALE) {627transform.getMatrix(info.devTx = new double[4]);628devAt = new AffineTransform(info.devTx);629info.glyphTx = new double[4];630for (int i = 0; i < 4; i++) {631info.glyphTx[i] = info.devTx[i] * ptSize;632}633textAt = new AffineTransform(info.glyphTx);634double shearx = transform.getShearX();635double scaley = transform.getScaleY();636if (shearx != 0) {637scaley = Math.sqrt(shearx * shearx + scaley * scaley);638}639info.pixelHeight = (int)(Math.abs(scaley * ptSize)+0.5);640} else {641/* If the double represents a common integral, we642* may have pre-allocated objects.643* A "sparse" array be seems to be as fast as a switch644* even for 3 or 4 pt sizes, and is more flexible.645* This should perform comparably in single-threaded646* rendering to the old code which synchronized on the647* class and scale better on MP systems.648*/649int pszInt = (int)ptSize;650if (ptSize == pszInt &&651pszInt >= MINALLOCATED && pszInt < TEXTARRSIZE) {652info.glyphTx = textTxArr[pszInt];653textAt = textAtArr[pszInt];654info.pixelHeight = pszInt;655} else {656info.pixelHeight = (int)(ptSize+0.5);657}658if (textAt == null) {659info.glyphTx = new double[] {ptSize, 0, 0, ptSize};660textAt = new AffineTransform(info.glyphTx);661}662663info.devTx = IDENT_MATRIX;664devAt = IDENT_ATX;665}666}667668info.nonInvertibleTx =669(Math.abs(textAt.getDeterminant()) <= Double.MIN_VALUE);670671info.font2D = FontUtilities.getFont2D(font);672673int fmhint = fractionalMetricsHint;674if (fmhint == SunHints.INTVAL_FRACTIONALMETRICS_DEFAULT) {675fmhint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;676}677info.lcdSubPixPos = false; // conditionally set true in LCD mode.678679/* The text anti-aliasing hints that are set by the client need680* to be interpreted for the current state and stored in the681* FontInfo.aahint which is what will actually be used and682* will be one of OFF, ON, LCD_HRGB or LCD_VRGB.683* This is what pipe selection code should typically refer to, not684* textAntialiasHint. This means we are now evaluating the meaning685* of "default" here. Any pipe that really cares about that will686* also need to consult that variable.687* Otherwise these are being used only as args to getStrike,688* and are encapsulated in that object which is part of the689* FontInfo, so we do not need to store them directly as fields690* in the FontInfo object.691* That could change if FontInfo's were more selectively692* revalidated when graphics state changed. Presently this693* method re-evaluates all fields in the fontInfo.694* The strike doesn't need to know the RGB subpixel order. Just695* if its H or V orientation, so if an LCD option is specified we696* always pass in the RGB hint to the strike.697* frc is non-null only if this is a GlyphVector. For reasons698* which are probably a historical mistake the AA hint in a GV699* is honoured when we render, overriding the Graphics setting.700*/701int aahint;702if (frc == null) {703aahint = textAntialiasHint;704} else {705aahint = ((SunHints.Value)frc.getAntiAliasingHint()).getIndex();706}707if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT) {708if (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {709aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;710} else {711aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;712}713} else {714/* If we are in checkFontInfo because a rendering hint has been715* set then all pipes are revalidated. But we can also716* be here because setFont() has been called when the 'gasp'717* hint is set, as then the font size determines the text pipe.718* See comments in SunGraphics2d.setFont(Font).719*/720if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP) {721if (info.font2D.useAAForPtSize(info.pixelHeight)) {722aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;723} else {724aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;725}726} else if (aahint >= SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB) {727/* loops for default rendering modes are installed in the SG2D728* constructor. If there are none this will be null.729* Not all compositing modes update the render loops, so730* we also test that this is a mode we know should support731* this. One minor issue is that the loops aren't necessarily732* installed for a new rendering mode until after this733* method is called during pipeline validation. So it is734* theoretically possible that it was set to null for a735* compositing mode, the composite is then set back to Src,736* but the loop is still null when this is called and AA=ON737* is installed instead of an LCD mode.738* However this is done in the right order in SurfaceData.java739* so this is not likely to be a problem - but not740* guaranteed.741*/742if (743!surfaceData.canRenderLCDText(this)744// loops.drawGlyphListLCDLoop == null ||745// compositeState > COMP_ISCOPY ||746// paintState > PAINT_ALPHACOLOR747) {748aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;749} else {750info.lcdRGBOrder = true;751/* Collapse these into just HRGB or VRGB.752* Pipe selection code needs only to test for these two.753* Since these both select the same pipe anyway its754* tempting to collapse into one value. But they are755* different strikes (glyph caches) so the distinction756* needs to be made for that purpose.757*/758if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HBGR) {759aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;760info.lcdRGBOrder = false;761} else if762(aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VBGR) {763aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB;764info.lcdRGBOrder = false;765}766/* Support subpixel positioning only for the case in767* which the horizontal resolution is increased768*/769info.lcdSubPixPos =770fmhint == SunHints.INTVAL_FRACTIONALMETRICS_ON &&771aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;772}773}774}775if (FontUtilities.isMacOSX14 &&776(aahint == SunHints.INTVAL_TEXT_ANTIALIAS_OFF))777{778aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;779}780info.aaHint = aahint;781info.fontStrike = info.font2D.getStrike(font, devAt, textAt,782aahint, fmhint);783return info;784}785786public static boolean isRotated(double [] mtx) {787if ((mtx[0] == mtx[3]) &&788(mtx[1] == 0.0) &&789(mtx[2] == 0.0) &&790(mtx[0] > 0.0))791{792return false;793}794795return true;796}797798public void setFont(Font font) {799/* replacing the reference equality test font != this.font with800* !font.equals(this.font) did not yield any measurable difference801* in testing, but there may be yet to be identified cases where it802* is beneficial.803*/804if (font != null && font!=this.font/*!font.equals(this.font)*/) {805/* In the GASP AA case the textpipe depends on the glyph size806* as determined by graphics and font transforms as well as the807* font size, and information in the font. But we may invalidate808* the pipe only to find that it made no difference.809* Deferring pipe invalidation to checkFontInfo won't work because810* when called we may already be rendering to the wrong pipe.811* So, if the font is transformed, or the graphics has more than812* a simple scale, we'll take that as enough of a hint to813* revalidate everything. But if they aren't we will814* use the font's point size to query the gasp table and see if815* what it says matches what's currently being used, in which816* case there's no need to invalidate the textpipe.817* This should be sufficient for all typical uses cases.818*/819if (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP &&820textpipe != invalidpipe &&821(transformState > TRANSFORM_ANY_TRANSLATE ||822font.isTransformed() ||823fontInfo == null || // Precaution, if true shouldn't get here824(fontInfo.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) !=825FontUtilities.getFont2D(font).826useAAForPtSize(font.getSize()))) {827textpipe = invalidpipe;828}829this.font = font;830this.fontMetrics = null;831this.validFontInfo = false;832}833}834835public FontInfo getFontInfo() {836if (!validFontInfo) {837this.fontInfo = checkFontInfo(this.fontInfo, font, null);838validFontInfo = true;839}840return this.fontInfo;841}842843/* Used by drawGlyphVector which specifies its own font. */844public FontInfo getGVFontInfo(Font font, FontRenderContext frc) {845if (glyphVectorFontInfo != null &&846glyphVectorFontInfo.font == font &&847glyphVectorFRC == frc) {848return glyphVectorFontInfo;849} else {850glyphVectorFRC = frc;851return glyphVectorFontInfo =852checkFontInfo(glyphVectorFontInfo, font, frc);853}854}855856public FontMetrics getFontMetrics() {857if (this.fontMetrics != null) {858return this.fontMetrics;859}860/* NB the constructor and the setter disallow "font" being null */861return this.fontMetrics =862FontDesignMetrics.getMetrics(font, getFontRenderContext());863}864865public FontMetrics getFontMetrics(Font font) {866if ((this.fontMetrics != null) && (font == this.font)) {867return this.fontMetrics;868}869FontMetrics fm =870FontDesignMetrics.getMetrics(font, getFontRenderContext());871872if (this.font == font) {873this.fontMetrics = fm;874}875return fm;876}877878/**879* Checks to see if a Path intersects the specified Rectangle in device880* space. The rendering attributes taken into account include the881* clip, transform, and stroke attributes.882* @param rect The area in device space to check for a hit.883* @param s The path to check for a hit.884* @param onStroke Flag to choose between testing the stroked or885* the filled path.886* @return True if there is a hit, false otherwise.887* @see #setStroke888* @see #fill(Shape)889* @see #draw(Shape)890* @see #transform891* @see #setTransform892* @see #clip893* @see #setClip894*/895public boolean hit(Rectangle rect, Shape s, boolean onStroke) {896if (onStroke) {897s = stroke.createStrokedShape(s);898}899900s = transformShape(s);901if ((constrainX|constrainY) != 0) {902rect = new Rectangle(rect);903rect.translate(constrainX, constrainY);904}905906return s.intersects(rect);907}908909/**910* Return the ColorModel associated with this Graphics2D.911*/912public ColorModel getDeviceColorModel() {913return surfaceData.getColorModel();914}915916/**917* Return the device configuration associated with this Graphics2D.918*/919public GraphicsConfiguration getDeviceConfiguration() {920return surfaceData.getDeviceConfiguration();921}922923/**924* Return the SurfaceData object assigned to manage the destination925* drawable surface of this Graphics2D.926*/927public SurfaceData getSurfaceData() {928return surfaceData;929}930931/**932* Sets the Composite in the current graphics state. Composite is used933* in all drawing methods such as drawImage, drawString, drawPath,934* and fillPath. It specifies how new pixels are to be combined with935* the existing pixels on the graphics device in the rendering process.936* @param comp The Composite object to be used for drawing.937* @see java.awt.Graphics#setXORMode938* @see java.awt.Graphics#setPaintMode939* @see AlphaComposite940*/941public void setComposite(Composite comp) {942if (composite == comp) {943return;944}945int newCompState;946CompositeType newCompType;947if (comp instanceof AlphaComposite) {948AlphaComposite alphacomp = (AlphaComposite) comp;949newCompType = CompositeType.forAlphaComposite(alphacomp);950if (newCompType == CompositeType.SrcOverNoEa) {951if (paintState == PAINT_OPAQUECOLOR ||952(paintState > PAINT_ALPHACOLOR &&953paint.getTransparency() == Transparency.OPAQUE))954{955newCompState = COMP_ISCOPY;956} else {957newCompState = COMP_ALPHA;958}959} else if (newCompType == CompositeType.SrcNoEa ||960newCompType == CompositeType.Src ||961newCompType == CompositeType.Clear)962{963newCompState = COMP_ISCOPY;964} else if (surfaceData.getTransparency() == Transparency.OPAQUE &&965newCompType == CompositeType.SrcIn)966{967newCompState = COMP_ISCOPY;968} else {969newCompState = COMP_ALPHA;970}971} else if (comp instanceof XORComposite) {972newCompState = COMP_XOR;973newCompType = CompositeType.Xor;974} else if (comp == null) {975throw new IllegalArgumentException("null Composite");976} else {977surfaceData.checkCustomComposite();978newCompState = COMP_CUSTOM;979newCompType = CompositeType.General;980}981if (compositeState != newCompState ||982imageComp != newCompType)983{984compositeState = newCompState;985imageComp = newCompType;986invalidatePipe();987validFontInfo = false;988}989composite = comp;990if (paintState <= PAINT_ALPHACOLOR) {991validateColor();992}993}994995/**996* Sets the Paint in the current graphics state.997* @param paint The Paint object to be used to generate color in998* the rendering process.999* @see java.awt.Graphics#setColor1000* @see GradientPaint1001* @see TexturePaint1002*/1003public void setPaint(Paint paint) {1004if (paint instanceof Color) {1005setColor((Color) paint);1006return;1007}1008if (paint == null || this.paint == paint) {1009return;1010}1011this.paint = paint;1012if (imageComp == CompositeType.SrcOverNoEa) {1013// special case where compState depends on opacity of paint1014if (paint.getTransparency() == Transparency.OPAQUE) {1015if (compositeState != COMP_ISCOPY) {1016compositeState = COMP_ISCOPY;1017}1018} else {1019if (compositeState == COMP_ISCOPY) {1020compositeState = COMP_ALPHA;1021}1022}1023}1024Class<? extends Paint> paintClass = paint.getClass();1025if (paintClass == GradientPaint.class) {1026paintState = PAINT_GRADIENT;1027} else if (paintClass == LinearGradientPaint.class) {1028paintState = PAINT_LIN_GRADIENT;1029} else if (paintClass == RadialGradientPaint.class) {1030paintState = PAINT_RAD_GRADIENT;1031} else if (paintClass == TexturePaint.class) {1032paintState = PAINT_TEXTURE;1033} else {1034paintState = PAINT_CUSTOM;1035}1036validFontInfo = false;1037invalidatePipe();1038}10391040static final int NON_UNIFORM_SCALE_MASK =1041(AffineTransform.TYPE_GENERAL_TRANSFORM |1042AffineTransform.TYPE_GENERAL_SCALE);1043public static final double MinPenSizeAA =1044sun.java2d.pipe.RenderingEngine.getInstance().getMinimumAAPenSize();1045public static final double MinPenSizeAASquared =1046(MinPenSizeAA * MinPenSizeAA);1047// Since inaccuracies in the trig package can cause us to1048// calculated a rotated pen width of just slightly greater1049// than 1.0, we add a fudge factor to our comparison value1050// here so that we do not misclassify single width lines as1051// wide lines under certain rotations.1052public static final double MinPenSizeSquared = 1.000000001;10531054private void validateBasicStroke(BasicStroke bs) {1055boolean aa = (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON);1056if (transformState < TRANSFORM_TRANSLATESCALE) {1057if (aa) {1058if (bs.getLineWidth() <= MinPenSizeAA) {1059if (bs.getDashArray() == null) {1060strokeState = STROKE_THIN;1061} else {1062strokeState = STROKE_THINDASHED;1063}1064} else {1065strokeState = STROKE_WIDE;1066}1067} else {1068if (bs == defaultStroke) {1069strokeState = STROKE_THIN;1070} else if (bs.getLineWidth() <= 1.0f) {1071if (bs.getDashArray() == null) {1072strokeState = STROKE_THIN;1073} else {1074strokeState = STROKE_THINDASHED;1075}1076} else {1077strokeState = STROKE_WIDE;1078}1079}1080} else {1081double widthsquared;1082if ((transform.getType() & NON_UNIFORM_SCALE_MASK) == 0) {1083/* sqrt omitted, compare to squared limits below. */1084widthsquared = Math.abs(transform.getDeterminant());1085} else {1086/* First calculate the "maximum scale" of this transform. */1087double A = transform.getScaleX(); // m001088double C = transform.getShearX(); // m011089double B = transform.getShearY(); // m101090double D = transform.getScaleY(); // m1110911092/*1093* Given a 2 x 2 affine matrix [ A B ] such that1094* [ C D ]1095* v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to1096* find the maximum magnitude (norm) of the vector v'1097* with the constraint (x^2 + y^2 = 1).1098* The equation to maximize is1099* |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)1100* or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).1101* Since sqrt is monotonic we can maximize |v'|^21102* instead and plug in the substitution y = sqrt(1 - x^2).1103* Trigonometric equalities can then be used to get1104* rid of most of the sqrt terms.1105*/1106double EA = A*A + B*B; // x^2 coefficient1107double EB = 2*(A*C + B*D); // xy coefficient1108double EC = C*C + D*D; // y^2 coefficient11091110/*1111* There is a lot of calculus omitted here.1112*1113* Conceptually, in the interests of understanding the1114* terms that the calculus produced we can consider1115* that EA and EC end up providing the lengths along1116* the major axes and the hypot term ends up being an1117* adjustment for the additional length along the off-axis1118* angle of rotated or sheared ellipses as well as an1119* adjustment for the fact that the equation below1120* averages the two major axis lengths. (Notice that1121* the hypot term contains a part which resolves to the1122* difference of these two axis lengths in the absence1123* of rotation.)1124*1125* In the calculus, the ratio of the EB and (EA-EC) terms1126* ends up being the tangent of 2*theta where theta is1127* the angle that the long axis of the ellipse makes1128* with the horizontal axis. Thus, this equation is1129* calculating the length of the hypotenuse of a triangle1130* along that axis.1131*/1132double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));11331134/* sqrt omitted, compare to squared limits below. */1135widthsquared = ((EA + EC + hypot)/2.0);1136}1137if (bs != defaultStroke) {1138widthsquared *= bs.getLineWidth() * bs.getLineWidth();1139}1140if (widthsquared <=1141(aa ? MinPenSizeAASquared : MinPenSizeSquared))1142{1143if (bs.getDashArray() == null) {1144strokeState = STROKE_THIN;1145} else {1146strokeState = STROKE_THINDASHED;1147}1148} else {1149strokeState = STROKE_WIDE;1150}1151}1152}11531154/*1155* Sets the Stroke in the current graphics state.1156* @param s The Stroke object to be used to stroke a Path in1157* the rendering process.1158* @see BasicStroke1159*/1160public void setStroke(Stroke s) {1161if (s == null) {1162throw new IllegalArgumentException("null Stroke");1163}1164int saveStrokeState = strokeState;1165stroke = s;1166if (s instanceof BasicStroke) {1167validateBasicStroke((BasicStroke) s);1168} else {1169strokeState = STROKE_CUSTOM;1170}1171if (strokeState != saveStrokeState) {1172invalidatePipe();1173}1174}11751176/**1177* Sets the preferences for the rendering algorithms.1178* Hint categories include controls for rendering quality and1179* overall time/quality trade-off in the rendering process.1180* @param hintKey The key of hint to be set. The strings are1181* defined in the RenderingHints class.1182* @param hintValue The value indicating preferences for the specified1183* hint category. These strings are defined in the RenderingHints1184* class.1185* @see RenderingHints1186*/1187public void setRenderingHint(Key hintKey, Object hintValue) {1188// If we recognize the key, we must recognize the value1189// otherwise throw an IllegalArgumentException1190// and do not change the Hints object1191// If we do not recognize the key, just pass it through1192// to the Hints object untouched1193if (!hintKey.isCompatibleValue(hintValue)) {1194throw new IllegalArgumentException1195(hintValue+" is not compatible with "+hintKey);1196}1197if (hintKey instanceof SunHints.Key) {1198boolean stateChanged;1199boolean textStateChanged = false;1200boolean recognized = true;1201SunHints.Key sunKey = (SunHints.Key) hintKey;1202int newHint;1203if (sunKey == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST) {1204newHint = ((Integer)hintValue).intValue();1205} else {1206newHint = ((SunHints.Value) hintValue).getIndex();1207}1208switch (sunKey.getIndex()) {1209case SunHints.INTKEY_RENDERING:1210stateChanged = (renderHint != newHint);1211if (stateChanged) {1212renderHint = newHint;1213if (interpolationHint == -1) {1214interpolationType =1215(newHint == SunHints.INTVAL_RENDER_QUALITY1216? AffineTransformOp.TYPE_BILINEAR1217: AffineTransformOp.TYPE_NEAREST_NEIGHBOR);1218}1219}1220break;1221case SunHints.INTKEY_ANTIALIASING:1222stateChanged = (antialiasHint != newHint);1223antialiasHint = newHint;1224if (stateChanged) {1225textStateChanged =1226(textAntialiasHint ==1227SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT);1228if (strokeState != STROKE_CUSTOM) {1229validateBasicStroke((BasicStroke) stroke);1230}1231}1232break;1233case SunHints.INTKEY_TEXT_ANTIALIASING:1234stateChanged = (textAntialiasHint != newHint);1235textStateChanged = stateChanged;1236textAntialiasHint = newHint;1237break;1238case SunHints.INTKEY_FRACTIONALMETRICS:1239stateChanged = (fractionalMetricsHint != newHint);1240textStateChanged = stateChanged;1241fractionalMetricsHint = newHint;1242break;1243case SunHints.INTKEY_AATEXT_LCD_CONTRAST:1244stateChanged = false;1245/* Already have validated it is an int 100 <= newHint <= 250 */1246lcdTextContrast = newHint;1247break;1248case SunHints.INTKEY_INTERPOLATION:1249interpolationHint = newHint;1250switch (newHint) {1251case SunHints.INTVAL_INTERPOLATION_BICUBIC:1252newHint = AffineTransformOp.TYPE_BICUBIC;1253break;1254case SunHints.INTVAL_INTERPOLATION_BILINEAR:1255newHint = AffineTransformOp.TYPE_BILINEAR;1256break;1257default:1258case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:1259newHint = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;1260break;1261}1262stateChanged = (interpolationType != newHint);1263interpolationType = newHint;1264break;1265case SunHints.INTKEY_STROKE_CONTROL:1266stateChanged = (strokeHint != newHint);1267strokeHint = newHint;1268break;1269case SunHints.INTKEY_RESOLUTION_VARIANT:1270stateChanged = (resolutionVariantHint != newHint);1271resolutionVariantHint = newHint;1272break;1273default:1274recognized = false;1275stateChanged = false;1276break;1277}1278if (recognized) {1279if (stateChanged) {1280invalidatePipe();1281if (textStateChanged) {1282fontMetrics = null;1283this.cachedFRC = null;1284validFontInfo = false;1285this.glyphVectorFontInfo = null;1286}1287}1288if (hints != null) {1289hints.put(hintKey, hintValue);1290}1291return;1292}1293}1294// Nothing we recognize so none of "our state" has changed1295if (hints == null) {1296hints = makeHints(null);1297}1298hints.put(hintKey, hintValue);1299}130013011302/**1303* Returns the preferences for the rendering algorithms.1304* @param hintKey The category of hint to be set. The strings1305* are defined in the RenderingHints class.1306* @return The preferences for rendering algorithms. The strings1307* are defined in the RenderingHints class.1308* @see RenderingHints1309*/1310public Object getRenderingHint(Key hintKey) {1311if (hints != null) {1312return hints.get(hintKey);1313}1314if (!(hintKey instanceof SunHints.Key)) {1315return null;1316}1317int keyindex = ((SunHints.Key)hintKey).getIndex();1318switch (keyindex) {1319case SunHints.INTKEY_RENDERING:1320return SunHints.Value.get(SunHints.INTKEY_RENDERING,1321renderHint);1322case SunHints.INTKEY_ANTIALIASING:1323return SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,1324antialiasHint);1325case SunHints.INTKEY_TEXT_ANTIALIASING:1326return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,1327textAntialiasHint);1328case SunHints.INTKEY_FRACTIONALMETRICS:1329return SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,1330fractionalMetricsHint);1331case SunHints.INTKEY_AATEXT_LCD_CONTRAST:1332return lcdTextContrast;1333case SunHints.INTKEY_INTERPOLATION:1334switch (interpolationHint) {1335case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:1336return SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;1337case SunHints.INTVAL_INTERPOLATION_BILINEAR:1338return SunHints.VALUE_INTERPOLATION_BILINEAR;1339case SunHints.INTVAL_INTERPOLATION_BICUBIC:1340return SunHints.VALUE_INTERPOLATION_BICUBIC;1341}1342return null;1343case SunHints.INTKEY_STROKE_CONTROL:1344return SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,1345strokeHint);1346case SunHints.INTKEY_RESOLUTION_VARIANT:1347return SunHints.Value.get(SunHints.INTKEY_RESOLUTION_VARIANT,1348resolutionVariantHint);1349}1350return null;1351}13521353/**1354* Sets the preferences for the rendering algorithms.1355* Hint categories include controls for rendering quality and1356* overall time/quality trade-off in the rendering process.1357* @param hints The rendering hints to be set1358* @see RenderingHints1359*/1360public void setRenderingHints(Map<?,?> hints) {1361this.hints = null;1362renderHint = SunHints.INTVAL_RENDER_DEFAULT;1363antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;1364textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;1365fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;1366lcdTextContrast = lcdTextContrastDefaultValue;1367interpolationHint = -1;1368interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;1369boolean customHintPresent = false;1370for (Object key : hints.keySet()) {1371if (key == SunHints.KEY_RENDERING ||1372key == SunHints.KEY_ANTIALIASING ||1373key == SunHints.KEY_TEXT_ANTIALIASING ||1374key == SunHints.KEY_FRACTIONALMETRICS ||1375key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||1376key == SunHints.KEY_STROKE_CONTROL ||1377key == SunHints.KEY_INTERPOLATION)1378{1379setRenderingHint((Key) key, hints.get(key));1380} else {1381customHintPresent = true;1382}1383}1384if (customHintPresent) {1385this.hints = makeHints(hints);1386}1387invalidatePipe();1388}13891390/**1391* Adds a number of preferences for the rendering algorithms.1392* Hint categories include controls for rendering quality and1393* overall time/quality trade-off in the rendering process.1394* @param hints The rendering hints to be set1395* @see RenderingHints1396*/1397public void addRenderingHints(Map<?,?> hints) {1398boolean customHintPresent = false;1399for (Object key : hints.keySet()) {1400if (key == SunHints.KEY_RENDERING ||1401key == SunHints.KEY_ANTIALIASING ||1402key == SunHints.KEY_TEXT_ANTIALIASING ||1403key == SunHints.KEY_FRACTIONALMETRICS ||1404key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||1405key == SunHints.KEY_STROKE_CONTROL ||1406key == SunHints.KEY_INTERPOLATION)1407{1408setRenderingHint((Key) key, hints.get(key));1409} else {1410customHintPresent = true;1411}1412}1413if (customHintPresent) {1414if (this.hints == null) {1415this.hints = makeHints(hints);1416} else {1417this.hints.putAll(hints);1418}1419}1420}14211422/**1423* Gets the preferences for the rendering algorithms.1424* Hint categories include controls for rendering quality and1425* overall time/quality trade-off in the rendering process.1426* @see RenderingHints1427*/1428public RenderingHints getRenderingHints() {1429if (hints == null) {1430return makeHints(null);1431} else {1432return (RenderingHints) hints.clone();1433}1434}14351436RenderingHints makeHints(Map<?,?> hints) {1437RenderingHints model = new RenderingHints(null);1438if (hints != null) {1439model.putAll(hints);1440}1441model.put(SunHints.KEY_RENDERING,1442SunHints.Value.get(SunHints.INTKEY_RENDERING,1443renderHint));1444model.put(SunHints.KEY_ANTIALIASING,1445SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,1446antialiasHint));1447model.put(SunHints.KEY_TEXT_ANTIALIASING,1448SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,1449textAntialiasHint));1450model.put(SunHints.KEY_FRACTIONALMETRICS,1451SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,1452fractionalMetricsHint));1453model.put(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST,1454Integer.valueOf(lcdTextContrast));1455Object value;1456switch (interpolationHint) {1457case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:1458value = SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;1459break;1460case SunHints.INTVAL_INTERPOLATION_BILINEAR:1461value = SunHints.VALUE_INTERPOLATION_BILINEAR;1462break;1463case SunHints.INTVAL_INTERPOLATION_BICUBIC:1464value = SunHints.VALUE_INTERPOLATION_BICUBIC;1465break;1466default:1467value = null;1468break;1469}1470if (value != null) {1471model.put(SunHints.KEY_INTERPOLATION, value);1472}1473model.put(SunHints.KEY_STROKE_CONTROL,1474SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,1475strokeHint));1476return model;1477}14781479/**1480* Concatenates the current transform of this Graphics2D with a1481* translation transformation.1482* This is equivalent to calling transform(T), where T is an1483* AffineTransform represented by the following matrix:1484* <pre>1485* [ 1 0 tx ]1486* [ 0 1 ty ]1487* [ 0 0 1 ]1488* </pre>1489*/1490public void translate(double tx, double ty) {1491transform.translate(tx, ty);1492invalidateTransform();1493}14941495/**1496* Concatenates the current transform of this Graphics2D with a1497* rotation transformation.1498* This is equivalent to calling transform(R), where R is an1499* AffineTransform represented by the following matrix:1500* <pre>1501* [ cos(theta) -sin(theta) 0 ]1502* [ sin(theta) cos(theta) 0 ]1503* [ 0 0 1 ]1504* </pre>1505* Rotating with a positive angle theta rotates points on the positive1506* x axis toward the positive y axis.1507* @param theta The angle of rotation in radians.1508*/1509public void rotate(double theta) {1510transform.rotate(theta);1511invalidateTransform();1512}15131514/**1515* Concatenates the current transform of this Graphics2D with a1516* translated rotation transformation.1517* This is equivalent to the following sequence of calls:1518* <pre>1519* translate(x, y);1520* rotate(theta);1521* translate(-x, -y);1522* </pre>1523* Rotating with a positive angle theta rotates points on the positive1524* x axis toward the positive y axis.1525* @param theta The angle of rotation in radians.1526* @param x The x coordinate of the origin of the rotation1527* @param y The x coordinate of the origin of the rotation1528*/1529public void rotate(double theta, double x, double y) {1530transform.rotate(theta, x, y);1531invalidateTransform();1532}15331534/**1535* Concatenates the current transform of this Graphics2D with a1536* scaling transformation.1537* This is equivalent to calling transform(S), where S is an1538* AffineTransform represented by the following matrix:1539* <pre>1540* [ sx 0 0 ]1541* [ 0 sy 0 ]1542* [ 0 0 1 ]1543* </pre>1544*/1545public void scale(double sx, double sy) {1546transform.scale(sx, sy);1547invalidateTransform();1548}15491550/**1551* Concatenates the current transform of this Graphics2D with a1552* shearing transformation.1553* This is equivalent to calling transform(SH), where SH is an1554* AffineTransform represented by the following matrix:1555* <pre>1556* [ 1 shx 0 ]1557* [ shy 1 0 ]1558* [ 0 0 1 ]1559* </pre>1560* @param shx The factor by which coordinates are shifted towards the1561* positive X axis direction according to their Y coordinate1562* @param shy The factor by which coordinates are shifted towards the1563* positive Y axis direction according to their X coordinate1564*/1565public void shear(double shx, double shy) {1566transform.shear(shx, shy);1567invalidateTransform();1568}15691570/**1571* Composes a Transform object with the transform in this1572* Graphics2D according to the rule last-specified-first-applied.1573* If the currrent transform is Cx, the result of composition1574* with Tx is a new transform Cx'. Cx' becomes the current1575* transform for this Graphics2D.1576* Transforming a point p by the updated transform Cx' is1577* equivalent to first transforming p by Tx and then transforming1578* the result by the original transform Cx. In other words,1579* Cx'(p) = Cx(Tx(p)).1580* A copy of the Tx is made, if necessary, so further1581* modifications to Tx do not affect rendering.1582* @param xform The Transform object to be composed with the current1583* transform.1584* @see #setTransform1585* @see AffineTransform1586*/1587public void transform(AffineTransform xform) {1588this.transform.concatenate(xform);1589invalidateTransform();1590}15911592/**1593* Translate1594*/1595public void translate(int x, int y) {1596transform.translate(x, y);1597if (transformState <= TRANSFORM_INT_TRANSLATE) {1598transX += x;1599transY += y;1600transformState = (((transX | transY) == 0) ?1601TRANSFORM_ISIDENT : TRANSFORM_INT_TRANSLATE);1602} else {1603invalidateTransform();1604}1605}16061607/**1608* Sets the Transform in the current graphics state.1609* @param Tx The Transform object to be used in the rendering process.1610* @see #transform1611* @see AffineTransform1612*/1613@Override1614public void setTransform(AffineTransform Tx) {1615if ((constrainX | constrainY) == 0) {1616transform.setTransform(Tx);1617} else {1618transform.setToTranslation(constrainX, constrainY);1619transform.concatenate(Tx);1620}1621invalidateTransform();1622}16231624protected void invalidateTransform() {1625int type = transform.getType();1626int origTransformState = transformState;1627if (type == AffineTransform.TYPE_IDENTITY) {1628transformState = TRANSFORM_ISIDENT;1629transX = transY = 0;1630} else if (type == AffineTransform.TYPE_TRANSLATION) {1631double dtx = transform.getTranslateX();1632double dty = transform.getTranslateY();1633transX = (int) Math.floor(dtx + 0.5);1634transY = (int) Math.floor(dty + 0.5);1635if (dtx == transX && dty == transY) {1636transformState = TRANSFORM_INT_TRANSLATE;1637} else {1638transformState = TRANSFORM_ANY_TRANSLATE;1639}1640} else if ((type & (AffineTransform.TYPE_FLIP |1641AffineTransform.TYPE_MASK_ROTATION |1642AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0)1643{1644transformState = TRANSFORM_TRANSLATESCALE;1645transX = transY = 0;1646} else {1647transformState = TRANSFORM_GENERIC;1648transX = transY = 0;1649}16501651if (transformState >= TRANSFORM_TRANSLATESCALE ||1652origTransformState >= TRANSFORM_TRANSLATESCALE)1653{1654/* Its only in this case that the previous or current transform1655* was more than a translate that font info is invalidated1656*/1657cachedFRC = null;1658this.validFontInfo = false;1659this.fontMetrics = null;1660this.glyphVectorFontInfo = null;16611662if (transformState != origTransformState) {1663invalidatePipe();1664}1665}1666if (strokeState != STROKE_CUSTOM) {1667validateBasicStroke((BasicStroke) stroke);1668}1669}16701671/**1672* Returns the current Transform in the Graphics2D state.1673* @see #transform1674* @see #setTransform1675*/1676@Override1677public AffineTransform getTransform() {1678if ((constrainX | constrainY) == 0) {1679return new AffineTransform(transform);1680}1681AffineTransform tx1682= AffineTransform.getTranslateInstance(-constrainX, -constrainY);1683tx.concatenate(transform);1684return tx;1685}16861687/**1688* Returns the current Transform ignoring the "constrain"1689* rectangle.1690*/1691public AffineTransform cloneTransform() {1692return new AffineTransform(transform);1693}16941695/**1696* Returns the current Paint in the Graphics2D state.1697* @see #setPaint1698* @see java.awt.Graphics#setColor1699*/1700public Paint getPaint() {1701return paint;1702}17031704/**1705* Returns the current Composite in the Graphics2D state.1706* @see #setComposite1707*/1708public Composite getComposite() {1709return composite;1710}17111712public Color getColor() {1713return foregroundColor;1714}17151716/*1717* Validate the eargb and pixel fields against the current color.1718*1719* The eargb field must take into account the extraAlpha1720* value of an AlphaComposite. It may also take into account1721* the Fsrc Porter-Duff blending function if such a function is1722* a constant (see handling of Clear mode below). For instance,1723* by factoring in the (Fsrc == 0) state of the Clear mode we can1724* use a SrcNoEa loop just as easily as a general Alpha loop1725* since the math will be the same in both cases.1726*1727* The pixel field will always be the best pixel data choice for1728* the final result of all calculations applied to the eargb field.1729*1730* Note that this method is only necessary under the following1731* conditions:1732* (paintState <= PAINT_ALPHA_COLOR &&1733* compositeState <= COMP_CUSTOM)1734* though nothing bad will happen if it is run in other states.1735*/1736void validateColor() {1737int eargb;1738if (imageComp == CompositeType.Clear) {1739eargb = 0;1740} else {1741eargb = foregroundColor.getRGB();1742if (compositeState <= COMP_ALPHA &&1743imageComp != CompositeType.SrcNoEa &&1744imageComp != CompositeType.SrcOverNoEa)1745{1746AlphaComposite alphacomp = (AlphaComposite) composite;1747int a = Math.round(alphacomp.getAlpha() * (eargb >>> 24));1748eargb = (eargb & 0x00ffffff) | (a << 24);1749}1750}1751this.eargb = eargb;1752this.pixel = surfaceData.pixelFor(eargb);1753}17541755public void setColor(Color color) {1756if (color == null || color == paint) {1757return;1758}1759this.paint = foregroundColor = color;1760validateColor();1761if ((eargb >> 24) == -1) {1762if (paintState == PAINT_OPAQUECOLOR) {1763return;1764}1765paintState = PAINT_OPAQUECOLOR;1766if (imageComp == CompositeType.SrcOverNoEa) {1767// special case where compState depends on opacity of paint1768compositeState = COMP_ISCOPY;1769}1770} else {1771if (paintState == PAINT_ALPHACOLOR) {1772return;1773}1774paintState = PAINT_ALPHACOLOR;1775if (imageComp == CompositeType.SrcOverNoEa) {1776// special case where compState depends on opacity of paint1777compositeState = COMP_ALPHA;1778}1779}1780validFontInfo = false;1781invalidatePipe();1782}17831784/**1785* Sets the background color in this context used for clearing a region.1786* When Graphics2D is constructed for a component, the backgroung color is1787* inherited from the component. Setting the background color in the1788* Graphics2D context only affects the subsequent clearRect() calls and1789* not the background color of the component. To change the background1790* of the component, use appropriate methods of the component.1791* @param color The background color that should be used in1792* subsequent calls to clearRect().1793* @see #getBackground1794* @see Graphics#clearRect1795*/1796public void setBackground(Color color) {1797backgroundColor = color;1798}17991800/**1801* Returns the background color used for clearing a region.1802* @see #setBackground1803*/1804public Color getBackground() {1805return backgroundColor;1806}18071808/**1809* Returns the current Stroke in the Graphics2D state.1810* @see #setStroke1811*/1812public Stroke getStroke() {1813return stroke;1814}18151816public Rectangle getClipBounds() {1817if (clipState == CLIP_DEVICE) {1818return null;1819}1820return getClipBounds(new Rectangle());1821}18221823public Rectangle getClipBounds(Rectangle r) {1824if (clipState != CLIP_DEVICE) {1825if (transformState <= TRANSFORM_INT_TRANSLATE) {1826if (usrClip instanceof Rectangle) {1827r.setBounds((Rectangle) usrClip);1828} else {1829r.setFrame(usrClip.getBounds2D());1830}1831r.translate(-transX, -transY);1832} else {1833r.setFrame(getClip().getBounds2D());1834}1835} else if (r == null) {1836throw new NullPointerException("null rectangle parameter");1837}1838return r;1839}18401841public boolean hitClip(int x, int y, int width, int height) {1842if (width <= 0 || height <= 0) {1843return false;1844}1845if (transformState > TRANSFORM_INT_TRANSLATE) {1846// Note: Technically the most accurate test would be to1847// raster scan the parallelogram of the transformed rectangle1848// and do a span for span hit test against the clip, but for1849// speed we approximate the test with a bounding box of the1850// transformed rectangle. The cost of rasterizing the1851// transformed rectangle is probably high enough that it is1852// not worth doing so to save the caller from having to call1853// a rendering method where we will end up discovering the1854// same answer in about the same amount of time anyway.1855// This logic breaks down if this hit test is being performed1856// on the bounds of a group of shapes in which case it might1857// be beneficial to be a little more accurate to avoid lots1858// of subsequent rendering calls. In either case, this relaxed1859// test should not be significantly less accurate than the1860// optimal test for most transforms and so the conservative1861// answer should not cause too much extra work.18621863double[] d = {1864x, y,1865x+width, y,1866x, y+height,1867x+width, y+height1868};1869transform.transform(d, 0, d, 0, 4);1870x = (int) Math.floor(Math.min(Math.min(d[0], d[2]),1871Math.min(d[4], d[6])));1872y = (int) Math.floor(Math.min(Math.min(d[1], d[3]),1873Math.min(d[5], d[7])));1874width = (int) Math.ceil(Math.max(Math.max(d[0], d[2]),1875Math.max(d[4], d[6])));1876height = (int) Math.ceil(Math.max(Math.max(d[1], d[3]),1877Math.max(d[5], d[7])));1878} else {1879x += transX;1880y += transY;1881width += x;1882height += y;1883}18841885try {1886if (!getCompClip().intersectsQuickCheckXYXY(x, y, width, height)) {1887return false;1888}1889} catch (InvalidPipeException e) {1890return false;1891}1892// REMIND: We could go one step further here and examine the1893// non-rectangular clip shape more closely if there is one.1894// Since the clip has already been rasterized, the performance1895// penalty of doing the scan is probably still within the bounds1896// of a good tradeoff between speed and quality of the answer.1897return true;1898}18991900protected void validateCompClip() {1901int origClipState = clipState;1902if (usrClip == null) {1903clipState = CLIP_DEVICE;1904clipRegion = devClip;1905} else if (usrClip instanceof Rectangle2D) {1906clipState = CLIP_RECTANGULAR;1907clipRegion = devClip.getIntersection((Rectangle2D) usrClip);1908} else {1909PathIterator cpi = usrClip.getPathIterator(null);1910int[] box = new int[4];1911ShapeSpanIterator sr = LoopPipe.getFillSSI(this);1912try {1913sr.setOutputArea(devClip);1914sr.appendPath(cpi);1915sr.getPathBox(box);1916Region r = Region.getInstance(box, sr);1917clipRegion = r;1918clipState =1919r.isRectangular() ? CLIP_RECTANGULAR : CLIP_SHAPE;1920} finally {1921sr.dispose();1922}1923}1924if (origClipState != clipState &&1925(clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE))1926{1927validFontInfo = false;1928invalidatePipe();1929}1930}19311932static final int NON_RECTILINEAR_TRANSFORM_MASK =1933(AffineTransform.TYPE_GENERAL_TRANSFORM |1934AffineTransform.TYPE_GENERAL_ROTATION);19351936protected Shape transformShape(Shape s) {1937if (s == null) {1938return null;1939}1940if (transformState > TRANSFORM_INT_TRANSLATE) {1941return transformShape(transform, s);1942} else {1943return transformShape(transX, transY, s);1944}1945}19461947public Shape untransformShape(Shape s) {1948if (s == null) {1949return null;1950}1951if (transformState > TRANSFORM_INT_TRANSLATE) {1952try {1953return transformShape(transform.createInverse(), s);1954} catch (NoninvertibleTransformException e) {1955return null;1956}1957} else {1958return transformShape(-transX, -transY, s);1959}1960}19611962protected static Shape transformShape(int tx, int ty, Shape s) {1963if (s == null) {1964return null;1965}19661967if (s instanceof Rectangle) {1968Rectangle r = s.getBounds();1969r.translate(tx, ty);1970return r;1971}1972if (s instanceof Rectangle2D) {1973Rectangle2D rect = (Rectangle2D) s;1974return new Rectangle2D.Double(rect.getX() + tx,1975rect.getY() + ty,1976rect.getWidth(),1977rect.getHeight());1978}19791980if (tx == 0 && ty == 0) {1981return cloneShape(s);1982}19831984AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty);1985return mat.createTransformedShape(s);1986}19871988protected static Shape transformShape(AffineTransform tx, Shape clip) {1989if (clip == null) {1990return null;1991}19921993if (clip instanceof Rectangle2D &&1994(tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0)1995{1996Rectangle2D rect = (Rectangle2D) clip;1997double[] matrix = new double[4];1998matrix[0] = rect.getX();1999matrix[1] = rect.getY();2000matrix[2] = matrix[0] + rect.getWidth();2001matrix[3] = matrix[1] + rect.getHeight();2002tx.transform(matrix, 0, matrix, 0, 2);2003fixRectangleOrientation(matrix, rect);2004return new Rectangle2D.Double(matrix[0], matrix[1],2005matrix[2] - matrix[0],2006matrix[3] - matrix[1]);2007}20082009if (tx.isIdentity()) {2010return cloneShape(clip);2011}20122013return tx.createTransformedShape(clip);2014}20152016/**2017* Sets orientation of the rectangle according to the clip.2018*/2019private static void fixRectangleOrientation(double[] m, Rectangle2D clip) {2020if (clip.getWidth() > 0 != (m[2] - m[0] > 0)) {2021double t = m[0];2022m[0] = m[2];2023m[2] = t;2024}2025if (clip.getHeight() > 0 != (m[3] - m[1] > 0)) {2026double t = m[1];2027m[1] = m[3];2028m[3] = t;2029}2030}20312032public void clipRect(int x, int y, int w, int h) {2033clip(new Rectangle(x, y, w, h));2034}20352036public void setClip(int x, int y, int w, int h) {2037setClip(new Rectangle(x, y, w, h));2038}20392040public Shape getClip() {2041return untransformShape(usrClip);2042}20432044public void setClip(Shape sh) {2045usrClip = transformShape(sh);2046validateCompClip();2047}20482049/**2050* Intersects the current clip with the specified Path and sets the2051* current clip to the resulting intersection. The clip is transformed2052* with the current transform in the Graphics2D state before being2053* intersected with the current clip. This method is used to make the2054* current clip smaller. To make the clip larger, use any setClip method.2055* @param s The Path to be intersected with the current clip.2056*/2057public void clip(Shape s) {2058s = transformShape(s);2059if (usrClip != null) {2060s = intersectShapes(usrClip, s, true, true);2061}2062usrClip = s;2063validateCompClip();2064}20652066public void setPaintMode() {2067setComposite(AlphaComposite.SrcOver);2068}20692070public void setXORMode(Color c) {2071if (c == null) {2072throw new IllegalArgumentException("null XORColor");2073}2074setComposite(new XORComposite(c, surfaceData));2075}20762077Blit lastCAblit;2078Composite lastCAcomp;20792080public void copyArea(int x, int y, int w, int h, int dx, int dy) {2081try {2082doCopyArea(x, y, w, h, dx, dy);2083} catch (InvalidPipeException e) {2084try {2085revalidateAll();2086doCopyArea(x, y, w, h, dx, dy);2087} catch (InvalidPipeException e2) {2088// Still catching the exception; we are not yet ready to2089// validate the surfaceData correctly. Fail for now and2090// try again next time around.2091}2092} finally {2093surfaceData.markDirty();2094}2095}20962097private void doCopyArea(int x, int y, int w, int h, int dx, int dy) {2098if (w <= 0 || h <= 0) {2099return;2100}21012102if (transformState == SunGraphics2D.TRANSFORM_ISIDENT) {2103// do nothing2104} else if (transformState <= SunGraphics2D.TRANSFORM_ANY_TRANSLATE) {2105x += transX;2106y += transY;2107} else if (transformState == SunGraphics2D.TRANSFORM_TRANSLATESCALE) {2108final double[] coords = {x, y, x + w, y + h, x + dx, y + dy};2109transform.transform(coords, 0, coords, 0, 3);2110x = (int) Math.ceil(coords[0] - 0.5);2111y = (int) Math.ceil(coords[1] - 0.5);2112w = ((int) Math.ceil(coords[2] - 0.5)) - x;2113h = ((int) Math.ceil(coords[3] - 0.5)) - y;2114dx = ((int) Math.ceil(coords[4] - 0.5)) - x;2115dy = ((int) Math.ceil(coords[5] - 0.5)) - y;2116// In case of negative scale transform, reflect the rect coords.2117if (w < 0) {2118w = -w;2119x -= w;2120}2121if (h < 0) {2122h = -h;2123y -= h;2124}2125} else {2126throw new InternalError("transformed copyArea not implemented yet");2127}21282129SurfaceData theData = surfaceData;2130if (theData.copyArea(this, x, y, w, h, dx, dy)) {2131return;2132}21332134// REMIND: This method does not deal with missing data from the2135// source object (i.e. it does not send exposure events...)21362137Region clip = getCompClip();21382139Composite comp = composite;2140if (lastCAcomp != comp) {2141SurfaceType dsttype = theData.getSurfaceType();2142CompositeType comptype = imageComp;2143if (CompositeType.SrcOverNoEa.equals(comptype) &&2144theData.getTransparency() == Transparency.OPAQUE)2145{2146comptype = CompositeType.SrcNoEa;2147}2148lastCAblit = Blit.locate(dsttype, comptype, dsttype);2149lastCAcomp = comp;2150}21512152Blit ob = lastCAblit;2153if (dy == 0 && dx > 0 && dx < w) {2154while (w > 0) {2155int partW = Math.min(w, dx);2156w -= partW;2157int sx = x + w;2158ob.Blit(theData, theData, comp, clip,2159sx, y, sx+dx, y+dy, partW, h);2160}2161return;2162}2163if (dy > 0 && dy < h && dx > -w && dx < w) {2164while (h > 0) {2165int partH = Math.min(h, dy);2166h -= partH;2167int sy = y + h;2168ob.Blit(theData, theData, comp, clip,2169x, sy, x+dx, sy+dy, w, partH);2170}2171return;2172}2173ob.Blit(theData, theData, comp, clip, x, y, x+dx, y+dy, w, h);2174}21752176/*2177public void XcopyArea(int x, int y, int w, int h, int dx, int dy) {2178Rectangle rect = new Rectangle(x, y, w, h);2179rect = transformBounds(rect, transform);2180Point2D point = new Point2D.Float(dx, dy);2181Point2D root = new Point2D.Float(0, 0);2182point = transform.transform(point, point);2183root = transform.transform(root, root);2184int fdx = (int)(point.getX()-root.getX());2185int fdy = (int)(point.getY()-root.getY());21862187Rectangle r = getCompBounds().intersection(rect.getBounds());21882189if (r.isEmpty()) {2190return;2191}21922193// Begin Rasterizer for Clip Shape2194boolean skipClip = true;2195byte[] clipAlpha = null;21962197if (clipState == CLIP_SHAPE) {21982199int box[] = new int[4];22002201clipRegion.getBounds(box);2202Rectangle devR = new Rectangle(box[0], box[1],2203box[2] - box[0],2204box[3] - box[1]);2205if (!devR.isEmpty()) {2206OutputManager mgr = getOutputManager();2207RegionIterator ri = clipRegion.getIterator();2208while (ri.nextYRange(box)) {2209int spany = box[1];2210int spanh = box[3] - spany;2211while (ri.nextXBand(box)) {2212int spanx = box[0];2213int spanw = box[2] - spanx;2214mgr.copyArea(this, null,2215spanw, 0,2216spanx, spany,2217spanw, spanh,2218fdx, fdy,2219null);2220}2221}2222}2223return;2224}2225// End Rasterizer for Clip Shape22262227getOutputManager().copyArea(this, null,2228r.width, 0,2229r.x, r.y, r.width,2230r.height, fdx, fdy,2231null);2232}2233*/22342235public void drawLine(int x1, int y1, int x2, int y2) {2236try {2237drawpipe.drawLine(this, x1, y1, x2, y2);2238} catch (InvalidPipeException e) {2239try {2240revalidateAll();2241drawpipe.drawLine(this, x1, y1, x2, y2);2242} catch (InvalidPipeException e2) {2243// Still catching the exception; we are not yet ready to2244// validate the surfaceData correctly. Fail for now and2245// try again next time around.2246}2247} finally {2248surfaceData.markDirty();2249}2250}22512252public void drawRoundRect(int x, int y, int w, int h, int arcW, int arcH) {2253try {2254drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);2255} catch (InvalidPipeException e) {2256try {2257revalidateAll();2258drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);2259} catch (InvalidPipeException e2) {2260// Still catching the exception; we are not yet ready to2261// validate the surfaceData correctly. Fail for now and2262// try again next time around.2263}2264} finally {2265surfaceData.markDirty();2266}2267}22682269public void fillRoundRect(int x, int y, int w, int h, int arcW, int arcH) {2270try {2271fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);2272} catch (InvalidPipeException e) {2273try {2274revalidateAll();2275fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);2276} catch (InvalidPipeException e2) {2277// Still catching the exception; we are not yet ready to2278// validate the surfaceData correctly. Fail for now and2279// try again next time around.2280}2281} finally {2282surfaceData.markDirty();2283}2284}22852286public void drawOval(int x, int y, int w, int h) {2287try {2288drawpipe.drawOval(this, x, y, w, h);2289} catch (InvalidPipeException e) {2290try {2291revalidateAll();2292drawpipe.drawOval(this, x, y, w, h);2293} catch (InvalidPipeException e2) {2294// Still catching the exception; we are not yet ready to2295// validate the surfaceData correctly. Fail for now and2296// try again next time around.2297}2298} finally {2299surfaceData.markDirty();2300}2301}23022303public void fillOval(int x, int y, int w, int h) {2304try {2305fillpipe.fillOval(this, x, y, w, h);2306} catch (InvalidPipeException e) {2307try {2308revalidateAll();2309fillpipe.fillOval(this, x, y, w, h);2310} catch (InvalidPipeException e2) {2311// Still catching the exception; we are not yet ready to2312// validate the surfaceData correctly. Fail for now and2313// try again next time around.2314}2315} finally {2316surfaceData.markDirty();2317}2318}23192320public void drawArc(int x, int y, int w, int h,2321int startAngl, int arcAngl) {2322try {2323drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);2324} catch (InvalidPipeException e) {2325try {2326revalidateAll();2327drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);2328} catch (InvalidPipeException e2) {2329// Still catching the exception; we are not yet ready to2330// validate the surfaceData correctly. Fail for now and2331// try again next time around.2332}2333} finally {2334surfaceData.markDirty();2335}2336}23372338public void fillArc(int x, int y, int w, int h,2339int startAngl, int arcAngl) {2340try {2341fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);2342} catch (InvalidPipeException e) {2343try {2344revalidateAll();2345fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);2346} catch (InvalidPipeException e2) {2347// Still catching the exception; we are not yet ready to2348// validate the surfaceData correctly. Fail for now and2349// try again next time around.2350}2351} finally {2352surfaceData.markDirty();2353}2354}23552356public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {2357try {2358drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);2359} catch (InvalidPipeException e) {2360try {2361revalidateAll();2362drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);2363} catch (InvalidPipeException e2) {2364// Still catching the exception; we are not yet ready to2365// validate the surfaceData correctly. Fail for now and2366// try again next time around.2367}2368} finally {2369surfaceData.markDirty();2370}2371}23722373public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {2374try {2375drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);2376} catch (InvalidPipeException e) {2377try {2378revalidateAll();2379drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);2380} catch (InvalidPipeException e2) {2381// Still catching the exception; we are not yet ready to2382// validate the surfaceData correctly. Fail for now and2383// try again next time around.2384}2385} finally {2386surfaceData.markDirty();2387}2388}23892390public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {2391try {2392fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);2393} catch (InvalidPipeException e) {2394try {2395revalidateAll();2396fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);2397} catch (InvalidPipeException e2) {2398// Still catching the exception; we are not yet ready to2399// validate the surfaceData correctly. Fail for now and2400// try again next time around.2401}2402} finally {2403surfaceData.markDirty();2404}2405}24062407public void drawRect (int x, int y, int w, int h) {2408try {2409drawpipe.drawRect(this, x, y, w, h);2410} catch (InvalidPipeException e) {2411try {2412revalidateAll();2413drawpipe.drawRect(this, x, y, w, h);2414} catch (InvalidPipeException e2) {2415// Still catching the exception; we are not yet ready to2416// validate the surfaceData correctly. Fail for now and2417// try again next time around.2418}2419} finally {2420surfaceData.markDirty();2421}2422}24232424public void fillRect (int x, int y, int w, int h) {2425try {2426fillpipe.fillRect(this, x, y, w, h);2427} catch (InvalidPipeException e) {2428try {2429revalidateAll();2430fillpipe.fillRect(this, x, y, w, h);2431} catch (InvalidPipeException e2) {2432// Still catching the exception; we are not yet ready to2433// validate the surfaceData correctly. Fail for now and2434// try again next time around.2435}2436} finally {2437surfaceData.markDirty();2438}2439}24402441private void revalidateAll() {2442try {2443// REMIND: This locking needs to be done around the2444// caller of this method so that the pipe stays valid2445// long enough to call the new primitive.2446// REMIND: No locking yet in screen SurfaceData objects!2447// surfaceData.lock();2448surfaceData = surfaceData.getReplacement();2449if (surfaceData == null) {2450surfaceData = NullSurfaceData.theInstance;2451}24522453invalidatePipe();24542455// this will recalculate the composite clip2456setDevClip(surfaceData.getBounds());24572458if (paintState <= PAINT_ALPHACOLOR) {2459validateColor();2460}2461if (composite instanceof XORComposite) {2462Color c = ((XORComposite) composite).getXorColor();2463setComposite(new XORComposite(c, surfaceData));2464}2465validatePipe();2466} finally {2467// REMIND: No locking yet in screen SurfaceData objects!2468// surfaceData.unlock();2469}2470}24712472public void clearRect(int x, int y, int w, int h) {2473// REMIND: has some "interesting" consequences if threads are2474// not synchronized2475Composite c = composite;2476Paint p = paint;2477setComposite(AlphaComposite.Src);2478setColor(getBackground());2479fillRect(x, y, w, h);2480setPaint(p);2481setComposite(c);2482}24832484/**2485* Strokes the outline of a Path using the settings of the current2486* graphics state. The rendering attributes applied include the2487* clip, transform, paint or color, composite and stroke attributes.2488* @param s The path to be drawn.2489* @see #setStroke2490* @see #setPaint2491* @see java.awt.Graphics#setColor2492* @see #transform2493* @see #setTransform2494* @see #clip2495* @see #setClip2496* @see #setComposite2497*/2498public void draw(Shape s) {2499try {2500shapepipe.draw(this, s);2501} catch (InvalidPipeException e) {2502try {2503revalidateAll();2504shapepipe.draw(this, s);2505} catch (InvalidPipeException e2) {2506// Still catching the exception; we are not yet ready to2507// validate the surfaceData correctly. Fail for now and2508// try again next time around.2509}2510} finally {2511surfaceData.markDirty();2512}2513}251425152516/**2517* Fills the interior of a Path using the settings of the current2518* graphics state. The rendering attributes applied include the2519* clip, transform, paint or color, and composite.2520* @see #setPaint2521* @see java.awt.Graphics#setColor2522* @see #transform2523* @see #setTransform2524* @see #setComposite2525* @see #clip2526* @see #setClip2527*/2528public void fill(Shape s) {2529try {2530shapepipe.fill(this, s);2531} catch (InvalidPipeException e) {2532try {2533revalidateAll();2534shapepipe.fill(this, s);2535} catch (InvalidPipeException e2) {2536// Still catching the exception; we are not yet ready to2537// validate the surfaceData correctly. Fail for now and2538// try again next time around.2539}2540} finally {2541surfaceData.markDirty();2542}2543}25442545/**2546* Returns true if the given AffineTransform is an integer2547* translation.2548*/2549private static boolean isIntegerTranslation(AffineTransform xform) {2550if (xform.isIdentity()) {2551return true;2552}2553if (xform.getType() == AffineTransform.TYPE_TRANSLATION) {2554double tx = xform.getTranslateX();2555double ty = xform.getTranslateY();2556return (tx == (int)tx && ty == (int)ty);2557}2558return false;2559}25602561/**2562* Returns the index of the tile corresponding to the supplied position2563* given the tile grid offset and size along the same axis.2564*/2565private static int getTileIndex(int p, int tileGridOffset, int tileSize) {2566p -= tileGridOffset;2567if (p < 0) {2568p += 1 - tileSize; // force round to -infinity (ceiling)2569}2570return p/tileSize;2571}25722573/**2574* Returns a rectangle in image coordinates that may be required2575* in order to draw the given image into the given clipping region2576* through a pair of AffineTransforms. In addition, horizontal and2577* vertical padding factors for antialising and interpolation may2578* be used.2579*/2580private static Rectangle getImageRegion(RenderedImage img,2581Region compClip,2582AffineTransform transform,2583AffineTransform xform,2584int padX, int padY) {2585Rectangle imageRect =2586new Rectangle(img.getMinX(), img.getMinY(),2587img.getWidth(), img.getHeight());25882589Rectangle result = null;2590try {2591double[] p = new double[8];2592p[0] = p[2] = compClip.getLoX();2593p[4] = p[6] = compClip.getHiX();2594p[1] = p[5] = compClip.getLoY();2595p[3] = p[7] = compClip.getHiY();25962597// Inverse transform the output bounding rect2598transform.inverseTransform(p, 0, p, 0, 4);2599xform.inverseTransform(p, 0, p, 0, 4);26002601// Determine a bounding box for the inverse transformed region2602double x0,x1,y0,y1;2603x0 = x1 = p[0];2604y0 = y1 = p[1];26052606for (int i = 2; i < 8; ) {2607double pt = p[i++];2608if (pt < x0) {2609x0 = pt;2610} else if (pt > x1) {2611x1 = pt;2612}2613pt = p[i++];2614if (pt < y0) {2615y0 = pt;2616} else if (pt > y1) {2617y1 = pt;2618}2619}26202621// This is padding for anti-aliasing and such. It may2622// be more than is needed.2623int x = (int)x0 - padX;2624int w = (int)(x1 - x0 + 2*padX);2625int y = (int)y0 - padY;2626int h = (int)(y1 - y0 + 2*padY);26272628Rectangle clipRect = new Rectangle(x,y,w,h);2629result = clipRect.intersection(imageRect);2630} catch (NoninvertibleTransformException nte) {2631// Worst case bounds are the bounds of the image.2632result = imageRect;2633}26342635return result;2636}26372638/**2639* Draws an image, applying a transform from image space into user space2640* before drawing.2641* The transformation from user space into device space is done with2642* the current transform in the Graphics2D.2643* The given transformation is applied to the image before the2644* transform attribute in the Graphics2D state is applied.2645* The rendering attributes applied include the clip, transform,2646* and composite attributes. Note that the result is2647* undefined, if the given transform is noninvertible.2648* @param img The image to be drawn. Does nothing if img is null.2649* @param xform The transformation from image space into user space.2650* @see #transform2651* @see #setTransform2652* @see #setComposite2653* @see #clip2654* @see #setClip2655*/2656public void drawRenderedImage(RenderedImage img,2657AffineTransform xform) {26582659if (img == null) {2660return;2661}26622663// BufferedImage case: use a simple drawImage call2664if (img instanceof BufferedImage) {2665BufferedImage bufImg = (BufferedImage)img;2666drawImage(bufImg,xform,null);2667return;2668}26692670// transformState tracks the state of transform and2671// transX, transY contain the integer casts of the2672// translation factors2673boolean isIntegerTranslate =2674(transformState <= TRANSFORM_INT_TRANSLATE) &&2675isIntegerTranslation(xform);26762677// Include padding for interpolation/antialiasing if necessary2678int pad = isIntegerTranslate ? 0 : 3;26792680Region clip;2681try {2682clip = getCompClip();2683} catch (InvalidPipeException e) {2684return;2685}26862687// Determine the region of the image that may contribute to2688// the clipped drawing area2689Rectangle region = getImageRegion(img,2690clip,2691transform,2692xform,2693pad, pad);2694if (region.width <= 0 || region.height <= 0) {2695return;2696}26972698// Attempt to optimize integer translation of tiled images.2699// Although theoretically we are O.K. if the concatenation of2700// the user transform and the device transform is an integer2701// translation, we'll play it safe and only optimize the case2702// where both are integer translations.2703if (isIntegerTranslate) {2704// Use optimized code2705// Note that drawTranslatedRenderedImage calls copyImage2706// which takes the user space to device space transform into2707// account, but we need to provide the image space to user space2708// translations.27092710drawTranslatedRenderedImage(img, region,2711(int) xform.getTranslateX(),2712(int) xform.getTranslateY());2713return;2714}27152716// General case: cobble the necessary region into a single Raster2717Raster raster = img.getData(region);27182719// Make a new Raster with the same contents as raster2720// but starting at (0, 0). This raster is thus in the same2721// coordinate system as the SampleModel of the original raster.2722WritableRaster wRaster =2723Raster.createWritableRaster(raster.getSampleModel(),2724raster.getDataBuffer(),2725null);27262727// If the original raster was in a different coordinate2728// system than its SampleModel, we need to perform an2729// additional translation in order to get the (minX, minY)2730// pixel of raster to be pixel (0, 0) of wRaster. We also2731// have to have the correct width and height.2732int minX = raster.getMinX();2733int minY = raster.getMinY();2734int width = raster.getWidth();2735int height = raster.getHeight();2736int px = minX - raster.getSampleModelTranslateX();2737int py = minY - raster.getSampleModelTranslateY();2738if (px != 0 || py != 0 || width != wRaster.getWidth() ||2739height != wRaster.getHeight()) {2740wRaster =2741wRaster.createWritableChild(px,2742py,2743width,2744height,27450, 0,2746null);2747}27482749// Now we have a BufferedImage starting at (0, 0)2750// with the same contents that started at (minX, minY)2751// in raster. So we must draw the BufferedImage with a2752// translation of (minX, minY).2753AffineTransform transXform = (AffineTransform)xform.clone();2754transXform.translate(minX, minY);27552756ColorModel cm = img.getColorModel();2757BufferedImage bufImg = new BufferedImage(cm,2758wRaster,2759cm.isAlphaPremultiplied(),2760null);2761drawImage(bufImg, transXform, null);2762}27632764/**2765* Intersects {@code destRect} with {@code clip} and2766* overwrites {@code destRect} with the result.2767* Returns false if the intersection was empty, true otherwise.2768*/2769private boolean clipTo(Rectangle destRect, Rectangle clip) {2770int x1 = Math.max(destRect.x, clip.x);2771int x2 = Math.min(destRect.x + destRect.width, clip.x + clip.width);2772int y1 = Math.max(destRect.y, clip.y);2773int y2 = Math.min(destRect.y + destRect.height, clip.y + clip.height);2774if (((x2 - x1) < 0) || ((y2 - y1) < 0)) {2775destRect.width = -1; // Set both just to be safe2776destRect.height = -1;2777return false;2778} else {2779destRect.x = x1;2780destRect.y = y1;2781destRect.width = x2 - x1;2782destRect.height = y2 - y1;2783return true;2784}2785}27862787/**2788* Draw a portion of a RenderedImage tile-by-tile with a given2789* integer image to user space translation. The user to2790* device transform must also be an integer translation.2791*/2792private void drawTranslatedRenderedImage(RenderedImage img,2793Rectangle region,2794int i2uTransX,2795int i2uTransY) {2796// Cache tile grid info2797int tileGridXOffset = img.getTileGridXOffset();2798int tileGridYOffset = img.getTileGridYOffset();2799int tileWidth = img.getTileWidth();2800int tileHeight = img.getTileHeight();28012802// Determine the tile index extrema in each direction2803int minTileX =2804getTileIndex(region.x, tileGridXOffset, tileWidth);2805int minTileY =2806getTileIndex(region.y, tileGridYOffset, tileHeight);2807int maxTileX =2808getTileIndex(region.x + region.width - 1,2809tileGridXOffset, tileWidth);2810int maxTileY =2811getTileIndex(region.y + region.height - 1,2812tileGridYOffset, tileHeight);28132814// Create a single ColorModel to use for all BufferedImages2815ColorModel colorModel = img.getColorModel();28162817// Reuse the same Rectangle for each iteration2818Rectangle tileRect = new Rectangle();28192820for (int ty = minTileY; ty <= maxTileY; ty++) {2821for (int tx = minTileX; tx <= maxTileX; tx++) {2822// Get the current tile.2823Raster raster = img.getTile(tx, ty);28242825// Fill in tileRect with the tile bounds2826tileRect.x = tx*tileWidth + tileGridXOffset;2827tileRect.y = ty*tileHeight + tileGridYOffset;2828tileRect.width = tileWidth;2829tileRect.height = tileHeight;28302831// Clip the tile against the image bounds and2832// backwards mapped clip region2833// The result can't be empty2834clipTo(tileRect, region);28352836// Create a WritableRaster containing the tile2837WritableRaster wRaster = null;2838if (raster instanceof WritableRaster) {2839wRaster = (WritableRaster)raster;2840} else {2841// Create a WritableRaster in the same coordinate system2842// as the original raster.2843wRaster =2844Raster.createWritableRaster(raster.getSampleModel(),2845raster.getDataBuffer(),2846null);2847}28482849// Translate wRaster to start at (0, 0) and to contain2850// only the relevent portion of the tile2851wRaster = wRaster.createWritableChild(tileRect.x, tileRect.y,2852tileRect.width,2853tileRect.height,28540, 0,2855null);28562857// Wrap wRaster in a BufferedImage2858BufferedImage bufImg =2859new BufferedImage(colorModel,2860wRaster,2861colorModel.isAlphaPremultiplied(),2862null);2863// Now we have a BufferedImage starting at (0, 0) that2864// represents data from a Raster starting at2865// (tileRect.x, tileRect.y). Additionally, it needs2866// to be translated by (i2uTransX, i2uTransY). We call2867// copyImage to draw just the region of interest2868// without needing to create a child image.2869copyImage(bufImg, tileRect.x + i2uTransX,2870tileRect.y + i2uTransY, 0, 0, tileRect.width,2871tileRect.height, null, null);2872}2873}2874}28752876public void drawRenderableImage(RenderableImage img,2877AffineTransform xform) {28782879if (img == null) {2880return;2881}28822883AffineTransform pipeTransform = transform;2884AffineTransform concatTransform = new AffineTransform(xform);2885concatTransform.concatenate(pipeTransform);2886AffineTransform reverseTransform;28872888RenderContext rc = new RenderContext(concatTransform);28892890try {2891reverseTransform = pipeTransform.createInverse();2892} catch (NoninvertibleTransformException nte) {2893rc = new RenderContext(pipeTransform);2894reverseTransform = new AffineTransform();2895}28962897RenderedImage rendering = img.createRendering(rc);2898drawRenderedImage(rendering,reverseTransform);2899}2900290129022903/*2904* Transform the bounding box of the BufferedImage2905*/2906protected Rectangle transformBounds(Rectangle rect,2907AffineTransform tx) {2908if (tx.isIdentity()) {2909return rect;2910}29112912Shape s = transformShape(tx, rect);2913return s.getBounds();2914}29152916// text rendering methods2917public void drawString(String str, int x, int y) {2918if (str == null) {2919throw new NullPointerException("String is null");2920}29212922if (font.hasLayoutAttributes()) {2923if (str.length() == 0) {2924return;2925}2926new TextLayout(str, font, getFontRenderContext()).draw(this, x, y);2927return;2928}29292930try {2931textpipe.drawString(this, str, x, y);2932} catch (InvalidPipeException e) {2933try {2934revalidateAll();2935textpipe.drawString(this, str, x, y);2936} catch (InvalidPipeException e2) {2937// Still catching the exception; we are not yet ready to2938// validate the surfaceData correctly. Fail for now and2939// try again next time around.2940}2941} finally {2942surfaceData.markDirty();2943}2944}29452946public void drawString(String str, float x, float y) {2947if (str == null) {2948throw new NullPointerException("String is null");2949}29502951if (font.hasLayoutAttributes()) {2952if (str.length() == 0) {2953return;2954}2955new TextLayout(str, font, getFontRenderContext()).draw(this, x, y);2956return;2957}29582959try {2960textpipe.drawString(this, str, x, y);2961} catch (InvalidPipeException e) {2962try {2963revalidateAll();2964textpipe.drawString(this, str, x, y);2965} catch (InvalidPipeException e2) {2966// Still catching the exception; we are not yet ready to2967// validate the surfaceData correctly. Fail for now and2968// try again next time around.2969}2970} finally {2971surfaceData.markDirty();2972}2973}29742975public void drawString(AttributedCharacterIterator iterator,2976int x, int y) {2977if (iterator == null) {2978throw new NullPointerException("AttributedCharacterIterator is null");2979}2980if (iterator.getBeginIndex() == iterator.getEndIndex()) {2981return; /* nothing to draw */2982}2983TextLayout tl = new TextLayout(iterator, getFontRenderContext());2984tl.draw(this, (float) x, (float) y);2985}29862987public void drawString(AttributedCharacterIterator iterator,2988float x, float y) {2989if (iterator == null) {2990throw new NullPointerException("AttributedCharacterIterator is null");2991}2992if (iterator.getBeginIndex() == iterator.getEndIndex()) {2993return; /* nothing to draw */2994}2995TextLayout tl = new TextLayout(iterator, getFontRenderContext());2996tl.draw(this, x, y);2997}29982999public void drawGlyphVector(GlyphVector gv, float x, float y)3000{3001if (gv == null) {3002throw new NullPointerException("GlyphVector is null");3003}30043005try {3006textpipe.drawGlyphVector(this, gv, x, y);3007} catch (InvalidPipeException e) {3008try {3009revalidateAll();3010textpipe.drawGlyphVector(this, gv, x, y);3011} catch (InvalidPipeException e2) {3012// Still catching the exception; we are not yet ready to3013// validate the surfaceData correctly. Fail for now and3014// try again next time around.3015}3016} finally {3017surfaceData.markDirty();3018}3019}30203021public void drawChars(char[] data, int offset, int length, int x, int y) {30223023if (data == null) {3024throw new NullPointerException("char data is null");3025}3026if (offset < 0 || length < 0 || offset + length < length ||3027offset + length > data.length) {3028throw new ArrayIndexOutOfBoundsException("bad offset/length");3029}3030if (font.hasLayoutAttributes()) {3031if (data.length == 0) {3032return;3033}3034new TextLayout(new String(data, offset, length),3035font, getFontRenderContext()).draw(this, x, y);3036return;3037}30383039try {3040textpipe.drawChars(this, data, offset, length, x, y);3041} catch (InvalidPipeException e) {3042try {3043revalidateAll();3044textpipe.drawChars(this, data, offset, length, x, y);3045} catch (InvalidPipeException e2) {3046// Still catching the exception; we are not yet ready to3047// validate the surfaceData correctly. Fail for now and3048// try again next time around.3049}3050} finally {3051surfaceData.markDirty();3052}3053}30543055public void drawBytes(byte[] data, int offset, int length, int x, int y) {3056if (data == null) {3057throw new NullPointerException("byte data is null");3058}3059if (offset < 0 || length < 0 || offset + length < length ||3060offset + length > data.length) {3061throw new ArrayIndexOutOfBoundsException("bad offset/length");3062}3063/* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */3064char[] chData = new char[length];3065for (int i = length; i-- > 0; ) {3066chData[i] = (char)(data[i+offset] & 0xff);3067}3068if (font.hasLayoutAttributes()) {3069if (data.length == 0) {3070return;3071}3072new TextLayout(new String(chData),3073font, getFontRenderContext()).draw(this, x, y);3074return;3075}30763077try {3078textpipe.drawChars(this, chData, 0, length, x, y);3079} catch (InvalidPipeException e) {3080try {3081revalidateAll();3082textpipe.drawChars(this, chData, 0, length, x, y);3083} catch (InvalidPipeException e2) {3084// Still catching the exception; we are not yet ready to3085// validate the surfaceData correctly. Fail for now and3086// try again next time around.3087}3088} finally {3089surfaceData.markDirty();3090}3091}3092// end of text rendering methods30933094private Boolean drawHiDPIImage(Image img,3095int dx1, int dy1, int dx2, int dy2,3096int sx1, int sy1, int sx2, int sy2,3097Color bgcolor, ImageObserver observer,3098AffineTransform xform) {3099try {3100if (img instanceof VolatileImage) {3101final SurfaceData sd = SurfaceManager.getManager(img)3102.getPrimarySurfaceData();3103final double scaleX = sd.getDefaultScaleX();3104final double scaleY = sd.getDefaultScaleY();3105if (scaleX == 1 && scaleY == 1) {3106return null;3107}3108sx1 = Region.clipRound(sx1 * scaleX);3109sx2 = Region.clipRound(sx2 * scaleX);3110sy1 = Region.clipRound(sy1 * scaleY);3111sy2 = Region.clipRound(sy2 * scaleY);31123113AffineTransform tx = null;3114if (xform != null) {3115tx = new AffineTransform(transform);3116transform(xform);3117}3118boolean result = scaleImage(img, dx1, dy1, dx2, dy2,3119sx1, sy1, sx2, sy2,3120bgcolor, observer);3121if (tx != null) {3122transform.setTransform(tx);3123invalidateTransform();3124}3125return result;3126} else if (img instanceof MultiResolutionImage) {3127// get scaled destination image size31283129int width = img.getWidth(observer);3130int height = img.getHeight(observer);31313132MultiResolutionImage mrImage = (MultiResolutionImage) img;3133Image resolutionVariant = getResolutionVariant(mrImage, width, height,3134dx1, dy1, dx2, dy2,3135sx1, sy1, sx2, sy2,3136xform);31373138if (resolutionVariant != img && resolutionVariant != null) {3139// recalculate source region for the resolution variant31403141ImageObserver rvObserver = MultiResolutionToolkitImage.3142getResolutionVariantObserver(img, observer,3143width, height, -1, -1);31443145int rvWidth = resolutionVariant.getWidth(rvObserver);3146int rvHeight = resolutionVariant.getHeight(rvObserver);31473148if (rvWidth < 0 || rvHeight < 0) {3149// The resolution variant is not loaded yet, try to use default resolution3150resolutionVariant = mrImage.getResolutionVariant(width, height);3151rvWidth = resolutionVariant.getWidth(rvObserver);3152rvHeight = resolutionVariant.getHeight(rvObserver);3153}31543155if (0 < width && 0 < height && 0 < rvWidth && 0 < rvHeight) {31563157double widthScale = ((double) rvWidth) / width;3158double heightScale = ((double) rvHeight) / height;31593160if (resolutionVariant instanceof VolatileImage) {3161SurfaceData sd = SurfaceManager3162.getManager(resolutionVariant)3163.getPrimarySurfaceData();3164widthScale *= sd.getDefaultScaleX();3165heightScale *= sd.getDefaultScaleY();3166}31673168sx1 = Region.clipScale(sx1, widthScale);3169sy1 = Region.clipScale(sy1, heightScale);3170sx2 = Region.clipScale(sx2, widthScale);3171sy2 = Region.clipScale(sy2, heightScale);31723173observer = rvObserver;3174img = resolutionVariant;31753176if (xform != null) {3177assert dx1 == 0 && dy1 == 0;3178AffineTransform renderTX = new AffineTransform(xform);3179renderTX.scale(1 / widthScale, 1 / heightScale);3180return transformImage(img, renderTX, observer);3181}31823183return scaleImage(img, dx1, dy1, dx2, dy2,3184sx1, sy1, sx2, sy2,3185bgcolor, observer);3186} else {3187return false; // Image variant is not initialized yet3188}3189}3190}3191} catch (InvalidPipeException e) {3192return false;3193}3194return null;3195}31963197private boolean scaleImage(Image img, int dx1, int dy1, int dx2, int dy2,3198int sx1, int sy1, int sx2, int sy2,3199Color bgcolor, ImageObserver observer)3200{3201try {3202return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1,3203sx2, sy2, bgcolor, observer);3204} catch (InvalidPipeException e) {3205try {3206revalidateAll();3207return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1,3208sy1, sx2, sy2, bgcolor, observer);3209} catch (InvalidPipeException e2) {3210// Still catching the exception; we are not yet ready to3211// validate the surfaceData correctly. Fail for now and3212// try again next time around.3213return false;3214}3215} finally {3216surfaceData.markDirty();3217}3218}32193220private boolean transformImage(Image img,3221AffineTransform xform,3222ImageObserver observer)3223{3224try {3225return imagepipe.transformImage(this, img, xform, observer);3226} catch (InvalidPipeException e) {3227try {3228revalidateAll();3229return imagepipe.transformImage(this, img, xform, observer);3230} catch (InvalidPipeException e2) {3231// Still catching the exception; we are not yet ready to3232// validate the surfaceData correctly. Fail for now and3233// try again next time around.3234return false;3235}3236} finally {3237surfaceData.markDirty();3238}3239}32403241private Image getResolutionVariant(MultiResolutionImage img,3242int srcWidth, int srcHeight, int dx1, int dy1, int dx2, int dy2,3243int sx1, int sy1, int sx2, int sy2, AffineTransform xform) {32443245if (srcWidth <= 0 || srcHeight <= 0) {3246return null;3247}32483249int sw = sx2 - sx1;3250int sh = sy2 - sy1;32513252if (sw == 0 || sh == 0) {3253return null;3254}32553256AffineTransform tx;32573258if (xform == null) {3259tx = transform;3260} else {3261tx = new AffineTransform(transform);3262tx.concatenate(xform);3263}32643265int type = tx.getType();3266int dw = dx2 - dx1;3267int dh = dy2 - dy1;32683269double destImageWidth;3270double destImageHeight;32713272if (resolutionVariantHint == SunHints.INTVAL_RESOLUTION_VARIANT_BASE) {3273destImageWidth = srcWidth;3274destImageHeight = srcHeight;3275} else if (resolutionVariantHint == SunHints.INTVAL_RESOLUTION_VARIANT_DPI_FIT) {3276AffineTransform configTransform = getDefaultTransform();3277if (configTransform.isIdentity()) {3278destImageWidth = srcWidth;3279destImageHeight = srcHeight;3280} else {3281destImageWidth = srcWidth * configTransform.getScaleX();3282destImageHeight = srcHeight * configTransform.getScaleY();3283}3284} else {3285double destRegionWidth;3286double destRegionHeight;32873288if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP)) == 0) {3289destRegionWidth = dw;3290destRegionHeight = dh;3291} else if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP | TYPE_MASK_SCALE)) == 0) {3292destRegionWidth = dw * tx.getScaleX();3293destRegionHeight = dh * tx.getScaleY();3294} else {3295destRegionWidth = dw * Math.hypot(3296tx.getScaleX(), tx.getShearY());3297destRegionHeight = dh * Math.hypot(3298tx.getShearX(), tx.getScaleY());3299}3300destImageWidth = Math.abs(srcWidth * destRegionWidth / sw);3301destImageHeight = Math.abs(srcHeight * destRegionHeight / sh);3302}33033304Image resolutionVariant3305= img.getResolutionVariant(destImageWidth, destImageHeight);33063307if (resolutionVariant instanceof ToolkitImage3308&& ((ToolkitImage) resolutionVariant).hasError()) {3309return null;3310}33113312return resolutionVariant;3313}33143315/**3316* Draws an image scaled to x,y,w,h in nonblocking mode with a3317* callback object.3318*/3319public boolean drawImage(Image img, int x, int y, int width, int height,3320ImageObserver observer) {3321return drawImage(img, x, y, width, height, null, observer);3322}33233324/**3325* Not part of the advertised API but a useful utility method3326* to call internally. This is for the case where we are3327* drawing to/from given coordinates using a given width/height,3328* but we guarantee that the surfaceData's width/height of the src and dest3329* areas are equal (no scale needed). Note that this method intentionally3330* ignore scale factor of the source image, and copy it as is.3331*/3332public boolean copyImage(Image img, int dx, int dy, int sx, int sy,3333int width, int height, Color bgcolor,3334ImageObserver observer) {3335try {3336return imagepipe.copyImage(this, img, dx, dy, sx, sy,3337width, height, bgcolor, observer);3338} catch (InvalidPipeException e) {3339try {3340revalidateAll();3341return imagepipe.copyImage(this, img, dx, dy, sx, sy,3342width, height, bgcolor, observer);3343} catch (InvalidPipeException e2) {3344// Still catching the exception; we are not yet ready to3345// validate the surfaceData correctly. Fail for now and3346// try again next time around.3347return false;3348}3349} finally {3350surfaceData.markDirty();3351}3352}33533354/**3355* Draws an image scaled to x,y,w,h in nonblocking mode with a3356* solid background color and a callback object.3357*/3358public boolean drawImage(Image img, int x, int y, int width, int height,3359Color bg, ImageObserver observer) {33603361if (img == null) {3362return true;3363}33643365if ((width == 0) || (height == 0)) {3366return true;3367}33683369final int imgW = img.getWidth(null);3370final int imgH = img.getHeight(null);3371Boolean hidpiImageDrawn = drawHiDPIImage(img, x, y, x + width, y + height,33720, 0, imgW, imgH, bg, observer,3373null);3374if (hidpiImageDrawn != null) {3375return hidpiImageDrawn;3376}33773378if (width == imgW && height == imgH) {3379return copyImage(img, x, y, 0, 0, width, height, bg, observer);3380}33813382try {3383return imagepipe.scaleImage(this, img, x, y, width, height,3384bg, observer);3385} catch (InvalidPipeException e) {3386try {3387revalidateAll();3388return imagepipe.scaleImage(this, img, x, y, width, height,3389bg, observer);3390} catch (InvalidPipeException e2) {3391// Still catching the exception; we are not yet ready to3392// validate the surfaceData correctly. Fail for now and3393// try again next time around.3394return false;3395}3396} finally {3397surfaceData.markDirty();3398}3399}34003401/**3402* Draws an image at x,y in nonblocking mode.3403*/3404public boolean drawImage(Image img, int x, int y, ImageObserver observer) {3405return drawImage(img, x, y, null, observer);3406}34073408/**3409* Draws an image at x,y in nonblocking mode with a solid background3410* color and a callback object.3411*/3412public boolean drawImage(Image img, int x, int y, Color bg,3413ImageObserver observer) {34143415if (img == null) {3416return true;3417}34183419final int imgW = img.getWidth(null);3420final int imgH = img.getHeight(null);3421Boolean hidpiImageDrawn = drawHiDPIImage(img, x, y, x + imgW, y + imgH,34220, 0, imgW, imgH, bg, observer,3423null);3424if (hidpiImageDrawn != null) {3425return hidpiImageDrawn;3426}34273428try {3429return imagepipe.copyImage(this, img, x, y, bg, observer);3430} catch (InvalidPipeException e) {3431try {3432revalidateAll();3433return imagepipe.copyImage(this, img, x, y, bg, observer);3434} catch (InvalidPipeException e2) {3435// Still catching the exception; we are not yet ready to3436// validate the surfaceData correctly. Fail for now and3437// try again next time around.3438return false;3439}3440} finally {3441surfaceData.markDirty();3442}3443}34443445/**3446* Draws a subrectangle of an image scaled to a destination rectangle3447* in nonblocking mode with a callback object.3448*/3449public boolean drawImage(Image img,3450int dx1, int dy1, int dx2, int dy2,3451int sx1, int sy1, int sx2, int sy2,3452ImageObserver observer) {3453return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null,3454observer);3455}34563457/**3458* Draws a subrectangle of an image scaled to a destination rectangle in3459* nonblocking mode with a solid background color and a callback object.3460*/3461public boolean drawImage(Image img,3462int dx1, int dy1, int dx2, int dy2,3463int sx1, int sy1, int sx2, int sy2,3464Color bgcolor, ImageObserver observer) {34653466if (img == null) {3467return true;3468}34693470if (dx1 == dx2 || dy1 == dy2 ||3471sx1 == sx2 || sy1 == sy2)3472{3473return true;3474}34753476Boolean hidpiImageDrawn = drawHiDPIImage(img, dx1, dy1, dx2, dy2,3477sx1, sy1, sx2, sy2,3478bgcolor, observer, null);34793480if (hidpiImageDrawn != null) {3481return hidpiImageDrawn;3482}34833484if (((sx2 - sx1) == (dx2 - dx1)) &&3485((sy2 - sy1) == (dy2 - dy1)))3486{3487// Not a scale - forward it to a copy routine3488int srcX, srcY, dstX, dstY, width, height;3489if (sx2 > sx1) {3490width = sx2 - sx1;3491srcX = sx1;3492dstX = dx1;3493} else {3494width = sx1 - sx2;3495srcX = sx2;3496dstX = dx2;3497}3498if (sy2 > sy1) {3499height = sy2-sy1;3500srcY = sy1;3501dstY = dy1;3502} else {3503height = sy1-sy2;3504srcY = sy2;3505dstY = dy2;3506}3507return copyImage(img, dstX, dstY, srcX, srcY,3508width, height, bgcolor, observer);3509}35103511try {3512return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,3513sx1, sy1, sx2, sy2, bgcolor,3514observer);3515} catch (InvalidPipeException e) {3516try {3517revalidateAll();3518return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,3519sx1, sy1, sx2, sy2, bgcolor,3520observer);3521} catch (InvalidPipeException e2) {3522// Still catching the exception; we are not yet ready to3523// validate the surfaceData correctly. Fail for now and3524// try again next time around.3525return false;3526}3527} finally {3528surfaceData.markDirty();3529}3530}35313532/**3533* Draw an image, applying a transform from image space into user space3534* before drawing.3535* The transformation from user space into device space is done with3536* the current transform in the Graphics2D.3537* The given transformation is applied to the image before the3538* transform attribute in the Graphics2D state is applied.3539* The rendering attributes applied include the clip, transform,3540* paint or color and composite attributes. Note that the result is3541* undefined, if the given transform is non-invertible.3542* @param img The image to be drawn.3543* @param xform The transformation from image space into user space.3544* @param observer The image observer to be notified on the image producing3545* progress.3546* @see #transform3547* @see #setComposite3548* @see #setClip3549*/3550public boolean drawImage(Image img,3551AffineTransform xform,3552ImageObserver observer) {35533554if (img == null) {3555return true;3556}35573558if (xform == null || xform.isIdentity()) {3559return drawImage(img, 0, 0, null, observer);3560}35613562final int w = img.getWidth(null);3563final int h = img.getHeight(null);3564Boolean hidpiImageDrawn = drawHiDPIImage(img, 0, 0, w, h, 0, 0, w, h,3565null, observer, xform);35663567if (hidpiImageDrawn != null) {3568return hidpiImageDrawn;3569}35703571return transformImage(img, xform, observer);3572}35733574public void drawImage(BufferedImage bImg,3575BufferedImageOp op,3576int x,3577int y) {35783579if (bImg == null) {3580return;3581}35823583try {3584imagepipe.transformImage(this, bImg, op, x, y);3585} catch (InvalidPipeException e) {3586try {3587revalidateAll();3588imagepipe.transformImage(this, bImg, op, x, y);3589} catch (InvalidPipeException e2) {3590// Still catching the exception; we are not yet ready to3591// validate the surfaceData correctly. Fail for now and3592// try again next time around.3593}3594} finally {3595surfaceData.markDirty();3596}3597}35983599/**3600* Get the rendering context of the font3601* within this Graphics2D context.3602*/3603public FontRenderContext getFontRenderContext() {3604if (cachedFRC == null) {3605int aahint = textAntialiasHint;3606if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT &&3607antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {3608aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;3609}3610// Translation components should be excluded from the FRC transform3611AffineTransform tx = null;3612if (transformState >= TRANSFORM_TRANSLATESCALE) {3613if (transform.getTranslateX() == 0 &&3614transform.getTranslateY() == 0) {3615tx = transform;3616} else {3617tx = new AffineTransform(transform.getScaleX(),3618transform.getShearY(),3619transform.getShearX(),3620transform.getScaleY(),36210, 0);3622}3623}3624cachedFRC = new FontRenderContext(tx,3625SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, aahint),3626SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,3627fractionalMetricsHint));3628}3629return cachedFRC;3630}3631private FontRenderContext cachedFRC;36323633/**3634* This object has no resources to dispose of per se, but the3635* doc comments for the base method in java.awt.Graphics imply3636* that this object will not be useable after it is disposed.3637* So, we sabotage the object to prevent further use to prevent3638* developers from relying on behavior that may not work on3639* other, less forgiving, VMs that really need to dispose of3640* resources.3641*/3642public void dispose() {3643surfaceData = NullSurfaceData.theInstance;3644invalidatePipe();3645}36463647/**3648* Graphics has a finalize method that automatically calls dispose()3649* for subclasses. For SunGraphics2D we do not need to be finalized3650* so that method simply causes us to be enqueued on the Finalizer3651* queues for no good reason. Unfortunately, that method and3652* implementation are now considered part of the public contract3653* of that base class so we can not remove or gut the method.3654* We override it here with an empty method and the VM is smart3655* enough to know that if our override is empty then it should not3656* mark us as finalizeable.3657*/3658@SuppressWarnings("deprecation")3659public void finalize() {3660// DO NOT REMOVE THIS METHOD3661}36623663/**3664* Returns destination that this Graphics renders to. This could be3665* either an Image or a Component; subclasses of SurfaceData are3666* responsible for returning the appropriate object.3667*/3668public Object getDestination() {3669return surfaceData.getDestination();3670}36713672/**3673* {@inheritDoc}3674*3675* @see sun.java2d.DestSurfaceProvider#getDestSurface3676*/3677@Override3678public Surface getDestSurface() {3679return surfaceData;3680}3681}368236833684