| /* |
| * Copyright (c) 1996, 2011, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.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.awt.image.SurfaceManager; |
| 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 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.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; |
| protected 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 in pixels |
| public int constrainX; |
| public int constrainY; |
| |
| public Region clipRegion; |
| public Shape usrClip; |
| protected Region devClip; // Actual physical drawable in pixels |
| |
| private final int devScale; // Actual physical scale factor |
| |
| // 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(); |
| |
| devScale = sd.getDefaultScale(); |
| if (devScale != 1) { |
| transform.setToScale(devScale, devScale); |
| invalidateTransform(); |
| } |
| |
| font = f; |
| if (font == null) { |
| font = defaultFont; |
| } |
| |
| 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. |
| */ |
| public void constrain(int x, int y, int w, int h, Region region) { |
| if ((x | y) != 0) { |
| translate(x, y); |
| } |
| if (transformState > TRANSFORM_TRANSLATESCALE) { |
| clipRect(0, 0, w, h); |
| return; |
| } |
| // changes parameters according to the current scale and translate. |
| final double scaleX = transform.getScaleX(); |
| final double scaleY = transform.getScaleY(); |
| x = constrainX = (int) transform.getTranslateX(); |
| y = constrainY = (int) transform.getTranslateY(); |
| w = Region.dimAdd(x, Region.clipScale(w, scaleX)); |
| h = Region.dimAdd(y, Region.clipScale(h, scaleY)); |
| |
| Region c = constrainClip; |
| if (c == null) { |
| c = Region.getInstanceXYXY(x, y, w, h); |
| } else { |
| c = c.getIntersectionXYXY(x, y, w, h); |
| } |
| if (region != null) { |
| region = region.getScaledRegion(scaleX, scaleY); |
| region = region.getTranslatedRegion(x, y); |
| c = c.getIntersection(region); |
| } |
| |
| if (c == constrainClip) { |
| // Common case to ignore |
| return; |
| } |
| |
| constrainClip = c; |
| if (!devClip.isInsideQuickCheck(c)) { |
| devClip = devClip.getIntersection(c); |
| validateCompClip(); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| @Override |
| public void constrain(int x, int y, int w, int h) { |
| constrain(x, y, w, h, null); |
| } |
| |
| protected static ValidatePipe invalidpipe = new ValidatePipe(); |
| |
| /* |
| * Invalidate the pipeline |
| */ |
| protected void invalidatePipe() { |
| drawpipe = invalidpipe; |
| fillpipe = invalidpipe; |
| shapepipe = invalidpipe; |
| textpipe = invalidpipe; |
| imagepipe = invalidpipe; |
| loops = null; |
| } |
| |
| public void validatePipe() { |
| /* This workaround is for the situation when we update the Pipelines |
| * for invalid SurfaceData and run further code when the current |
| * pipeline doesn't support the type of new SurfaceData created during |
| * the current pipeline's work (in place of the invalid SurfaceData). |
| * Usually SurfaceData and Pipelines are repaired (through revalidateAll) |
| * and called again in the exception handlers */ |
| |
| if (!surfaceData.isValid()) { |
| throw new InvalidPipeException("attempt to validate Pipe with invalid SurfaceData"); |
| } |
| |
| 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<TEXTARRSIZE;i++) { |
| textTxArr[i] = new double [] {i, 0, 0, i}; |
| textAtArr[i] = new AffineTransform( textTxArr[i]); |
| } |
| } |
| |
| // cached state for various draw[String,Char,Byte] optimizations |
| public FontInfo checkFontInfo(FontInfo info, Font font, |
| FontRenderContext frc) { |
| /* Do not create a FontInfo object as part of construction of an |
| * SG2D as its possible it may never be needed - ie if no text |
| * is drawn using this SG2D. |
| */ |
| if (info == null) { |
| info = new FontInfo(); |
| } |
| |
| float ptSize = font.getSize2D(); |
| int txFontType; |
| AffineTransform devAt, textAt=null; |
| if (font.isTransformed()) { |
| textAt = font.getTransform(); |
| textAt.scale(ptSize, ptSize); |
| txFontType = textAt.getType(); |
| info.originX = (float)textAt.getTranslateX(); |
| info.originY = (float)textAt.getTranslateY(); |
| textAt.translate(-info.originX, -info.originY); |
| if (transformState >= 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: |
| * <pre> |
| * [ 1 0 tx ] |
| * [ 0 1 ty ] |
| * [ 0 0 1 ] |
| * </pre> |
| */ |
| 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: |
| * <pre> |
| * [ cos(theta) -sin(theta) 0 ] |
| * [ sin(theta) cos(theta) 0 ] |
| * [ 0 0 1 ] |
| * </pre> |
| * 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: |
| * <pre> |
| * translate(x, y); |
| * rotate(theta); |
| * translate(-x, -y); |
| * </pre> |
| * 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: |
| * <pre> |
| * [ sx 0 0 ] |
| * [ 0 sy 0 ] |
| * [ 0 0 1 ] |
| * </pre> |
| */ |
| 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: |
| * <pre> |
| * [ 1 shx 0 ] |
| * [ shy 1 0 ] |
| * [ 0 0 1 ] |
| * </pre> |
| * @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 |
| */ |
| @Override |
| public void setTransform(AffineTransform Tx) { |
| if ((constrainX | constrainY) == 0 && devScale == 1) { |
| transform.setTransform(Tx); |
| } else { |
| transform.setTransform(devScale, 0, 0, devScale, 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 |
| */ |
| @Override |
| public AffineTransform getTransform() { |
| if ((constrainX | constrainY) == 0 && devScale == 1) { |
| return new AffineTransform(transform); |
| } |
| final double invScale = 1.0 / devScale; |
| AffineTransform tx = new AffineTransform(invScale, 0, 0, invScale, |
| -constrainX * invScale, |
| -constrainY * invScale); |
| 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() { |
| if (clipState == CLIP_DEVICE) { |
| return null; |
| } |
| return getClipBounds(new Rectangle()); |
| } |
| |
| public Rectangle getClipBounds(Rectangle r) { |
| if (clipState != CLIP_DEVICE) { |
| if (transformState <= TRANSFORM_INT_TRANSLATE) { |
| if (usrClip instanceof Rectangle) { |
| r.setBounds((Rectangle) usrClip); |
| } else { |
| r.setFrame(usrClip.getBounds2D()); |
| } |
| r.translate(-transX, -transY); |
| } else { |
| r.setFrame(getClip().getBounds2D()); |
| } |
| } 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; |
| } |
| |
| try { |
| if (!getCompClip().intersectsQuickCheckXYXY(x, y, width, height)) { |
| return false; |
| } |
| } catch (InvalidPipeException e) { |
| 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); |
| fixRectangleOrientation(matrix, rect); |
| return new Rectangle2D.Double(matrix[0], matrix[1], |
| matrix[2] - matrix[0], |
| matrix[3] - matrix[1]); |
| } |
| |
| if (tx.isIdentity()) { |
| return cloneShape(clip); |
| } |
| |
| return tx.createTransformedShape(clip); |
| } |
| |
| /** |
| * Sets orientation of the rectangle according to the clip. |
| */ |
| private static void fixRectangleOrientation(double[] m, Rectangle2D clip) { |
| if (clip.getWidth() > 0 != (m[2] - m[0] > 0)) { |
| double t = m[0]; |
| m[0] = m[2]; |
| m[2] = t; |
| } |
| if (clip.getHeight() > 0 != (m[3] - m[1] > 0)) { |
| double t = m[1]; |
| m[1] = m[3]; |
| m[3] = t; |
| } |
| } |
| |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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()); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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; |
| |
| Region clip; |
| try { |
| clip = getCompClip(); |
| } catch (InvalidPipeException e) { |
| return; |
| } |
| |
| // Determine the region of the image that may contribute to |
| // the clipped drawing area |
| Rectangle region = getImageRegion(img, |
| clip, |
| 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 <code>destRect</code> with <code>clip</code> and |
| * overwrites <code>destRect</code> 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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 |
| |
| private static boolean isHiDPIImage(final Image img) { |
| return SurfaceManager.getImageScale(img) != 1; |
| } |
| |
| private boolean drawHiDPIImage(Image img, int dx1, int dy1, int dx2, |
| int dy2, int sx1, int sy1, int sx2, int sy2, |
| Color bgcolor, ImageObserver observer) { |
| final int scale = SurfaceManager.getImageScale(img); |
| sx1 = Region.clipScale(sx1, scale); |
| sx2 = Region.clipScale(sx2, scale); |
| sy1 = Region.clipScale(sy1, scale); |
| sy2 = Region.clipScale(sy2, scale); |
| try { |
| return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, |
| sx2, sy2, bgcolor, observer); |
| } catch (InvalidPipeException e) { |
| try { |
| revalidateAll(); |
| 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(); |
| } |
| } |
| |
| /** |
| * 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 surfaceData's width/height of the src and dest |
| * areas are equal (no scale needed). Note that this method intentionally |
| * ignore scale factor of the source image, and copy it as is. |
| */ |
| 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) { |
| try { |
| revalidateAll(); |
| 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; |
| } |
| |
| final int imgW = img.getWidth(null); |
| final int imgH = img.getHeight(null); |
| if (isHiDPIImage(img)) { |
| return drawHiDPIImage(img, x, y, x + width, y + height, 0, 0, imgW, |
| imgH, bg, observer); |
| } |
| |
| if (width == imgW && height == imgH) { |
| 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) { |
| try { |
| revalidateAll(); |
| 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; |
| } |
| |
| if (isHiDPIImage(img)) { |
| final int imgW = img.getWidth(null); |
| final int imgH = img.getHeight(null); |
| return drawHiDPIImage(img, x, y, x + imgW, y + imgH, 0, 0, imgW, |
| imgH, bg, observer); |
| } |
| |
| try { |
| return imagepipe.copyImage(this, img, x, y, bg, observer); |
| } catch (InvalidPipeException e) { |
| try { |
| revalidateAll(); |
| 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 (isHiDPIImage(img)) { |
| return drawHiDPIImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, |
| bgcolor, observer); |
| } |
| |
| 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) { |
| try { |
| revalidateAll(); |
| 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); |
| } |
| |
| if (isHiDPIImage(img)) { |
| final int w = img.getWidth(null); |
| final int h = img.getHeight(null); |
| final AffineTransform tx = new AffineTransform(transform); |
| transform(xform); |
| boolean result = drawHiDPIImage(img, 0, 0, w, h, 0, 0, w, h, null, |
| observer); |
| transform.setTransform(tx); |
| invalidateTransform(); |
| return result; |
| } |
| |
| try { |
| return imagepipe.transformImage(this, img, xform, observer); |
| } catch (InvalidPipeException e) { |
| try { |
| revalidateAll(); |
| 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) { |
| try { |
| revalidateAll(); |
| 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; |
| } |
| } |