/* * Copyright 1996-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.java2d; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; import java.awt.geom.Area; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.RenderedImage; import java.awt.image.renderable.RenderableImage; import java.awt.image.renderable.RenderContext; import java.awt.image.AffineTransformOp; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.awt.Image; import java.awt.Composite; import java.awt.Color; import java.awt.image.ColorModel; import java.awt.GraphicsConfiguration; import java.awt.Paint; import java.awt.GradientPaint; import java.awt.LinearGradientPaint; import java.awt.RadialGradientPaint; import java.awt.TexturePaint; import java.awt.geom.Rectangle2D; import java.awt.geom.PathIterator; import java.awt.geom.GeneralPath; import java.awt.Shape; import java.awt.Stroke; import java.awt.FontMetrics; import java.awt.Rectangle; import java.text.AttributedCharacterIterator; import java.awt.Font; import java.awt.image.ImageObserver; import java.awt.Transparency; import java.awt.font.GlyphVector; import java.awt.font.TextLayout; import sun.font.FontDesignMetrics; import sun.font.FontUtilities; import sun.java2d.pipe.PixelDrawPipe; import sun.java2d.pipe.PixelFillPipe; import sun.java2d.pipe.ShapeDrawPipe; import sun.java2d.pipe.ValidatePipe; import sun.java2d.pipe.ShapeSpanIterator; import sun.java2d.pipe.Region; import sun.java2d.pipe.TextPipe; import sun.java2d.pipe.DrawImagePipe; import sun.java2d.pipe.LoopPipe; import sun.java2d.loops.FontInfo; import sun.java2d.loops.RenderLoops; import sun.java2d.loops.CompositeType; import sun.java2d.loops.SurfaceType; import sun.java2d.loops.Blit; import sun.java2d.loops.MaskFill; import sun.font.FontManager; import java.awt.font.FontRenderContext; import sun.java2d.loops.XORComposite; import sun.awt.ConstrainableGraphics; import sun.awt.SunHints; import java.util.Map; import java.util.Iterator; import sun.java2d.DestSurfaceProvider; import sun.misc.PerformanceLogger; /** * This is a the master Graphics2D superclass for all of the Sun * Graphics implementations. This class relies on subclasses to * manage the various device information, but provides an overall * general framework for performing all of the requests in the * Graphics and Graphics2D APIs. * * @author Jim Graham */ public final class SunGraphics2D extends Graphics2D implements ConstrainableGraphics, Cloneable, DestSurfaceProvider { /* * Attribute States */ /* Paint */ public static final int PAINT_CUSTOM = 6; /* Any other Paint object */ public static final int PAINT_TEXTURE = 5; /* Tiled Image */ public static final int PAINT_RAD_GRADIENT = 4; /* Color RadialGradient */ public static final int PAINT_LIN_GRADIENT = 3; /* Color LinearGradient */ public static final int PAINT_GRADIENT = 2; /* Color Gradient */ public static final int PAINT_ALPHACOLOR = 1; /* Non-opaque Color */ public static final int PAINT_OPAQUECOLOR = 0; /* Opaque Color */ /* Composite*/ public static final int COMP_CUSTOM = 3;/* Custom Composite */ public static final int COMP_XOR = 2;/* XOR Mode Composite */ public static final int COMP_ALPHA = 1;/* AlphaComposite */ public static final int COMP_ISCOPY = 0;/* simple stores into destination, * i.e. Src, SrcOverNoEa, and other * alpha modes which replace * the destination. */ /* Stroke */ public static final int STROKE_CUSTOM = 3; /* custom Stroke */ public static final int STROKE_WIDE = 2; /* BasicStroke */ public static final int STROKE_THINDASHED = 1; /* BasicStroke */ public static final int STROKE_THIN = 0; /* BasicStroke */ /* Transform */ public static final int TRANSFORM_GENERIC = 4; /* any 3x2 */ public static final int TRANSFORM_TRANSLATESCALE = 3; /* scale XY */ public static final int TRANSFORM_ANY_TRANSLATE = 2; /* non-int translate */ public static final int TRANSFORM_INT_TRANSLATE = 1; /* int translate */ public static final int TRANSFORM_ISIDENT = 0; /* Identity */ /* Clipping */ public static final int CLIP_SHAPE = 2; /* arbitrary clip */ public static final int CLIP_RECTANGULAR = 1; /* rectangular clip */ public static final int CLIP_DEVICE = 0; /* no clipping set */ /* The following fields are used when the current Paint is a Color. */ public int eargb; // ARGB value with ExtraAlpha baked in public int pixel; // pixel value for eargb public SurfaceData surfaceData; public PixelDrawPipe drawpipe; public PixelFillPipe fillpipe; public DrawImagePipe imagepipe; public ShapeDrawPipe shapepipe; public TextPipe textpipe; public MaskFill alphafill; public RenderLoops loops; public CompositeType imageComp; /* Image Transparency checked on fly */ public int paintState; public int compositeState; public int strokeState; public int transformState; public int clipState; public Color foregroundColor; public Color backgroundColor; public AffineTransform transform; public int transX; public int transY; protected static final Stroke defaultStroke = new BasicStroke(); protected static final Composite defaultComposite = AlphaComposite.SrcOver; private static final Font defaultFont = new Font(Font.DIALOG, Font.PLAIN, 12); public Paint paint; public Stroke stroke; public Composite composite; protected Font font; protected FontMetrics fontMetrics; public int renderHint; public int antialiasHint; public int textAntialiasHint; private int fractionalMetricsHint; /* A gamma adjustment to the colour used in lcd text blitting */ public int lcdTextContrast; private static int lcdTextContrastDefaultValue = 140; private int interpolationHint; // raw value of rendering Hint public int strokeHint; public int interpolationType; // algorithm choice based on // interpolation and render Hints public RenderingHints hints; public Region constrainClip; // lightweight bounds public int constrainX; public int constrainY; public Region clipRegion; public Shape usrClip; protected Region devClip; // Actual physical drawable // cached state for text rendering private boolean validFontInfo; private FontInfo fontInfo; private FontInfo glyphVectorFontInfo; private FontRenderContext glyphVectorFRC; private final static int slowTextTransformMask = AffineTransform.TYPE_GENERAL_TRANSFORM | AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_FLIP; static { if (PerformanceLogger.loggingEnabled()) { PerformanceLogger.setTime("SunGraphics2D static initialization"); } } public SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f) { surfaceData = sd; foregroundColor = fg; backgroundColor = bg; transform = new AffineTransform(); stroke = defaultStroke; composite = defaultComposite; paint = foregroundColor; imageComp = CompositeType.SrcOverNoEa; renderHint = SunHints.INTVAL_RENDER_DEFAULT; antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF; textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT; fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF; lcdTextContrast = lcdTextContrastDefaultValue; interpolationHint = -1; strokeHint = SunHints.INTVAL_STROKE_DEFAULT; interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; validateColor(); font = f; if (font == null) { font = defaultFont; } loops = sd.getRenderLoops(this); setDevClip(sd.getBounds()); invalidatePipe(); } protected Object clone() { try { SunGraphics2D g = (SunGraphics2D) super.clone(); g.transform = new AffineTransform(this.transform); if (hints != null) { g.hints = (RenderingHints) this.hints.clone(); } /* FontInfos are re-used, so must be cloned too, if they * are valid, and be nulled out if invalid. * The implied trade-off is that there is more to be gained * from re-using these objects than is lost by having to * clone them when the SG2D is cloned. */ if (this.fontInfo != null) { if (this.validFontInfo) { g.fontInfo = (FontInfo)this.fontInfo.clone(); } else { g.fontInfo = null; } } if (this.glyphVectorFontInfo != null) { g.glyphVectorFontInfo = (FontInfo)this.glyphVectorFontInfo.clone(); g.glyphVectorFRC = this.glyphVectorFRC; } //g.invalidatePipe(); return g; } catch (CloneNotSupportedException e) { } return null; } /** * Create a new SunGraphics2D based on this one. */ public Graphics create() { return (Graphics) clone(); } public void setDevClip(int x, int y, int w, int h) { Region c = constrainClip; if (c == null) { devClip = Region.getInstanceXYWH(x, y, w, h); } else { devClip = c.getIntersectionXYWH(x, y, w, h); } validateCompClip(); } public void setDevClip(Rectangle r) { setDevClip(r.x, r.y, r.width, r.height); } /** * Constrain rendering for lightweight objects. * * REMIND: This method will back off to the "workaround" * of using translate and clipRect if the Graphics * to be constrained has a complex transform. The * drawback of the workaround is that the resulting * clip and device origin cannot be "enforced". * * @exception IllegalStateException If the Graphics * to be constrained has a complex transform. */ public void constrain(int x, int y, int w, int h) { if ((x|y) != 0) { translate(x, y); } if (transformState >= TRANSFORM_TRANSLATESCALE) { clipRect(0, 0, w, h); return; } x = constrainX = transX; y = constrainY = transY; w = Region.dimAdd(x, w); h = Region.dimAdd(y, h); Region c = constrainClip; if (c == null) { c = Region.getInstanceXYXY(x, y, w, h); } else { c = c.getIntersectionXYXY(x, y, w, h); if (c == constrainClip) { // Common case to ignore return; } } constrainClip = c; if (!devClip.isInsideQuickCheck(c)) { devClip = devClip.getIntersection(c); validateCompClip(); } } protected static ValidatePipe invalidpipe = new ValidatePipe(); /* * Invalidate the pipeline */ protected void invalidatePipe() { drawpipe = invalidpipe; fillpipe = invalidpipe; shapepipe = invalidpipe; textpipe = invalidpipe; imagepipe = invalidpipe; } public void validatePipe() { surfaceData.validatePipe(this); } /* * Intersect two Shapes by the simplest method, attempting to produce * a simplified result. * The boolean arguments keep1 and keep2 specify whether or not * the first or second shapes can be modified during the operation * or whether that shape must be "kept" unmodified. */ Shape intersectShapes(Shape s1, Shape s2, boolean keep1, boolean keep2) { if (s1 instanceof Rectangle && s2 instanceof Rectangle) { return ((Rectangle) s1).intersection((Rectangle) s2); } if (s1 instanceof Rectangle2D) { return intersectRectShape((Rectangle2D) s1, s2, keep1, keep2); } else if (s2 instanceof Rectangle2D) { return intersectRectShape((Rectangle2D) s2, s1, keep2, keep1); } return intersectByArea(s1, s2, keep1, keep2); } /* * Intersect a Rectangle with a Shape by the simplest method, * attempting to produce a simplified result. * The boolean arguments keep1 and keep2 specify whether or not * the first or second shapes can be modified during the operation * or whether that shape must be "kept" unmodified. */ Shape intersectRectShape(Rectangle2D r, Shape s, boolean keep1, boolean keep2) { if (s instanceof Rectangle2D) { Rectangle2D r2 = (Rectangle2D) s; Rectangle2D outrect; if (!keep1) { outrect = r; } else if (!keep2) { outrect = r2; } else { outrect = new Rectangle2D.Float(); } double x1 = Math.max(r.getX(), r2.getX()); double x2 = Math.min(r.getX() + r.getWidth(), r2.getX() + r2.getWidth()); double y1 = Math.max(r.getY(), r2.getY()); double y2 = Math.min(r.getY() + r.getHeight(), r2.getY() + r2.getHeight()); if (((x2 - x1) < 0) || ((y2 - y1) < 0)) // Width or height is negative. No intersection. outrect.setFrameFromDiagonal(0, 0, 0, 0); else outrect.setFrameFromDiagonal(x1, y1, x2, y2); return outrect; } if (r.contains(s.getBounds2D())) { if (keep2) { s = cloneShape(s); } return s; } return intersectByArea(r, s, keep1, keep2); } protected static Shape cloneShape(Shape s) { return new GeneralPath(s); } /* * Intersect two Shapes using the Area class. Presumably other * attempts at simpler intersection methods proved fruitless. * The boolean arguments keep1 and keep2 specify whether or not * the first or second shapes can be modified during the operation * or whether that shape must be "kept" unmodified. * @see #intersectShapes * @see #intersectRectShape */ Shape intersectByArea(Shape s1, Shape s2, boolean keep1, boolean keep2) { Area a1, a2; // First see if we can find an overwriteable source shape // to use as our destination area to avoid duplication. if (!keep1 && (s1 instanceof Area)) { a1 = (Area) s1; } else if (!keep2 && (s2 instanceof Area)) { a1 = (Area) s2; s2 = s1; } else { a1 = new Area(s1); } if (s2 instanceof Area) { a2 = (Area) s2; } else { a2 = new Area(s2); } a1.intersect(a2); if (a1.isRectangular()) { return a1.getBounds(); } return a1; } /* * Intersect usrClip bounds and device bounds to determine the composite * rendering boundaries. */ public Region getCompClip() { if (!surfaceData.isValid()) { // revalidateAll() implicitly recalculcates the composite clip revalidateAll(); } return clipRegion; } public Font getFont() { if (font == null) { font = defaultFont; } return font; } private static final double[] IDENT_MATRIX = {1, 0, 0, 1}; private static final AffineTransform IDENT_ATX = new AffineTransform(); private static final int MINALLOCATED = 8; private static final int TEXTARRSIZE = 17; private static double[][] textTxArr = new double[TEXTARRSIZE][]; private static AffineTransform[] textAtArr = new AffineTransform[TEXTARRSIZE]; static { for (int i=MINALLOCATED;i= TRANSFORM_TRANSLATESCALE) { transform.getMatrix(info.devTx = new double[4]); devAt = new AffineTransform(info.devTx); textAt.preConcatenate(devAt); } else { info.devTx = IDENT_MATRIX; devAt = IDENT_ATX; } textAt.getMatrix(info.glyphTx = new double[4]); double shearx = textAt.getShearX(); double scaley = textAt.getScaleY(); if (shearx != 0) { scaley = Math.sqrt(shearx * shearx + scaley * scaley); } info.pixelHeight = (int)(Math.abs(scaley)+0.5); } else { txFontType = AffineTransform.TYPE_IDENTITY; info.originX = info.originY = 0; if (transformState >= TRANSFORM_TRANSLATESCALE) { transform.getMatrix(info.devTx = new double[4]); devAt = new AffineTransform(info.devTx); info.glyphTx = new double[4]; for (int i = 0; i < 4; i++) { info.glyphTx[i] = info.devTx[i] * ptSize; } textAt = new AffineTransform(info.glyphTx); double shearx = transform.getShearX(); double scaley = transform.getScaleY(); if (shearx != 0) { scaley = Math.sqrt(shearx * shearx + scaley * scaley); } info.pixelHeight = (int)(Math.abs(scaley * ptSize)+0.5); } else { /* If the double represents a common integral, we * may have pre-allocated objects. * A "sparse" array be seems to be as fast as a switch * even for 3 or 4 pt sizes, and is more flexible. * This should perform comparably in single-threaded * rendering to the old code which synchronized on the * class and scale better on MP systems. */ int pszInt = (int)ptSize; if (ptSize == pszInt && pszInt >= MINALLOCATED && pszInt < TEXTARRSIZE) { info.glyphTx = textTxArr[pszInt]; textAt = textAtArr[pszInt]; info.pixelHeight = pszInt; } else { info.pixelHeight = (int)(ptSize+0.5); } if (textAt == null) { info.glyphTx = new double[] {ptSize, 0, 0, ptSize}; textAt = new AffineTransform(info.glyphTx); } info.devTx = IDENT_MATRIX; devAt = IDENT_ATX; } } info.font2D = FontUtilities.getFont2D(font); int fmhint = fractionalMetricsHint; if (fmhint == SunHints.INTVAL_FRACTIONALMETRICS_DEFAULT) { fmhint = SunHints.INTVAL_FRACTIONALMETRICS_OFF; } info.lcdSubPixPos = false; // conditionally set true in LCD mode. /* The text anti-aliasing hints that are set by the client need * to be interpreted for the current state and stored in the * FontInfo.aahint which is what will actually be used and * will be one of OFF, ON, LCD_HRGB or LCD_VRGB. * This is what pipe selection code should typically refer to, not * textAntialiasHint. This means we are now evaluating the meaning * of "default" here. Any pipe that really cares about that will * also need to consult that variable. * Otherwise these are being used only as args to getStrike, * and are encapsulated in that object which is part of the * FontInfo, so we do not need to store them directly as fields * in the FontInfo object. * That could change if FontInfo's were more selectively * revalidated when graphics state changed. Presently this * method re-evaluates all fields in the fontInfo. * The strike doesn't need to know the RGB subpixel order. Just * if its H or V orientation, so if an LCD option is specified we * always pass in the RGB hint to the strike. * frc is non-null only if this is a GlyphVector. For reasons * which are probably a historical mistake the AA hint in a GV * is honoured when we render, overriding the Graphics setting. */ int aahint; if (frc == null) { aahint = textAntialiasHint; } else { aahint = ((SunHints.Value)frc.getAntiAliasingHint()).getIndex(); } if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT) { if (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; } else { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF; } } else { /* If we are in checkFontInfo because a rendering hint has been * set then all pipes are revalidated. But we can also * be here because setFont() has been called when the 'gasp' * hint is set, as then the font size determines the text pipe. * See comments in SunGraphics2d.setFont(Font). */ if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP) { if (info.font2D.useAAForPtSize(info.pixelHeight)) { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; } else { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF; } } else if (aahint >= SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB) { /* loops for default rendering modes are installed in the SG2D * constructor. If there are none this will be null. * Not all compositing modes update the render loops, so * we also test that this is a mode we know should support * this. One minor issue is that the loops aren't necessarily * installed for a new rendering mode until after this * method is called during pipeline validation. So it is * theoretically possible that it was set to null for a * compositing mode, the composite is then set back to Src, * but the loop is still null when this is called and AA=ON * is installed instead of an LCD mode. * However this is done in the right order in SurfaceData.java * so this is not likely to be a problem - but not * guaranteed. */ if ( !surfaceData.canRenderLCDText(this) // loops.drawGlyphListLCDLoop == null || // compositeState > COMP_ISCOPY || // paintState > PAINT_ALPHACOLOR ) { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; } else { info.lcdRGBOrder = true; /* Collapse these into just HRGB or VRGB. * Pipe selection code needs only to test for these two. * Since these both select the same pipe anyway its * tempting to collapse into one value. But they are * different strikes (glyph caches) so the distinction * needs to be made for that purpose. */ if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HBGR) { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB; info.lcdRGBOrder = false; } else if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VBGR) { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB; info.lcdRGBOrder = false; } /* Support subpixel positioning only for the case in * which the horizontal resolution is increased */ info.lcdSubPixPos = fmhint == SunHints.INTVAL_FRACTIONALMETRICS_ON && aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB; } } } info.aaHint = aahint; info.fontStrike = info.font2D.getStrike(font, devAt, textAt, aahint, fmhint); return info; } public static boolean isRotated(double [] mtx) { if ((mtx[0] == mtx[3]) && (mtx[1] == 0.0) && (mtx[2] == 0.0) && (mtx[0] > 0.0)) { return false; } return true; } public void setFont(Font font) { /* replacing the reference equality test font != this.font with * !font.equals(this.font) did not yield any measurable difference * in testing, but there may be yet to be identified cases where it * is beneficial. */ if (font != null && font!=this.font/*!font.equals(this.font)*/) { /* In the GASP AA case the textpipe depends on the glyph size * as determined by graphics and font transforms as well as the * font size, and information in the font. But we may invalidate * the pipe only to find that it made no difference. * Deferring pipe invalidation to checkFontInfo won't work because * when called we may already be rendering to the wrong pipe. * So, if the font is transformed, or the graphics has more than * a simple scale, we'll take that as enough of a hint to * revalidate everything. But if they aren't we will * use the font's point size to query the gasp table and see if * what it says matches what's currently being used, in which * case there's no need to invalidate the textpipe. * This should be sufficient for all typical uses cases. */ if (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP && textpipe != invalidpipe && (transformState > TRANSFORM_ANY_TRANSLATE || font.isTransformed() || fontInfo == null || // Precaution, if true shouldn't get here (fontInfo.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) != FontUtilities.getFont2D(font). useAAForPtSize(font.getSize()))) { textpipe = invalidpipe; } this.font = font; this.fontMetrics = null; this.validFontInfo = false; } } public FontInfo getFontInfo() { if (!validFontInfo) { this.fontInfo = checkFontInfo(this.fontInfo, font, null); validFontInfo = true; } return this.fontInfo; } /* Used by drawGlyphVector which specifies its own font. */ public FontInfo getGVFontInfo(Font font, FontRenderContext frc) { if (glyphVectorFontInfo != null && glyphVectorFontInfo.font == font && glyphVectorFRC == frc) { return glyphVectorFontInfo; } else { glyphVectorFRC = frc; return glyphVectorFontInfo = checkFontInfo(glyphVectorFontInfo, font, frc); } } public FontMetrics getFontMetrics() { if (this.fontMetrics != null) { return this.fontMetrics; } /* NB the constructor and the setter disallow "font" being null */ return this.fontMetrics = FontDesignMetrics.getMetrics(font, getFontRenderContext()); } public FontMetrics getFontMetrics(Font font) { if ((this.fontMetrics != null) && (font == this.font)) { return this.fontMetrics; } FontMetrics fm = FontDesignMetrics.getMetrics(font, getFontRenderContext()); if (this.font == font) { this.fontMetrics = fm; } return fm; } /** * Checks to see if a Path intersects the specified Rectangle in device * space. The rendering attributes taken into account include the * clip, transform, and stroke attributes. * @param rect The area in device space to check for a hit. * @param p The path to check for a hit. * @param onStroke Flag to choose between testing the stroked or * the filled path. * @return True if there is a hit, false otherwise. * @see #setStroke * @see #fillPath * @see #drawPath * @see #transform * @see #setTransform * @see #clip * @see #setClip */ public boolean hit(Rectangle rect, Shape s, boolean onStroke) { if (onStroke) { s = stroke.createStrokedShape(s); } s = transformShape(s); if ((constrainX|constrainY) != 0) { rect = new Rectangle(rect); rect.translate(constrainX, constrainY); } return s.intersects(rect); } /** * Return the ColorModel associated with this Graphics2D. */ public ColorModel getDeviceColorModel() { return surfaceData.getColorModel(); } /** * Return the device configuration associated with this Graphics2D. */ public GraphicsConfiguration getDeviceConfiguration() { return surfaceData.getDeviceConfiguration(); } /** * Return the SurfaceData object assigned to manage the destination * drawable surface of this Graphics2D. */ public final SurfaceData getSurfaceData() { return surfaceData; } /** * Sets the Composite in the current graphics state. Composite is used * in all drawing methods such as drawImage, drawString, drawPath, * and fillPath. It specifies how new pixels are to be combined with * the existing pixels on the graphics device in the rendering process. * @param comp The Composite object to be used for drawing. * @see java.awt.Graphics#setXORMode * @see java.awt.Graphics#setPaintMode * @see AlphaComposite */ public void setComposite(Composite comp) { if (composite == comp) { return; } int newCompState; CompositeType newCompType; if (comp instanceof AlphaComposite) { AlphaComposite alphacomp = (AlphaComposite) comp; newCompType = CompositeType.forAlphaComposite(alphacomp); if (newCompType == CompositeType.SrcOverNoEa) { if (paintState == PAINT_OPAQUECOLOR || (paintState > PAINT_ALPHACOLOR && paint.getTransparency() == Transparency.OPAQUE)) { newCompState = COMP_ISCOPY; } else { newCompState = COMP_ALPHA; } } else if (newCompType == CompositeType.SrcNoEa || newCompType == CompositeType.Src || newCompType == CompositeType.Clear) { newCompState = COMP_ISCOPY; } else if (surfaceData.getTransparency() == Transparency.OPAQUE && newCompType == CompositeType.SrcIn) { newCompState = COMP_ISCOPY; } else { newCompState = COMP_ALPHA; } } else if (comp instanceof XORComposite) { newCompState = COMP_XOR; newCompType = CompositeType.Xor; } else if (comp == null) { throw new IllegalArgumentException("null Composite"); } else { surfaceData.checkCustomComposite(); newCompState = COMP_CUSTOM; newCompType = CompositeType.General; } if (compositeState != newCompState || imageComp != newCompType) { compositeState = newCompState; imageComp = newCompType; invalidatePipe(); validFontInfo = false; } composite = comp; if (paintState <= PAINT_ALPHACOLOR) { validateColor(); } } /** * Sets the Paint in the current graphics state. * @param paint The Paint object to be used to generate color in * the rendering process. * @see java.awt.Graphics#setColor * @see GradientPaint * @see TexturePaint */ public void setPaint(Paint paint) { if (paint instanceof Color) { setColor((Color) paint); return; } if (paint == null || this.paint == paint) { return; } this.paint = paint; if (imageComp == CompositeType.SrcOverNoEa) { // special case where compState depends on opacity of paint if (paint.getTransparency() == Transparency.OPAQUE) { if (compositeState != COMP_ISCOPY) { compositeState = COMP_ISCOPY; } } else { if (compositeState == COMP_ISCOPY) { compositeState = COMP_ALPHA; } } } Class paintClass = paint.getClass(); if (paintClass == GradientPaint.class) { paintState = PAINT_GRADIENT; } else if (paintClass == LinearGradientPaint.class) { paintState = PAINT_LIN_GRADIENT; } else if (paintClass == RadialGradientPaint.class) { paintState = PAINT_RAD_GRADIENT; } else if (paintClass == TexturePaint.class) { paintState = PAINT_TEXTURE; } else { paintState = PAINT_CUSTOM; } validFontInfo = false; invalidatePipe(); } static final int NON_UNIFORM_SCALE_MASK = (AffineTransform.TYPE_GENERAL_TRANSFORM | AffineTransform.TYPE_GENERAL_SCALE); public static final double MinPenSizeAA = sun.java2d.pipe.RenderingEngine.getInstance().getMinimumAAPenSize(); public static final double MinPenSizeAASquared = (MinPenSizeAA * MinPenSizeAA); // Since inaccuracies in the trig package can cause us to // calculated a rotated pen width of just slightly greater // than 1.0, we add a fudge factor to our comparison value // here so that we do not misclassify single width lines as // wide lines under certain rotations. public static final double MinPenSizeSquared = 1.000000001; private void validateBasicStroke(BasicStroke bs) { boolean aa = (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON); if (transformState < TRANSFORM_TRANSLATESCALE) { if (aa) { if (bs.getLineWidth() <= MinPenSizeAA) { if (bs.getDashArray() == null) { strokeState = STROKE_THIN; } else { strokeState = STROKE_THINDASHED; } } else { strokeState = STROKE_WIDE; } } else { if (bs == defaultStroke) { strokeState = STROKE_THIN; } else if (bs.getLineWidth() <= 1.0f) { if (bs.getDashArray() == null) { strokeState = STROKE_THIN; } else { strokeState = STROKE_THINDASHED; } } else { strokeState = STROKE_WIDE; } } } else { double widthsquared; if ((transform.getType() & NON_UNIFORM_SCALE_MASK) == 0) { /* sqrt omitted, compare to squared limits below. */ widthsquared = Math.abs(transform.getDeterminant()); } else { /* First calculate the "maximum scale" of this transform. */ double A = transform.getScaleX(); // m00 double C = transform.getShearX(); // m01 double B = transform.getShearY(); // m10 double D = transform.getScaleY(); // m11 /* * Given a 2 x 2 affine matrix [ A B ] such that * [ C D ] * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to * find the maximum magnitude (norm) of the vector v' * with the constraint (x^2 + y^2 = 1). * The equation to maximize is * |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2) * or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2). * Since sqrt is monotonic we can maximize |v'|^2 * instead and plug in the substitution y = sqrt(1 - x^2). * Trigonometric equalities can then be used to get * rid of most of the sqrt terms. */ double EA = A*A + B*B; // x^2 coefficient double EB = 2*(A*C + B*D); // xy coefficient double EC = C*C + D*D; // y^2 coefficient /* * There is a lot of calculus omitted here. * * Conceptually, in the interests of understanding the * terms that the calculus produced we can consider * that EA and EC end up providing the lengths along * the major axes and the hypot term ends up being an * adjustment for the additional length along the off-axis * angle of rotated or sheared ellipses as well as an * adjustment for the fact that the equation below * averages the two major axis lengths. (Notice that * the hypot term contains a part which resolves to the * difference of these two axis lengths in the absence * of rotation.) * * In the calculus, the ratio of the EB and (EA-EC) terms * ends up being the tangent of 2*theta where theta is * the angle that the long axis of the ellipse makes * with the horizontal axis. Thus, this equation is * calculating the length of the hypotenuse of a triangle * along that axis. */ double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC)); /* sqrt omitted, compare to squared limits below. */ widthsquared = ((EA + EC + hypot)/2.0); } if (bs != defaultStroke) { widthsquared *= bs.getLineWidth() * bs.getLineWidth(); } if (widthsquared <= (aa ? MinPenSizeAASquared : MinPenSizeSquared)) { if (bs.getDashArray() == null) { strokeState = STROKE_THIN; } else { strokeState = STROKE_THINDASHED; } } else { strokeState = STROKE_WIDE; } } } /* * Sets the Stroke in the current graphics state. * @param s The Stroke object to be used to stroke a Path in * the rendering process. * @see BasicStroke */ public void setStroke(Stroke s) { if (s == null) { throw new IllegalArgumentException("null Stroke"); } int saveStrokeState = strokeState; stroke = s; if (s instanceof BasicStroke) { validateBasicStroke((BasicStroke) s); } else { strokeState = STROKE_CUSTOM; } if (strokeState != saveStrokeState) { invalidatePipe(); } } /** * Sets the preferences for the rendering algorithms. * Hint categories include controls for rendering quality and * overall time/quality trade-off in the rendering process. * @param hintKey The key of hint to be set. The strings are * defined in the RenderingHints class. * @param hintValue The value indicating preferences for the specified * hint category. These strings are defined in the RenderingHints * class. * @see RenderingHints */ public void setRenderingHint(Key hintKey, Object hintValue) { // If we recognize the key, we must recognize the value // otherwise throw an IllegalArgumentException // and do not change the Hints object // If we do not recognize the key, just pass it through // to the Hints object untouched if (!hintKey.isCompatibleValue(hintValue)) { throw new IllegalArgumentException (hintValue+" is not compatible with "+hintKey); } if (hintKey instanceof SunHints.Key) { boolean stateChanged; boolean textStateChanged = false; boolean recognized = true; SunHints.Key sunKey = (SunHints.Key) hintKey; int newHint; if (sunKey == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST) { newHint = ((Integer)hintValue).intValue(); } else { newHint = ((SunHints.Value) hintValue).getIndex(); } switch (sunKey.getIndex()) { case SunHints.INTKEY_RENDERING: stateChanged = (renderHint != newHint); if (stateChanged) { renderHint = newHint; if (interpolationHint == -1) { interpolationType = (newHint == SunHints.INTVAL_RENDER_QUALITY ? AffineTransformOp.TYPE_BILINEAR : AffineTransformOp.TYPE_NEAREST_NEIGHBOR); } } break; case SunHints.INTKEY_ANTIALIASING: stateChanged = (antialiasHint != newHint); antialiasHint = newHint; if (stateChanged) { textStateChanged = (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT); if (strokeState != STROKE_CUSTOM) { validateBasicStroke((BasicStroke) stroke); } } break; case SunHints.INTKEY_TEXT_ANTIALIASING: stateChanged = (textAntialiasHint != newHint); textStateChanged = stateChanged; textAntialiasHint = newHint; break; case SunHints.INTKEY_FRACTIONALMETRICS: stateChanged = (fractionalMetricsHint != newHint); textStateChanged = stateChanged; fractionalMetricsHint = newHint; break; case SunHints.INTKEY_AATEXT_LCD_CONTRAST: stateChanged = false; /* Already have validated it is an int 100 <= newHint <= 250 */ lcdTextContrast = newHint; break; case SunHints.INTKEY_INTERPOLATION: interpolationHint = newHint; switch (newHint) { case SunHints.INTVAL_INTERPOLATION_BICUBIC: newHint = AffineTransformOp.TYPE_BICUBIC; break; case SunHints.INTVAL_INTERPOLATION_BILINEAR: newHint = AffineTransformOp.TYPE_BILINEAR; break; default: case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR: newHint = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; break; } stateChanged = (interpolationType != newHint); interpolationType = newHint; break; case SunHints.INTKEY_STROKE_CONTROL: stateChanged = (strokeHint != newHint); strokeHint = newHint; break; default: recognized = false; stateChanged = false; break; } if (recognized) { if (stateChanged) { invalidatePipe(); if (textStateChanged) { fontMetrics = null; this.cachedFRC = null; validFontInfo = false; this.glyphVectorFontInfo = null; } } if (hints != null) { hints.put(hintKey, hintValue); } return; } } // Nothing we recognize so none of "our state" has changed if (hints == null) { hints = makeHints(null); } hints.put(hintKey, hintValue); } /** * Returns the preferences for the rendering algorithms. * @param hintCategory The category of hint to be set. The strings * are defined in the RenderingHints class. * @return The preferences for rendering algorithms. The strings * are defined in the RenderingHints class. * @see RenderingHints */ public Object getRenderingHint(Key hintKey) { if (hints != null) { return hints.get(hintKey); } if (!(hintKey instanceof SunHints.Key)) { return null; } int keyindex = ((SunHints.Key)hintKey).getIndex(); switch (keyindex) { case SunHints.INTKEY_RENDERING: return SunHints.Value.get(SunHints.INTKEY_RENDERING, renderHint); case SunHints.INTKEY_ANTIALIASING: return SunHints.Value.get(SunHints.INTKEY_ANTIALIASING, antialiasHint); case SunHints.INTKEY_TEXT_ANTIALIASING: return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, textAntialiasHint); case SunHints.INTKEY_FRACTIONALMETRICS: return SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS, fractionalMetricsHint); case SunHints.INTKEY_AATEXT_LCD_CONTRAST: return new Integer(lcdTextContrast); case SunHints.INTKEY_INTERPOLATION: switch (interpolationHint) { case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR: return SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; case SunHints.INTVAL_INTERPOLATION_BILINEAR: return SunHints.VALUE_INTERPOLATION_BILINEAR; case SunHints.INTVAL_INTERPOLATION_BICUBIC: return SunHints.VALUE_INTERPOLATION_BICUBIC; } return null; case SunHints.INTKEY_STROKE_CONTROL: return SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL, strokeHint); } return null; } /** * Sets the preferences for the rendering algorithms. * Hint categories include controls for rendering quality and * overall time/quality trade-off in the rendering process. * @param hints The rendering hints to be set * @see RenderingHints */ public void setRenderingHints(Map hints) { this.hints = null; renderHint = SunHints.INTVAL_RENDER_DEFAULT; antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF; textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT; fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF; lcdTextContrast = lcdTextContrastDefaultValue; interpolationHint = -1; interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; boolean customHintPresent = false; Iterator iter = hints.keySet().iterator(); while (iter.hasNext()) { Object key = iter.next(); if (key == SunHints.KEY_RENDERING || key == SunHints.KEY_ANTIALIASING || key == SunHints.KEY_TEXT_ANTIALIASING || key == SunHints.KEY_FRACTIONALMETRICS || key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST || key == SunHints.KEY_STROKE_CONTROL || key == SunHints.KEY_INTERPOLATION) { setRenderingHint((Key) key, hints.get(key)); } else { customHintPresent = true; } } if (customHintPresent) { this.hints = makeHints(hints); } invalidatePipe(); } /** * Adds a number of preferences for the rendering algorithms. * Hint categories include controls for rendering quality and * overall time/quality trade-off in the rendering process. * @param hints The rendering hints to be set * @see RenderingHints */ public void addRenderingHints(Map hints) { boolean customHintPresent = false; Iterator iter = hints.keySet().iterator(); while (iter.hasNext()) { Object key = iter.next(); if (key == SunHints.KEY_RENDERING || key == SunHints.KEY_ANTIALIASING || key == SunHints.KEY_TEXT_ANTIALIASING || key == SunHints.KEY_FRACTIONALMETRICS || key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST || key == SunHints.KEY_STROKE_CONTROL || key == SunHints.KEY_INTERPOLATION) { setRenderingHint((Key) key, hints.get(key)); } else { customHintPresent = true; } } if (customHintPresent) { if (this.hints == null) { this.hints = makeHints(hints); } else { this.hints.putAll(hints); } } } /** * Gets the preferences for the rendering algorithms. * Hint categories include controls for rendering quality and * overall time/quality trade-off in the rendering process. * @see RenderingHints */ public RenderingHints getRenderingHints() { if (hints == null) { return makeHints(null); } else { return (RenderingHints) hints.clone(); } } RenderingHints makeHints(Map hints) { RenderingHints model = new RenderingHints(hints); model.put(SunHints.KEY_RENDERING, SunHints.Value.get(SunHints.INTKEY_RENDERING, renderHint)); model.put(SunHints.KEY_ANTIALIASING, SunHints.Value.get(SunHints.INTKEY_ANTIALIASING, antialiasHint)); model.put(SunHints.KEY_TEXT_ANTIALIASING, SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, textAntialiasHint)); model.put(SunHints.KEY_FRACTIONALMETRICS, SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS, fractionalMetricsHint)); model.put(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST, Integer.valueOf(lcdTextContrast)); Object value; switch (interpolationHint) { case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR: value = SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; break; case SunHints.INTVAL_INTERPOLATION_BILINEAR: value = SunHints.VALUE_INTERPOLATION_BILINEAR; break; case SunHints.INTVAL_INTERPOLATION_BICUBIC: value = SunHints.VALUE_INTERPOLATION_BICUBIC; break; default: value = null; break; } if (value != null) { model.put(SunHints.KEY_INTERPOLATION, value); } model.put(SunHints.KEY_STROKE_CONTROL, SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL, strokeHint)); return model; } /** * Concatenates the current transform of this Graphics2D with a * translation transformation. * This is equivalent to calling transform(T), where T is an * AffineTransform represented by the following matrix: *
     *          [   1    0    tx  ]
     *          [   0    1    ty  ]
     *          [   0    0    1   ]
     * 
*/ public void translate(double tx, double ty) { transform.translate(tx, ty); invalidateTransform(); } /** * Concatenates the current transform of this Graphics2D with a * rotation transformation. * This is equivalent to calling transform(R), where R is an * AffineTransform represented by the following matrix: *
     *          [   cos(theta)    -sin(theta)    0   ]
     *          [   sin(theta)     cos(theta)    0   ]
     *          [       0              0         1   ]
     * 
* Rotating with a positive angle theta rotates points on the positive * x axis toward the positive y axis. * @param theta The angle of rotation in radians. */ public void rotate(double theta) { transform.rotate(theta); invalidateTransform(); } /** * Concatenates the current transform of this Graphics2D with a * translated rotation transformation. * This is equivalent to the following sequence of calls: *
     *          translate(x, y);
     *          rotate(theta);
     *          translate(-x, -y);
     * 
* Rotating with a positive angle theta rotates points on the positive * x axis toward the positive y axis. * @param theta The angle of rotation in radians. * @param x The x coordinate of the origin of the rotation * @param y The x coordinate of the origin of the rotation */ public void rotate(double theta, double x, double y) { transform.rotate(theta, x, y); invalidateTransform(); } /** * Concatenates the current transform of this Graphics2D with a * scaling transformation. * This is equivalent to calling transform(S), where S is an * AffineTransform represented by the following matrix: *
     *          [   sx   0    0   ]
     *          [   0    sy   0   ]
     *          [   0    0    1   ]
     * 
*/ public void scale(double sx, double sy) { transform.scale(sx, sy); invalidateTransform(); } /** * Concatenates the current transform of this Graphics2D with a * shearing transformation. * This is equivalent to calling transform(SH), where SH is an * AffineTransform represented by the following matrix: *
     *          [   1   shx   0   ]
     *          [  shy   1    0   ]
     *          [   0    0    1   ]
     * 
* @param shx The factor by which coordinates are shifted towards the * positive X axis direction according to their Y coordinate * @param shy The factor by which coordinates are shifted towards the * positive Y axis direction according to their X coordinate */ public void shear(double shx, double shy) { transform.shear(shx, shy); invalidateTransform(); } /** * Composes a Transform object with the transform in this * Graphics2D according to the rule last-specified-first-applied. * If the currrent transform is Cx, the result of composition * with Tx is a new transform Cx'. Cx' becomes the current * transform for this Graphics2D. * Transforming a point p by the updated transform Cx' is * equivalent to first transforming p by Tx and then transforming * the result by the original transform Cx. In other words, * Cx'(p) = Cx(Tx(p)). * A copy of the Tx is made, if necessary, so further * modifications to Tx do not affect rendering. * @param Tx The Transform object to be composed with the current * transform. * @see #setTransform * @see AffineTransform */ public void transform(AffineTransform xform) { this.transform.concatenate(xform); invalidateTransform(); } /** * Translate */ public void translate(int x, int y) { transform.translate(x, y); if (transformState <= TRANSFORM_INT_TRANSLATE) { transX += x; transY += y; transformState = (((transX | transY) == 0) ? TRANSFORM_ISIDENT : TRANSFORM_INT_TRANSLATE); } else { invalidateTransform(); } } /** * Sets the Transform in the current graphics state. * @param Tx The Transform object to be used in the rendering process. * @see #transform * @see TransformChain * @see AffineTransform */ public void setTransform(AffineTransform Tx) { if ((constrainX|constrainY) == 0) { transform.setTransform(Tx); } else { transform.setToTranslation(constrainX, constrainY); transform.concatenate(Tx); } invalidateTransform(); } protected void invalidateTransform() { int type = transform.getType(); int origTransformState = transformState; if (type == AffineTransform.TYPE_IDENTITY) { transformState = TRANSFORM_ISIDENT; transX = transY = 0; } else if (type == AffineTransform.TYPE_TRANSLATION) { double dtx = transform.getTranslateX(); double dty = transform.getTranslateY(); transX = (int) Math.floor(dtx + 0.5); transY = (int) Math.floor(dty + 0.5); if (dtx == transX && dty == transY) { transformState = TRANSFORM_INT_TRANSLATE; } else { transformState = TRANSFORM_ANY_TRANSLATE; } } else if ((type & (AffineTransform.TYPE_FLIP | AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0) { transformState = TRANSFORM_TRANSLATESCALE; transX = transY = 0; } else { transformState = TRANSFORM_GENERIC; transX = transY = 0; } if (transformState >= TRANSFORM_TRANSLATESCALE || origTransformState >= TRANSFORM_TRANSLATESCALE) { /* Its only in this case that the previous or current transform * was more than a translate that font info is invalidated */ cachedFRC = null; this.validFontInfo = false; this.fontMetrics = null; this.glyphVectorFontInfo = null; if (transformState != origTransformState) { invalidatePipe(); } } if (strokeState != STROKE_CUSTOM) { validateBasicStroke((BasicStroke) stroke); } } /** * Returns the current Transform in the Graphics2D state. * @see #transform * @see #setTransform */ public AffineTransform getTransform() { if ((constrainX|constrainY) == 0) { return new AffineTransform(transform); } AffineTransform tx = AffineTransform.getTranslateInstance(-constrainX, -constrainY); tx.concatenate(transform); return tx; } /** * Returns the current Transform ignoring the "constrain" * rectangle. */ public AffineTransform cloneTransform() { return new AffineTransform(transform); } /** * Returns the current Paint in the Graphics2D state. * @see #setPaint * @see java.awt.Graphics#setColor */ public Paint getPaint() { return paint; } /** * Returns the current Composite in the Graphics2D state. * @see #setComposite */ public Composite getComposite() { return composite; } public Color getColor() { return foregroundColor; } /* * Validate the eargb and pixel fields against the current color. * * The eargb field must take into account the extraAlpha * value of an AlphaComposite. It may also take into account * the Fsrc Porter-Duff blending function if such a function is * a constant (see handling of Clear mode below). For instance, * by factoring in the (Fsrc == 0) state of the Clear mode we can * use a SrcNoEa loop just as easily as a general Alpha loop * since the math will be the same in both cases. * * The pixel field will always be the best pixel data choice for * the final result of all calculations applied to the eargb field. * * Note that this method is only necessary under the following * conditions: * (paintState <= PAINT_ALPHA_COLOR && * compositeState <= COMP_CUSTOM) * though nothing bad will happen if it is run in other states. */ final void validateColor() { int eargb; if (imageComp == CompositeType.Clear) { eargb = 0; } else { eargb = foregroundColor.getRGB(); if (compositeState <= COMP_ALPHA && imageComp != CompositeType.SrcNoEa && imageComp != CompositeType.SrcOverNoEa) { AlphaComposite alphacomp = (AlphaComposite) composite; int a = Math.round(alphacomp.getAlpha() * (eargb >>> 24)); eargb = (eargb & 0x00ffffff) | (a << 24); } } this.eargb = eargb; this.pixel = surfaceData.pixelFor(eargb); } public void setColor(Color color) { if (color == null || color == paint) { return; } this.paint = foregroundColor = color; validateColor(); if ((eargb >> 24) == -1) { if (paintState == PAINT_OPAQUECOLOR) { return; } paintState = PAINT_OPAQUECOLOR; if (imageComp == CompositeType.SrcOverNoEa) { // special case where compState depends on opacity of paint compositeState = COMP_ISCOPY; } } else { if (paintState == PAINT_ALPHACOLOR) { return; } paintState = PAINT_ALPHACOLOR; if (imageComp == CompositeType.SrcOverNoEa) { // special case where compState depends on opacity of paint compositeState = COMP_ALPHA; } } validFontInfo = false; invalidatePipe(); } /** * Sets the background color in this context used for clearing a region. * When Graphics2D is constructed for a component, the backgroung color is * inherited from the component. Setting the background color in the * Graphics2D context only affects the subsequent clearRect() calls and * not the background color of the component. To change the background * of the component, use appropriate methods of the component. * @param color The background color that should be used in * subsequent calls to clearRect(). * @see getBackground * @see Graphics.clearRect() */ public void setBackground(Color color) { backgroundColor = color; } /** * Returns the background color used for clearing a region. * @see setBackground */ public Color getBackground() { return backgroundColor; } /** * Returns the current Stroke in the Graphics2D state. * @see setStroke */ public Stroke getStroke() { return stroke; } public Rectangle getClipBounds() { Rectangle r; if (clipState == CLIP_DEVICE) { r = null; } else if (transformState <= TRANSFORM_INT_TRANSLATE) { if (usrClip instanceof Rectangle) { r = new Rectangle((Rectangle) usrClip); } else { r = usrClip.getBounds(); } r.translate(-transX, -transY); } else { r = getClip().getBounds(); } return r; } public Rectangle getClipBounds(Rectangle r) { if (clipState != CLIP_DEVICE) { if (transformState <= TRANSFORM_INT_TRANSLATE) { if (usrClip instanceof Rectangle) { r.setBounds((Rectangle) usrClip); } else { r.setBounds(usrClip.getBounds()); } r.translate(-transX, -transY); } else { r.setBounds(getClip().getBounds()); } } else if (r == null) { throw new NullPointerException("null rectangle parameter"); } return r; } public boolean hitClip(int x, int y, int width, int height) { if (width <= 0 || height <= 0) { return false; } if (transformState > TRANSFORM_INT_TRANSLATE) { // Note: Technically the most accurate test would be to // raster scan the parallelogram of the transformed rectangle // and do a span for span hit test against the clip, but for // speed we approximate the test with a bounding box of the // transformed rectangle. The cost of rasterizing the // transformed rectangle is probably high enough that it is // not worth doing so to save the caller from having to call // a rendering method where we will end up discovering the // same answer in about the same amount of time anyway. // This logic breaks down if this hit test is being performed // on the bounds of a group of shapes in which case it might // be beneficial to be a little more accurate to avoid lots // of subsequent rendering calls. In either case, this relaxed // test should not be significantly less accurate than the // optimal test for most transforms and so the conservative // answer should not cause too much extra work. double d[] = { x, y, x+width, y, x, y+height, x+width, y+height }; transform.transform(d, 0, d, 0, 4); x = (int) Math.floor(Math.min(Math.min(d[0], d[2]), Math.min(d[4], d[6]))); y = (int) Math.floor(Math.min(Math.min(d[1], d[3]), Math.min(d[5], d[7]))); width = (int) Math.ceil(Math.max(Math.max(d[0], d[2]), Math.max(d[4], d[6]))); height = (int) Math.ceil(Math.max(Math.max(d[1], d[3]), Math.max(d[5], d[7]))); } else { x += transX; y += transY; width += x; height += y; } if (!getCompClip().intersectsQuickCheckXYXY(x, y, width, height)) { return false; } // REMIND: We could go one step further here and examine the // non-rectangular clip shape more closely if there is one. // Since the clip has already been rasterized, the performance // penalty of doing the scan is probably still within the bounds // of a good tradeoff between speed and quality of the answer. return true; } protected void validateCompClip() { int origClipState = clipState; if (usrClip == null) { clipState = CLIP_DEVICE; clipRegion = devClip; } else if (usrClip instanceof Rectangle2D) { clipState = CLIP_RECTANGULAR; if (usrClip instanceof Rectangle) { clipRegion = devClip.getIntersection((Rectangle)usrClip); } else { clipRegion = devClip.getIntersection(usrClip.getBounds()); } } else { PathIterator cpi = usrClip.getPathIterator(null); int box[] = new int[4]; ShapeSpanIterator sr = LoopPipe.getFillSSI(this); try { sr.setOutputArea(devClip); sr.appendPath(cpi); sr.getPathBox(box); Region r = Region.getInstance(box); r.appendSpans(sr); clipRegion = r; clipState = r.isRectangular() ? CLIP_RECTANGULAR : CLIP_SHAPE; } finally { sr.dispose(); } } if (origClipState != clipState && (clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE)) { validFontInfo = false; invalidatePipe(); } } static final int NON_RECTILINEAR_TRANSFORM_MASK = (AffineTransform.TYPE_GENERAL_TRANSFORM | AffineTransform.TYPE_GENERAL_ROTATION); protected Shape transformShape(Shape s) { if (s == null) { return null; } if (transformState > TRANSFORM_INT_TRANSLATE) { return transformShape(transform, s); } else { return transformShape(transX, transY, s); } } public Shape untransformShape(Shape s) { if (s == null) { return null; } if (transformState > TRANSFORM_INT_TRANSLATE) { try { return transformShape(transform.createInverse(), s); } catch (NoninvertibleTransformException e) { return null; } } else { return transformShape(-transX, -transY, s); } } protected static Shape transformShape(int tx, int ty, Shape s) { if (s == null) { return null; } if (s instanceof Rectangle) { Rectangle r = s.getBounds(); r.translate(tx, ty); return r; } if (s instanceof Rectangle2D) { Rectangle2D rect = (Rectangle2D) s; return new Rectangle2D.Double(rect.getX() + tx, rect.getY() + ty, rect.getWidth(), rect.getHeight()); } if (tx == 0 && ty == 0) { return cloneShape(s); } AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty); return mat.createTransformedShape(s); } protected static Shape transformShape(AffineTransform tx, Shape clip) { if (clip == null) { return null; } if (clip instanceof Rectangle2D && (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0) { Rectangle2D rect = (Rectangle2D) clip; double matrix[] = new double[4]; matrix[0] = rect.getX(); matrix[1] = rect.getY(); matrix[2] = matrix[0] + rect.getWidth(); matrix[3] = matrix[1] + rect.getHeight(); tx.transform(matrix, 0, matrix, 0, 2); rect = new Rectangle2D.Float(); rect.setFrameFromDiagonal(matrix[0], matrix[1], matrix[2], matrix[3]); return rect; } if (tx.isIdentity()) { return cloneShape(clip); } return tx.createTransformedShape(clip); } public void clipRect(int x, int y, int w, int h) { clip(new Rectangle(x, y, w, h)); } public void setClip(int x, int y, int w, int h) { setClip(new Rectangle(x, y, w, h)); } public Shape getClip() { return untransformShape(usrClip); } public void setClip(Shape sh) { usrClip = transformShape(sh); validateCompClip(); } /** * Intersects the current clip with the specified Path and sets the * current clip to the resulting intersection. The clip is transformed * with the current transform in the Graphics2D state before being * intersected with the current clip. This method is used to make the * current clip smaller. To make the clip larger, use any setClip method. * @param p The Path to be intersected with the current clip. */ public void clip(Shape s) { s = transformShape(s); if (usrClip != null) { s = intersectShapes(usrClip, s, true, true); } usrClip = s; validateCompClip(); } public void setPaintMode() { setComposite(AlphaComposite.SrcOver); } public void setXORMode(Color c) { if (c == null) { throw new IllegalArgumentException("null XORColor"); } setComposite(new XORComposite(c, surfaceData)); } Blit lastCAblit; Composite lastCAcomp; public void copyArea(int x, int y, int w, int h, int dx, int dy) { try { doCopyArea(x, y, w, h, dx, dy); } catch (InvalidPipeException e) { revalidateAll(); try { doCopyArea(x, y, w, h, dx, dy); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } private void doCopyArea(int x, int y, int w, int h, int dx, int dy) { if (w <= 0 || h <= 0) { return; } SurfaceData theData = surfaceData; if (theData.copyArea(this, x, y, w, h, dx, dy)) { return; } if (transformState >= TRANSFORM_TRANSLATESCALE) { throw new InternalError("transformed copyArea not implemented yet"); } // REMIND: This method does not deal with missing data from the // source object (i.e. it does not send exposure events...) Region clip = getCompClip(); Composite comp = composite; if (lastCAcomp != comp) { SurfaceType dsttype = theData.getSurfaceType(); CompositeType comptype = imageComp; if (CompositeType.SrcOverNoEa.equals(comptype) && theData.getTransparency() == Transparency.OPAQUE) { comptype = CompositeType.SrcNoEa; } lastCAblit = Blit.locate(dsttype, comptype, dsttype); lastCAcomp = comp; } x += transX; y += transY; Blit ob = lastCAblit; if (dy == 0 && dx > 0 && dx < w) { while (w > 0) { int partW = Math.min(w, dx); w -= partW; int sx = x + w; ob.Blit(theData, theData, comp, clip, sx, y, sx+dx, y+dy, partW, h); } return; } if (dy > 0 && dy < h && dx > -w && dx < w) { while (h > 0) { int partH = Math.min(h, dy); h -= partH; int sy = y + h; ob.Blit(theData, theData, comp, clip, x, sy, x+dx, sy+dy, w, partH); } return; } ob.Blit(theData, theData, comp, clip, x, y, x+dx, y+dy, w, h); } /* public void XcopyArea(int x, int y, int w, int h, int dx, int dy) { Rectangle rect = new Rectangle(x, y, w, h); rect = transformBounds(rect, transform); Point2D point = new Point2D.Float(dx, dy); Point2D root = new Point2D.Float(0, 0); point = transform.transform(point, point); root = transform.transform(root, root); int fdx = (int)(point.getX()-root.getX()); int fdy = (int)(point.getY()-root.getY()); Rectangle r = getCompBounds().intersection(rect.getBounds()); if (r.isEmpty()) { return; } // Begin Rasterizer for Clip Shape boolean skipClip = true; byte[] clipAlpha = null; if (clipState == CLIP_SHAPE) { int box[] = new int[4]; clipRegion.getBounds(box); Rectangle devR = new Rectangle(box[0], box[1], box[2] - box[0], box[3] - box[1]); if (!devR.isEmpty()) { OutputManager mgr = getOutputManager(); RegionIterator ri = clipRegion.getIterator(); while (ri.nextYRange(box)) { int spany = box[1]; int spanh = box[3] - spany; while (ri.nextXBand(box)) { int spanx = box[0]; int spanw = box[2] - spanx; mgr.copyArea(this, null, spanw, 0, spanx, spany, spanw, spanh, fdx, fdy, null); } } } return; } // End Rasterizer for Clip Shape getOutputManager().copyArea(this, null, r.width, 0, r.x, r.y, r.width, r.height, fdx, fdy, null); } */ public void drawLine(int x1, int y1, int x2, int y2) { try { drawpipe.drawLine(this, x1, y1, x2, y2); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawLine(this, x1, y1, x2, y2); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawRoundRect(int x, int y, int w, int h, int arcW, int arcH) { try { drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void fillRoundRect(int x, int y, int w, int h, int arcW, int arcH) { try { fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawOval(int x, int y, int w, int h) { try { drawpipe.drawOval(this, x, y, w, h); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawOval(this, x, y, w, h); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void fillOval(int x, int y, int w, int h) { try { fillpipe.fillOval(this, x, y, w, h); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillOval(this, x, y, w, h); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawArc(int x, int y, int w, int h, int startAngl, int arcAngl) { try { drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void fillArc(int x, int y, int w, int h, int startAngl, int arcAngl) { try { fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawPolyline(int xPoints[], int yPoints[], int nPoints) { try { drawpipe.drawPolyline(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawPolyline(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawPolygon(int xPoints[], int yPoints[], int nPoints) { try { drawpipe.drawPolygon(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawPolygon(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void fillPolygon(int xPoints[], int yPoints[], int nPoints) { try { fillpipe.fillPolygon(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillPolygon(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawRect (int x, int y, int w, int h) { try { drawpipe.drawRect(this, x, y, w, h); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawRect(this, x, y, w, h); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void fillRect (int x, int y, int w, int h) { try { fillpipe.fillRect(this, x, y, w, h); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillRect(this, x, y, w, h); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } private void revalidateAll() { try { // REMIND: This locking needs to be done around the // caller of this method so that the pipe stays valid // long enough to call the new primitive. // REMIND: No locking yet in screen SurfaceData objects! // surfaceData.lock(); surfaceData = surfaceData.getReplacement(); if (surfaceData == null) { surfaceData = NullSurfaceData.theInstance; } // this will recalculate the composite clip setDevClip(surfaceData.getBounds()); if (paintState <= PAINT_ALPHACOLOR) { validateColor(); } if (composite instanceof XORComposite) { Color c = ((XORComposite) composite).getXorColor(); setComposite(new XORComposite(c, surfaceData)); } validatePipe(); } finally { // REMIND: No locking yet in screen SurfaceData objects! // surfaceData.unlock(); } } public void clearRect(int x, int y, int w, int h) { // REMIND: has some "interesting" consequences if threads are // not synchronized Composite c = composite; Paint p = paint; setComposite(AlphaComposite.Src); setColor(getBackground()); validatePipe(); fillRect(x, y, w, h); setPaint(p); setComposite(c); } /** * Strokes the outline of a Path using the settings of the current * graphics state. The rendering attributes applied include the * clip, transform, paint or color, composite and stroke attributes. * @param p The path to be drawn. * @see #setStroke * @see #setPaint * @see java.awt.Graphics#setColor * @see #transform * @see #setTransform * @see #clip * @see #setClip * @see #setComposite */ public void draw(Shape s) { try { shapepipe.draw(this, s); } catch (InvalidPipeException e) { revalidateAll(); try { shapepipe.draw(this, s); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } /** * Fills the interior of a Path using the settings of the current * graphics state. The rendering attributes applied include the * clip, transform, paint or color, and composite. * @see #setPaint * @see java.awt.Graphics#setColor * @see #transform * @see #setTransform * @see #setComposite * @see #clip * @see #setClip */ public void fill(Shape s) { try { shapepipe.fill(this, s); } catch (InvalidPipeException e) { revalidateAll(); try { shapepipe.fill(this, s); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } /** * Returns true if the given AffineTransform is an integer * translation. */ private static boolean isIntegerTranslation(AffineTransform xform) { if (xform.isIdentity()) { return true; } if (xform.getType() == AffineTransform.TYPE_TRANSLATION) { double tx = xform.getTranslateX(); double ty = xform.getTranslateY(); return (tx == (int)tx && ty == (int)ty); } return false; } /** * Returns the index of the tile corresponding to the supplied position * given the tile grid offset and size along the same axis. */ private static int getTileIndex(int p, int tileGridOffset, int tileSize) { p -= tileGridOffset; if (p < 0) { p += 1 - tileSize; // force round to -infinity (ceiling) } return p/tileSize; } /** * Returns a rectangle in image coordinates that may be required * in order to draw the given image into the given clipping region * through a pair of AffineTransforms. In addition, horizontal and * vertical padding factors for antialising and interpolation may * be used. */ private static Rectangle getImageRegion(RenderedImage img, Region compClip, AffineTransform transform, AffineTransform xform, int padX, int padY) { Rectangle imageRect = new Rectangle(img.getMinX(), img.getMinY(), img.getWidth(), img.getHeight()); Rectangle result = null; try { double p[] = new double[8]; p[0] = p[2] = compClip.getLoX(); p[4] = p[6] = compClip.getHiX(); p[1] = p[5] = compClip.getLoY(); p[3] = p[7] = compClip.getHiY(); // Inverse transform the output bounding rect transform.inverseTransform(p, 0, p, 0, 4); xform.inverseTransform(p, 0, p, 0, 4); // Determine a bounding box for the inverse transformed region double x0,x1,y0,y1; x0 = x1 = p[0]; y0 = y1 = p[1]; for (int i = 2; i < 8; ) { double pt = p[i++]; if (pt < x0) { x0 = pt; } else if (pt > x1) { x1 = pt; } pt = p[i++]; if (pt < y0) { y0 = pt; } else if (pt > y1) { y1 = pt; } } // This is padding for anti-aliasing and such. It may // be more than is needed. int x = (int)x0 - padX; int w = (int)(x1 - x0 + 2*padX); int y = (int)y0 - padY; int h = (int)(y1 - y0 + 2*padY); Rectangle clipRect = new Rectangle(x,y,w,h); result = clipRect.intersection(imageRect); } catch (NoninvertibleTransformException nte) { // Worst case bounds are the bounds of the image. result = imageRect; } return result; } /** * Draws an image, applying a transform from image space into user space * before drawing. * The transformation from user space into device space is done with * the current transform in the Graphics2D. * The given transformation is applied to the image before the * transform attribute in the Graphics2D state is applied. * The rendering attributes applied include the clip, transform, * and composite attributes. Note that the result is * undefined, if the given transform is noninvertible. * @param img The image to be drawn. Does nothing if img is null. * @param xform The transformation from image space into user space. * @see #transform * @see #setTransform * @see #setComposite * @see #clip * @see #setClip */ public void drawRenderedImage(RenderedImage img, AffineTransform xform) { if (img == null) { return; } // BufferedImage case: use a simple drawImage call if (img instanceof BufferedImage) { BufferedImage bufImg = (BufferedImage)img; drawImage(bufImg,xform,null); return; } // transformState tracks the state of transform and // transX, transY contain the integer casts of the // translation factors boolean isIntegerTranslate = (transformState <= TRANSFORM_INT_TRANSLATE) && isIntegerTranslation(xform); // Include padding for interpolation/antialiasing if necessary int pad = isIntegerTranslate ? 0 : 3; // Determine the region of the image that may contribute to // the clipped drawing area Rectangle region = getImageRegion(img, getCompClip(), transform, xform, pad, pad); if (region.width <= 0 || region.height <= 0) { return; } // Attempt to optimize integer translation of tiled images. // Although theoretically we are O.K. if the concatenation of // the user transform and the device transform is an integer // translation, we'll play it safe and only optimize the case // where both are integer translations. if (isIntegerTranslate) { // Use optimized code // Note that drawTranslatedRenderedImage calls copyImage // which takes the user space to device space transform into // account, but we need to provide the image space to user space // translations. drawTranslatedRenderedImage(img, region, (int) xform.getTranslateX(), (int) xform.getTranslateY()); return; } // General case: cobble the necessary region into a single Raster Raster raster = img.getData(region); // Make a new Raster with the same contents as raster // but starting at (0, 0). This raster is thus in the same // coordinate system as the SampleModel of the original raster. WritableRaster wRaster = Raster.createWritableRaster(raster.getSampleModel(), raster.getDataBuffer(), null); // If the original raster was in a different coordinate // system than its SampleModel, we need to perform an // additional translation in order to get the (minX, minY) // pixel of raster to be pixel (0, 0) of wRaster. We also // have to have the correct width and height. int minX = raster.getMinX(); int minY = raster.getMinY(); int width = raster.getWidth(); int height = raster.getHeight(); int px = minX - raster.getSampleModelTranslateX(); int py = minY - raster.getSampleModelTranslateY(); if (px != 0 || py != 0 || width != wRaster.getWidth() || height != wRaster.getHeight()) { wRaster = wRaster.createWritableChild(px, py, width, height, 0, 0, null); } // Now we have a BufferedImage starting at (0, 0) // with the same contents that started at (minX, minY) // in raster. So we must draw the BufferedImage with a // translation of (minX, minY). AffineTransform transXform = (AffineTransform)xform.clone(); transXform.translate(minX, minY); ColorModel cm = img.getColorModel(); BufferedImage bufImg = new BufferedImage(cm, wRaster, cm.isAlphaPremultiplied(), null); drawImage(bufImg, transXform, null); } /** * Intersects destRect with clip and * overwrites destRect with the result. * Returns false if the intersection was empty, true otherwise. */ private boolean clipTo(Rectangle destRect, Rectangle clip) { int x1 = Math.max(destRect.x, clip.x); int x2 = Math.min(destRect.x + destRect.width, clip.x + clip.width); int y1 = Math.max(destRect.y, clip.y); int y2 = Math.min(destRect.y + destRect.height, clip.y + clip.height); if (((x2 - x1) < 0) || ((y2 - y1) < 0)) { destRect.width = -1; // Set both just to be safe destRect.height = -1; return false; } else { destRect.x = x1; destRect.y = y1; destRect.width = x2 - x1; destRect.height = y2 - y1; return true; } } /** * Draw a portion of a RenderedImage tile-by-tile with a given * integer image to user space translation. The user to * device transform must also be an integer translation. */ private void drawTranslatedRenderedImage(RenderedImage img, Rectangle region, int i2uTransX, int i2uTransY) { // Cache tile grid info int tileGridXOffset = img.getTileGridXOffset(); int tileGridYOffset = img.getTileGridYOffset(); int tileWidth = img.getTileWidth(); int tileHeight = img.getTileHeight(); // Determine the tile index extrema in each direction int minTileX = getTileIndex(region.x, tileGridXOffset, tileWidth); int minTileY = getTileIndex(region.y, tileGridYOffset, tileHeight); int maxTileX = getTileIndex(region.x + region.width - 1, tileGridXOffset, tileWidth); int maxTileY = getTileIndex(region.y + region.height - 1, tileGridYOffset, tileHeight); // Create a single ColorModel to use for all BufferedImages ColorModel colorModel = img.getColorModel(); // Reuse the same Rectangle for each iteration Rectangle tileRect = new Rectangle(); for (int ty = minTileY; ty <= maxTileY; ty++) { for (int tx = minTileX; tx <= maxTileX; tx++) { // Get the current tile. Raster raster = img.getTile(tx, ty); // Fill in tileRect with the tile bounds tileRect.x = tx*tileWidth + tileGridXOffset; tileRect.y = ty*tileHeight + tileGridYOffset; tileRect.width = tileWidth; tileRect.height = tileHeight; // Clip the tile against the image bounds and // backwards mapped clip region // The result can't be empty clipTo(tileRect, region); // Create a WritableRaster containing the tile WritableRaster wRaster = null; if (raster instanceof WritableRaster) { wRaster = (WritableRaster)raster; } else { // Create a WritableRaster in the same coordinate system // as the original raster. wRaster = Raster.createWritableRaster(raster.getSampleModel(), raster.getDataBuffer(), null); } // Translate wRaster to start at (0, 0) and to contain // only the relevent portion of the tile wRaster = wRaster.createWritableChild(tileRect.x, tileRect.y, tileRect.width, tileRect.height, 0, 0, null); // Wrap wRaster in a BufferedImage BufferedImage bufImg = new BufferedImage(colorModel, wRaster, colorModel.isAlphaPremultiplied(), null); // Now we have a BufferedImage starting at (0, 0) that // represents data from a Raster starting at // (tileRect.x, tileRect.y). Additionally, it needs // to be translated by (i2uTransX, i2uTransY). We call // copyImage to draw just the region of interest // without needing to create a child image. copyImage(bufImg, tileRect.x + i2uTransX, tileRect.y + i2uTransY, 0, 0, tileRect.width, tileRect.height, null, null); } } } public void drawRenderableImage(RenderableImage img, AffineTransform xform) { if (img == null) { return; } AffineTransform pipeTransform = transform; AffineTransform concatTransform = new AffineTransform(xform); concatTransform.concatenate(pipeTransform); AffineTransform reverseTransform; RenderContext rc = new RenderContext(concatTransform); try { reverseTransform = pipeTransform.createInverse(); } catch (NoninvertibleTransformException nte) { rc = new RenderContext(pipeTransform); reverseTransform = new AffineTransform(); } RenderedImage rendering = img.createRendering(rc); drawRenderedImage(rendering,reverseTransform); } /* * Transform the bounding box of the BufferedImage */ protected Rectangle transformBounds(Rectangle rect, AffineTransform tx) { if (tx.isIdentity()) { return rect; } Shape s = transformShape(tx, rect); return s.getBounds(); } // text rendering methods public void drawString(String str, int x, int y) { if (str == null) { throw new NullPointerException("String is null"); } if (font.hasLayoutAttributes()) { if (str.length() == 0) { return; } new TextLayout(str, font, getFontRenderContext()).draw(this, x, y); return; } try { textpipe.drawString(this, str, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawString(this, str, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawString(String str, float x, float y) { if (str == null) { throw new NullPointerException("String is null"); } if (font.hasLayoutAttributes()) { if (str.length() == 0) { return; } new TextLayout(str, font, getFontRenderContext()).draw(this, x, y); return; } try { textpipe.drawString(this, str, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawString(this, str, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawString(AttributedCharacterIterator iterator, int x, int y) { if (iterator == null) { throw new NullPointerException("AttributedCharacterIterator is null"); } if (iterator.getBeginIndex() == iterator.getEndIndex()) { return; /* nothing to draw */ } TextLayout tl = new TextLayout(iterator, getFontRenderContext()); tl.draw(this, (float) x, (float) y); } public void drawString(AttributedCharacterIterator iterator, float x, float y) { if (iterator == null) { throw new NullPointerException("AttributedCharacterIterator is null"); } if (iterator.getBeginIndex() == iterator.getEndIndex()) { return; /* nothing to draw */ } TextLayout tl = new TextLayout(iterator, getFontRenderContext()); tl.draw(this, x, y); } public void drawGlyphVector(GlyphVector gv, float x, float y) { if (gv == null) { throw new NullPointerException("GlyphVector is null"); } try { textpipe.drawGlyphVector(this, gv, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawGlyphVector(this, gv, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawChars(char data[], int offset, int length, int x, int y) { if (data == null) { throw new NullPointerException("char data is null"); } if (offset < 0 || length < 0 || offset + length > data.length) { throw new ArrayIndexOutOfBoundsException("bad offset/length"); } if (font.hasLayoutAttributes()) { if (data.length == 0) { return; } new TextLayout(new String(data, offset, length), font, getFontRenderContext()).draw(this, x, y); return; } try { textpipe.drawChars(this, data, offset, length, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawChars(this, data, offset, length, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } public void drawBytes(byte data[], int offset, int length, int x, int y) { if (data == null) { throw new NullPointerException("byte data is null"); } if (offset < 0 || length < 0 || offset + length > data.length) { throw new ArrayIndexOutOfBoundsException("bad offset/length"); } /* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */ char chData[] = new char[length]; for (int i = length; i-- > 0; ) { chData[i] = (char)(data[i+offset] & 0xff); } if (font.hasLayoutAttributes()) { if (data.length == 0) { return; } new TextLayout(new String(chData), font, getFontRenderContext()).draw(this, x, y); return; } try { textpipe.drawChars(this, chData, 0, length, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawChars(this, chData, 0, length, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } // end of text rendering methods /** * Draws an image scaled to x,y,w,h in nonblocking mode with a * callback object. */ public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { return drawImage(img, x, y, width, height, null, observer); } /** * Not part of the advertised API but a useful utility method * to call internally. This is for the case where we are * drawing to/from given coordinates using a given width/height, * but we guarantee that the weidth/height of the src and dest * areas are equal (no scale needed). */ public boolean copyImage(Image img, int dx, int dy, int sx, int sy, int width, int height, Color bgcolor, ImageObserver observer) { try { return imagepipe.copyImage(this, img, dx, dy, sx, sy, width, height, bgcolor, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.copyImage(this, img, dx, dy, sx, sy, width, height, bgcolor, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } finally { surfaceData.markDirty(); } } /** * Draws an image scaled to x,y,w,h in nonblocking mode with a * solid background color and a callback object. */ public boolean drawImage(Image img, int x, int y, int width, int height, Color bg, ImageObserver observer) { if (img == null) { return true; } if ((width == 0) || (height == 0)) { return true; } if (width == img.getWidth(null) && height == img.getHeight(null)) { return copyImage(img, x, y, 0, 0, width, height, bg, observer); } try { return imagepipe.scaleImage(this, img, x, y, width, height, bg, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.scaleImage(this, img, x, y, width, height, bg, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } finally { surfaceData.markDirty(); } } /** * Draws an image at x,y in nonblocking mode. */ public boolean drawImage(Image img, int x, int y, ImageObserver observer) { return drawImage(img, x, y, null, observer); } /** * Draws an image at x,y in nonblocking mode with a solid background * color and a callback object. */ public boolean drawImage(Image img, int x, int y, Color bg, ImageObserver observer) { if (img == null) { return true; } try { return imagepipe.copyImage(this, img, x, y, bg, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.copyImage(this, img, x, y, bg, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } finally { surfaceData.markDirty(); } } /** * Draws a subrectangle of an image scaled to a destination rectangle * in nonblocking mode with a callback object. */ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer); } /** * Draws a subrectangle of an image scaled to a destination rectangle in * nonblocking mode with a solid background color and a callback object. */ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) { if (img == null) { return true; } if (dx1 == dx2 || dy1 == dy2 || sx1 == sx2 || sy1 == sy2) { return true; } if (((sx2 - sx1) == (dx2 - dx1)) && ((sy2 - sy1) == (dy2 - dy1))) { // Not a scale - forward it to a copy routine int srcX, srcY, dstX, dstY, width, height; if (sx2 > sx1) { width = sx2 - sx1; srcX = sx1; dstX = dx1; } else { width = sx1 - sx2; srcX = sx2; dstX = dx2; } if (sy2 > sy1) { height = sy2-sy1; srcY = sy1; dstY = dy1; } else { height = sy1-sy2; srcY = sy2; dstY = dy2; } return copyImage(img, dstX, dstY, srcX, srcY, width, height, bgcolor, observer); } try { return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } finally { surfaceData.markDirty(); } } /** * Draw an image, applying a transform from image space into user space * before drawing. * The transformation from user space into device space is done with * the current transform in the Graphics2D. * The given transformation is applied to the image before the * transform attribute in the Graphics2D state is applied. * The rendering attributes applied include the clip, transform, * paint or color and composite attributes. Note that the result is * undefined, if the given transform is non-invertible. * @param img The image to be drawn. * @param xform The transformation from image space into user space. * @param observer The image observer to be notified on the image producing * progress. * @see #transform * @see #setComposite * @see #setClip */ public boolean drawImage(Image img, AffineTransform xform, ImageObserver observer) { if (img == null) { return true; } if (xform == null || xform.isIdentity()) { return drawImage(img, 0, 0, null, observer); } try { return imagepipe.transformImage(this, img, xform, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.transformImage(this, img, xform, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } finally { surfaceData.markDirty(); } } public void drawImage(BufferedImage bImg, BufferedImageOp op, int x, int y) { if (bImg == null) { return; } try { imagepipe.transformImage(this, bImg, op, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { imagepipe.transformImage(this, bImg, op, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } finally { surfaceData.markDirty(); } } /** * Get the rendering context of the font * within this Graphics2D context. */ public FontRenderContext getFontRenderContext() { if (cachedFRC == null) { int aahint = textAntialiasHint; if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT && antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; } // Translation components should be excluded from the FRC transform AffineTransform tx = null; if (transformState >= TRANSFORM_TRANSLATESCALE) { if (transform.getTranslateX() == 0 && transform.getTranslateY() == 0) { tx = transform; } else { tx = new AffineTransform(transform.getScaleX(), transform.getShearY(), transform.getShearX(), transform.getScaleY(), 0, 0); } } cachedFRC = new FontRenderContext(tx, SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, aahint), SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS, fractionalMetricsHint)); } return cachedFRC; } private FontRenderContext cachedFRC; /** * This object has no resources to dispose of per se, but the * doc comments for the base method in java.awt.Graphics imply * that this object will not be useable after it is disposed. * So, we sabotage the object to prevent further use to prevent * developers from relying on behavior that may not work on * other, less forgiving, VMs that really need to dispose of * resources. */ public void dispose() { surfaceData = NullSurfaceData.theInstance; invalidatePipe(); } /** * Graphics has a finalize method that automatically calls dispose() * for subclasses. For SunGraphics2D we do not need to be finalized * so that method simply causes us to be enqueued on the Finalizer * queues for no good reason. Unfortunately, that method and * implementation are now considered part of the public contract * of that base class so we can not remove or gut the method. * We override it here with an empty method and the VM is smart * enough to know that if our override is empty then it should not * mark us as finalizeable. */ public void finalize() { // DO NOT REMOVE THIS METHOD } /** * Returns destination that this Graphics renders to. This could be * either an Image or a Component; subclasses of SurfaceData are * responsible for returning the appropriate object. */ public Object getDestination() { return surfaceData.getDestination(); } /** * {@inheritDoc} * * @see sun.java2d.DestSurfaceProvider#getDestSurface */ @Override public Surface getDestSurface() { return surfaceData; } }