/* * This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com> * Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/) */ /* * @(#)SunGraphics2D.java 1.316 03/03/20 * * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ 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.SampleModel; import java.awt.image.VolatileImage; import java.awt.image.WritableRaster; import java.awt.Image; import java.awt.Composite; import java.awt.Color; import java.awt.color.ColorSpace; import java.awt.image.DataBuffer; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.DirectColorModel; import java.awt.GraphicsConfiguration; import java.awt.Paint; import java.awt.geom.Point2D; 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 sun.awt.image.ImageRepresentation; import sun.awt.image.BufImgSurfaceData; import sun.awt.image.AcceleratedOffScreenImage; import java.awt.image.ColorConvertOp; import java.awt.Transparency; import java.awt.font.GlyphVector; import java.awt.font.TextLayout; import sun.awt.font.FontDesignMetrics; import sun.awt.font.StandardGlyphVector; 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.RegionIterator; import sun.java2d.pipe.TextPipe; import sun.java2d.pipe.DrawImagePipe; import sun.java2d.pipe.DuctusRenderer; 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.BlitBg; import sun.java2d.loops.MaskFill; import sun.awt.font.NativeFontWrapper; import java.awt.font.FontRenderContext; import sun.awt.font.ShapingException; import sun.java2d.loops.XORComposite; import sun.awt.ConstrainableGraphics; import sun.awt.SunHints; import java.util.Map; import java.util.Iterator; import sun.awt.image.OffScreenImage; /** * 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. * * @version 1.211 05/07/98 * @author Jim Graham */ public final class SunGraphics2D extends Graphics2D implements ConstrainableGraphics, Cloneable { public static final ColorModel XRGBModel = new DirectColorModel(24,0x00ff0000,0x0000ff00,0x000000ff); /* * Attribute States */ /* Paint */ public static final int PAINT_TILE = 2; /* delivered as Tile */ public static final int PAINT_SINGLECOLOR = 1; /* Single Color */ public static final int PAINT_SOLIDCOLOR = 0; /* Solid single 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;/* SRC, extraAlpha = 1.0 or * SRC_OVER, extraAlpha = 1.0 */ /* Stroke */ public static final int STROKE_CUSTOM = 2; /* custom Stroke */ public static final int STROKE_WIDE = 1; /* BasicStroke */ public static final int STROKE_THIN = 0; /* BasicStroke */ /* Transform */ public static final int TRANSFORM_GENERIC = 3; /* any 3x2 */ public static final int TRANSFORM_TRANSLATESCALE = 2; /* scale XY */ public static final int TRANSFORM_TRANSLATEONLY = 1; 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 */ public int rgb; public int pixel; 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 fillComp; /* Considering Paint Transparency */ 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; public boolean complexTransform; protected static final Stroke defaultStroke = new BasicStroke(); protected static final Composite defaultComposite = AlphaComposite.SrcOver; public Paint paint; public Stroke stroke; public Composite composite; protected Font font; protected FontMetrics fontMetrics; public int renderHint; public int antialiasHint; public int textAntialiasHint; private int fractionalMetricsHint; public int interpolationHint; public int strokeHint; public RenderingHints hints; public Region constrainClip; // lightweight bounds public int constrainX; public int constrainY; public Region clipRegion; public Shape usrClip; protected Region devClip; // Actual physical drawable // cached state for text rendering // REMIND: think about when to invalidate these public FontInfo fontInfo; public FontInfo lastGlyphVectorFontInfo; private final static int slowTextTransformMask = AffineTransform.TYPE_GENERAL_TRANSFORM | AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_FLIP; public SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f) { foregroundColor = fg; backgroundColor = bg; int rgb; this.rgb = rgb = fg.getRGB(); pixel = sd.pixelFor(rgb); transform = new AffineTransform(); stroke = defaultStroke; composite = defaultComposite; paint = foregroundColor; fillComp = CompositeType.SrcNoEa; imageComp = CompositeType.SrcOverNoEa; renderHint = SunHints.INTVAL_RENDER_DEFAULT; antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF; textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT; fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF; interpolationHint = -1; strokeHint = SunHints.INTVAL_STROKE_DEFAULT; font = f; if (font == null) { font = new Font("Dialog", 10, Font.PLAIN); } fontInfo = checkFontInfo(null, font); surfaceData = sd; loops = sd.getRenderLoops(this); setDevClip(sd.getBounds()); invalidatePipe(); } protected Object clone() { try { SunGraphics2D g = (SunGraphics2D) super.clone(); g.transform = new AffineTransform(this.transform); if (hints != null) { g.hints = (RenderingHints) this.hints.clone(); } //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. * * NOTE: This method requires that the Graphics * to be constrained doesn't have a complex transform. * * @exception IllegalStateException If the Graphics * to be constrained has a complex transform. */ public void constrain(int x, int y, int w, int h) { if (transformState > TRANSFORM_TRANSLATEONLY) { clipRect(0, 0, w, h); return; } if ((x|y) != 0) { translate(x, y); } x = constrainX = transX; y = constrainY = transY; w = Region.dimAdd(x, w); h = Region.dimAdd(y, h); Region c = constrainClip; if (c == null) { c = Region.getInstanceXYXY(x, y, w, h); } else { c = c.getIntersectionXYXY(x, y, w, h); if (c == constrainClip) { // Common case to ignore return; } } constrainClip = c; if (!devClip.isInsideQuickCheck(c)) { devClip = devClip.getIntersection(c); validateCompClip(); } } protected static ValidatePipe invalidpipe = new ValidatePipe(); /* * Invalidate the pipeline */ protected void invalidatePipe() { drawpipe = invalidpipe; fillpipe = invalidpipe; shapepipe = invalidpipe; textpipe = invalidpipe; imagepipe = invalidpipe; } public void validatePipe() { surfaceData.validatePipe(this); } /* * Intersect two Shapes by the simplest method, attempting to produce * a simplified result. * The boolean arguments keep1 and keep2 specify whether or not * the first or second shapes can be modified during the operation * or whether that shape must be "kept" unmodified. */ Shape intersectShapes(Shape s1, Shape s2, boolean keep1, boolean keep2) { if (s1 instanceof Rectangle && s2 instanceof Rectangle) { return ((Rectangle) s1).intersection((Rectangle) s2); } if (s1 instanceof Rectangle2D) { return intersectRectShape((Rectangle2D) s1, s2, keep1, keep2); } else if (s2 instanceof Rectangle2D) { return intersectRectShape((Rectangle2D) s2, s1, keep2, keep1); } return intersectByArea(s1, s2, keep1, keep2); } /* * Intersect a Rectangle with a Shape by the simplest method, * attempting to produce a simplified result. * The boolean arguments keep1 and keep2 specify whether or not * the first or second shapes can be modified during the operation * or whether that shape must be "kept" unmodified. */ Shape intersectRectShape(Rectangle2D r, Shape s, boolean keep1, boolean keep2) { if (s instanceof Rectangle2D) { Rectangle2D r2 = (Rectangle2D) s; Rectangle2D outrect; if (!keep1) { outrect = r; } else if (!keep2) { outrect = r2; } else { outrect = new Rectangle2D.Float(); } double x1 = Math.max(r.getX(), r2.getX()); double x2 = Math.min(r.getX() + r.getWidth(), r2.getX() + r2.getWidth()); double y1 = Math.max(r.getY(), r2.getY()); double y2 = Math.min(r.getY() + r.getHeight(), r2.getY() + r2.getHeight()); if (((x2 - x1) < 0) || ((y2 - y1) < 0)) // Width or height is negative. No intersection. outrect.setFrameFromDiagonal(0, 0, 0, 0); else outrect.setFrameFromDiagonal(x1, y1, x2, y2); return outrect; } if (r.contains(s.getBounds2D())) { if (keep2) { s = cloneShape(s); } return s; } return intersectByArea(r, s, keep1, keep2); } protected static Shape cloneShape(Shape s) { return new GeneralPath(s); } /* * Intersect two Shapes using the Area class. Presumably other * attempts at simpler intersection methods proved fruitless. * The boolean arguments keep1 and keep2 specify whether or not * the first or second shapes can be modified during the operation * or whether that shape must be "kept" unmodified. * @see #intersectShapes * @see #intersectRectShape */ Shape intersectByArea(Shape s1, Shape s2, boolean keep1, boolean keep2) { Area a1, a2; // First see if we can find an overwriteable source shape // to use as our destination area to avoid duplication. if (!keep1 && (s1 instanceof Area)) { a1 = (Area) s1; } else if (!keep2 && (s2 instanceof Area)) { a1 = (Area) s2; s2 = s1; } else { a1 = new Area(s1); } if (s2 instanceof Area) { a2 = (Area) s2; } else { a2 = new Area(s2); } a1.intersect(a2); if (a1.isRectangular()) { return a1.getBounds(); } return a1; } /* * Intersect usrClip bounds and device bounds to determine the composite * rendering boundaries */ public Region getCompClip() { if (!surfaceData.isValid()) { revalidateAll(); } return clipRegion; } /* * Convert a BufferedImage into another BufferedImage with a desired * ColorModel. */ protected BufferedImage convertCM(BufferedImage src, ColorModel cm) { WritableRaster wr = cm.createCompatibleWritableRaster(src.getWidth(), src.getHeight()); BufferedImage dst = new BufferedImage(cm, wr, src.isAlphaPremultiplied(), null); for (int i = 0 ; i < src.getHeight() ; i++) { for (int j = 0 ; j < src.getWidth() ; j++) { dst.setRGB(j, i, src.getRGB(j, i)); } } return dst; } /* * Convert a given Raster to the desired data format. */ public WritableRaster convertRaster(Raster inRaster, ColorModel inCM, ColorModel outCM) { // Use a faster conversion if this is an IndexColorModel if (inCM instanceof IndexColorModel && ((outCM.equals(ColorModel.getRGBdefault())) || (outCM.equals(XRGBModel)))) { IndexColorModel icm = (IndexColorModel) inCM; BufferedImage dbi = icm.convertToIntDiscrete(inRaster, false); return dbi.getRaster(); } BufferedImage dbi = new BufferedImage(outCM, outCM.createCompatibleWritableRaster(inRaster.getWidth(), inRaster.getHeight()), outCM.isAlphaPremultiplied(), null); // ColorSpace[] cs = {inCM.getColorSpace(), outCM.getColorSpace()}; // ColorConvertOp cOp = new ColorConvertOp(cs); // cOp.filter(sbi, dbi); // use this slow method to convert untill ColorConvertOp is available. // Does not take in to account quality dithering if applicable. Object buffer = inRaster.getDataElements(0, 0, null); for (int i = 0 ; i < dbi.getHeight() ; i++) { for (int j = 0 ; j < dbi.getWidth() ; j++) { dbi.setRGB(j, i, inCM.getRGB(inRaster.getDataElements(j,i,buffer))); } } return dbi.getRaster(); } public Font getFont() { if (font == null) { font = new Font("Dialog", 10, Font.PLAIN); } return font; } private static double[] oldTx; private static final double[] IDENT_TX = {1, 0, 0, 1}; // cached state for various draw[String,Char,Byte] optimizations public FontInfo checkFontInfo(FontInfo oldinfo, Font font) { if (oldinfo == null || oldinfo.font != font) { FontInfo newinfo = new FontInfo(); newinfo.font = font; float ptSize = font.getSize2D(); int txFontType; boolean glyphMightRotate; if (font.isTransformed()) { glyphMightRotate = true; AffineTransform textTx = font.getTransform(); textTx.scale(ptSize, ptSize); txFontType = textTx.getType(); newinfo.originX = (float)textTx.getTranslateX(); newinfo.originY = (float)textTx.getTranslateY(); if (transformState > TRANSFORM_TRANSLATEONLY) { textTx.preConcatenate(transform); transform.getMatrix(newinfo.devTx = new double[4]); } else { newinfo.devTx = IDENT_TX; } textTx.getMatrix(newinfo.glyphTx = new double[4]); } else { txFontType = AffineTransform.TYPE_IDENTITY; if (transformState > TRANSFORM_TRANSLATEONLY) { glyphMightRotate = true; transform.getMatrix(newinfo.devTx = new double[4]); newinfo.glyphTx = new double[4]; for (int i = 0; i < 4; i++) { newinfo.glyphTx[i] = newinfo.devTx[i] * ptSize; } newinfo.pixelHeight = (int)(Math.abs(transform.getScaleY() * ptSize)); } else { glyphMightRotate = false; synchronized (SunGraphics2D.class) { if (oldTx == null || oldTx[0] != ptSize) { oldTx = new double[] {ptSize, 0, 0, ptSize}; } newinfo.glyphTx = oldTx; } newinfo.pixelHeight = (int)ptSize; newinfo.devTx = IDENT_TX; } } if (glyphMightRotate && isRotated(newinfo.glyphTx)) { newinfo.rotate = NativeFontWrapper.fontCanRotate(font); } else { newinfo.rotate = NativeFontWrapper.ROTATE_TRUE; } int aahint = textAntialiasHint; if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT) { aahint = antialiasHint; } newinfo.doAntiAlias = (aahint == SunHints.INTVAL_ANTIALIAS_ON); newinfo.doFractEnable = (fractionalMetricsHint == SunHints.INTVAL_FRACTIONALMETRICS_ON); //useFastTextClip = false; if (false && 0 == (txFontType & slowTextTransformMask)) { FontMetrics fm = getFontMetrics(font); int height = fm.getHeight(); Region r = getCompClip(); // This was the fix for bug 4210705 in BufferedImageGraphics2D: // drawString fails on a buffer image when y < 17 // if (rect != null) { // fastClipMinY = rect.y - height; // fastClipMaxY = rect.y + rect.height + height; // } else { // fastClipMinY = (devClip.y - yOrigin) - height; // fastClipMaxY = (devClip.y - yOrigin) + // devClip.height + height; // } // useFastTextClip = true; } oldinfo = newinfo; } return oldinfo; } 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) { if (font != null && font != this.font) { this.font = font; this.fontMetrics = null; this.fontInfo = checkFontInfo(this.fontInfo, font); } } public static FontDesignMetrics metricsCache[] = new FontDesignMetrics[5]; public synchronized static FontMetrics makeFontMetrics(Font font, FontRenderContext frc) { FontDesignMetrics fdm = null; int i; for (i = 0; i < metricsCache.length; ++i) { FontDesignMetrics tfdm = metricsCache[i]; if (tfdm == null) { break; } if (tfdm.getFont().equals(font) && tfdm.getFRC().equals(frc)) { fdm = tfdm; break; } } if (fdm == null) { fdm = new FontDesignMetrics(font, frc); if (i == metricsCache.length) { --i; } } if (i > 0) { System.arraycopy(metricsCache, 0, metricsCache, 1, i); } metricsCache[0] = fdm; return fdm; } public FontMetrics getFontMetrics(Font font) { if ((this.fontMetrics != null) && (font == this.font)) { return this.fontMetrics; } FontMetrics fm; if (NativeFontWrapper.usePlatformFontMetrics()) { fm = java.awt.Toolkit.getDefaultToolkit().getFontMetrics(font); } else { fm = makeFontMetrics(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); imageComp = newCompType; if (newCompType == CompositeType.SrcOverNoEa && (paintState == PAINT_SOLIDCOLOR || (paintState == PAINT_TILE && paint.getTransparency() == Transparency.OPAQUE))) { newCompType = CompositeType.SrcNoEa; newCompState = COMP_ISCOPY; } else { // REMIND: Could handle Src this way too by munging the pixel if (newCompType == CompositeType.SrcNoEa) { newCompState = COMP_ISCOPY; } else { newCompState = COMP_ALPHA; } } } else if (comp instanceof XORComposite) { newCompState = COMP_XOR; newCompType = CompositeType.Xor; imageComp = newCompType; } else if (comp == null) { throw new IllegalArgumentException("null Composite"); } else { surfaceData.checkCustomComposite(); newCompState = COMP_CUSTOM; newCompType = CompositeType.General; imageComp = newCompType; } if (compositeState != newCompState || fillComp != newCompType) { compositeState = newCompState; fillComp = newCompType; alphafill = null; invalidatePipe(); } composite = comp; } /** * 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; boolean invalid = false; if (imageComp == CompositeType.SrcOverNoEa) { // special case where fillComp depends on opacity of paint if (paint.getTransparency() == Transparency.OPAQUE) { if (compositeState != COMP_ISCOPY) { compositeState = COMP_ISCOPY; fillComp = CompositeType.SrcNoEa; alphafill = null; invalid = true; } } else { if (compositeState == COMP_ISCOPY) { compositeState = COMP_ALPHA; fillComp = CompositeType.SrcOverNoEa; alphafill = null; invalid = true; } } } if (invalid || paintState != PAINT_TILE) { paintState = PAINT_TILE; invalidatePipe(); } } static final int NON_UNIFORM_SCALE_MASK = (AffineTransform.TYPE_GENERAL_TRANSFORM | AffineTransform.TYPE_GENERAL_SCALE); public static final double MinPenSizeAASquared = (DuctusRenderer.MinPenSizeAA * DuctusRenderer.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_TRANSLATEONLY) { if (aa) { if (bs.getLineWidth() <= DuctusRenderer.MinPenSizeAA && bs.getDashArray() == null) { strokeState = STROKE_THIN; } else { strokeState = STROKE_WIDE; } } else { if (bs == defaultStroke || (bs.getLineWidth() <= 1.0f && bs.getDashArray() == null)) { strokeState = STROKE_THIN; } 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 <= 1.0 && bs.getDashArray() != null) { widthsquared = 2.0; } } if (widthsquared <= (aa ? MinPenSizeAASquared : MinPenSizeSquared)) { strokeState = STROKE_THIN; } 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 = ((SunHints.Value) hintValue).getIndex(); switch (sunKey.getIndex()) { case SunHints.INTKEY_RENDERING: stateChanged = (renderHint != newHint); renderHint = newHint; 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_INTERPOLATION: switch (newHint) { case SunHints.INTVAL_INTERPOLATION_BICUBIC: case SunHints.INTVAL_INTERPOLATION_BILINEAR: newHint = AffineTransformOp.TYPE_BILINEAR; break; default: case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR: newHint = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; break; } stateChanged = (interpolationHint != newHint); interpolationHint = 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; this.fontInfo = checkFontInfo(null, this.font); this.lastGlyphVectorFontInfo = 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_INTERPOLATION: switch (interpolationHint) { case AffineTransformOp.TYPE_NEAREST_NEIGHBOR: return SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; case AffineTransformOp.TYPE_BILINEAR: return SunHints.VALUE_INTERPOLATION_BILINEAR; // case AffineTransformOp.TYPE_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; interpolationHint = -1; 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_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_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)); Object value; switch (interpolationHint) { case AffineTransformOp.TYPE_NEAREST_NEIGHBOR: value = SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; break; case AffineTransformOp.TYPE_BILINEAR: value = SunHints.VALUE_INTERPOLATION_BILINEAR; break; // case AffineTransformOp.TYPE_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 (!complexTransform) { transX += x; transY += y; transformState = ((transX == 0 && transY == 0) ? TRANSFORM_ISIDENT : TRANSFORM_TRANSLATEONLY); //invalidatePipe(); } else { // Once invalidateTransform is ever called, // complexTransform becomes true invalidateTransform(); } } /** * Sets the Transform in the current graphics state. * @param Tx The Transform object to be used in the rendering process. * @see #transform * @see TransformChain * @see AffineTransform */ public void setTransform(AffineTransform Tx) { if ((constrainX|constrainY) == 0) { transform.setTransform(Tx); } else { transform.setToTranslation(constrainX, constrainY); transform.concatenate(Tx); } invalidateTransform(); } protected void invalidateTransform() { complexTransform = true; // Do not shortcut translate(int, int) int type = transform.getType(); int origTransformState = transformState; if (type == AffineTransform.TYPE_IDENTITY) { transformState = TRANSFORM_ISIDENT; complexTransform = false; transX = transY = 0; } else if (type == AffineTransform.TYPE_TRANSLATION) { transformState = TRANSFORM_TRANSLATEONLY; // transX/Y Should not be used in this mode, but just in case... transX = (int) transform.getTranslateX(); transY = (int) transform.getTranslateY(); } 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_TRANSLATEONLY || origTransformState > TRANSFORM_TRANSLATEONLY) { cachedFRC = null; if (transformState != origTransformState) { invalidatePipe(); } } this.fontInfo = checkFontInfo(null, this.font); this.fontMetrics = null; this.lastGlyphVectorFontInfo = null; if (strokeState != STROKE_CUSTOM) { validateBasicStroke((BasicStroke) stroke); } } /** * Returns the current Transform in the Graphics2D state. * @see #transform * @see #setTransform */ public AffineTransform getTransform() { if ((constrainX|constrainY) == 0) { return new AffineTransform(transform); } AffineTransform tx = AffineTransform.getTranslateInstance(-constrainX, -constrainY); tx.concatenate(transform); return tx; } /** * Returns the current Transform ignoring the "constrain" * rectangle. */ public AffineTransform cloneTransform() { return new AffineTransform(transform); } /** * Returns the current Paint in the Graphics2D state. * @see #setPaint * @see java.awt.Graphics#setColor */ public Paint getPaint() { return paint; } /** * Returns the current Composite in the Graphics2D state. * @see #setComposite */ public Composite getComposite() { return composite; } public Color getColor() { return foregroundColor; } public void setColor(Color color) { if (color == null || color == paint) { return; } this.paint = foregroundColor = color; int rgb; this.rgb = rgb = foregroundColor.getRGB(); pixel = surfaceData.pixelFor(rgb); if ((rgb >> 24) == -1) { if (paintState == PAINT_SOLIDCOLOR) { return; } paintState = PAINT_SOLIDCOLOR; if (imageComp == CompositeType.SrcOverNoEa) { // special case where fillComp depends on opacity of paint compositeState = COMP_ISCOPY; fillComp = CompositeType.SrcNoEa; alphafill = null; } } else { if (paintState == PAINT_SINGLECOLOR) { return; } paintState = PAINT_SINGLECOLOR; if (imageComp == CompositeType.SrcOverNoEa) { // special case where fillComp depends on opacity of paint compositeState = COMP_ALPHA; fillComp = CompositeType.SrcOverNoEa; alphafill = null; } } invalidatePipe(); } /** * Sets the background color in this context used for clearing a region. * When Graphics2D is constructed for a component, the backgroung color is * inherited from the component. Setting the background color in the * Graphics2D context only affects the subsequent clearRect() calls and * not the background color of the component. To change the background * of the component, use appropriate methods of the component. * @param color The background color that should be used in * subsequent calls to clearRect(). * @see getBackground * @see Graphics.clearRect() */ public void setBackground(Color color) { backgroundColor = color; } /** * Returns the background color used for clearing a region. * @see setBackground */ public Color getBackground() { return backgroundColor; } /** * Returns the current Stroke in the Graphics2D state. * @see setStroke */ public Stroke getStroke() { return stroke; } public Rectangle getClipBounds() { Rectangle r; if (clipState == CLIP_DEVICE) { r = null; } else if (transformState < TRANSFORM_TRANSLATESCALE) { if (usrClip instanceof Rectangle) { r = new Rectangle((Rectangle) usrClip); } else { r = usrClip.getBounds(); } r.translate(-transX, -transY); } else { r = getClip().getBounds(); } return r; } public Rectangle getClipBounds(Rectangle r) { if (clipState != CLIP_DEVICE) { if (transformState < TRANSFORM_TRANSLATESCALE) { if (usrClip instanceof Rectangle) { r.setBounds((Rectangle) usrClip); } else { r.setBounds(usrClip.getBounds()); } r.translate(-transX, -transY); } else { r.setBounds(getClip().getBounds()); } } else if (r == null) { throw new NullPointerException("null rectangle parameter"); } return r; } public boolean hitClip(int x, int y, int width, int height) { if (width <= 0 || height <= 0) { return false; } if (transformState > TRANSFORM_TRANSLATEONLY) { // Note: Technically the most accurate test would be to // raster scan the parallelogram of the transformed rectangle // and do a span for span hit test against the clip, but for // speed we approximate the test with a bounding box of the // transformed rectangle. The cost of rasterizing the // transformed rectangle is probably high enough that it is // not worth doing so to save the caller from having to call // a rendering method where we will end up discovering the // same answer in about the same amount of time anyway. // This logic breaks down if this hit test is being performed // on the bounds of a group of shapes in which case it might // be beneficial to be a little more accurate to avoid lots // of subsequent rendering calls. In either case, this relaxed // test should not be significantly less accurate than the // optimal test for most transforms and so the conservative // answer should not cause too much extra work. double d[] = { x, y, x+width, y, x, y+height, x+width, y+height }; transform.transform(d, 0, d, 0, 4); x = (int) Math.floor(Math.min(Math.min(d[0], d[2]), Math.min(d[4], d[6]))); y = (int) Math.floor(Math.min(Math.min(d[1], d[3]), Math.min(d[5], d[7]))); width = (int) Math.ceil(Math.max(Math.max(d[0], d[2]), Math.max(d[4], d[6]))); height = (int) Math.ceil(Math.max(Math.max(d[1], d[3]), Math.max(d[5], d[7]))); } else { x += transX; y += transY; width += x; height += y; } if (!getCompClip().intersectsQuickCheckXYXY(x, y, width, height)) { return false; } // REMIND: We could go one step further here and examine the // non-rectangular clip shape more closely if there is one. // Since the clip has already been rasterized, the performance // penalty of doing the scan is probably still within the bounds // of a good tradeoff between speed and quality of the answer. return true; } protected void validateCompClip() { int origClipState = clipState; if (usrClip == null) { clipState = CLIP_DEVICE; clipRegion = devClip; } else { if (usrClip instanceof Rectangle2D) { clipState = CLIP_RECTANGULAR; if (usrClip instanceof Rectangle) { clipRegion = devClip.getIntersection((Rectangle)usrClip); } else { clipRegion = devClip.getIntersection(usrClip.getBounds()); } } else { PathIterator cpi = usrClip.getPathIterator(null); int box[] = new int[4]; ShapeSpanIterator sr = new ShapeSpanIterator(this, false); 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)) { 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 (complexTransform) { return transformShape(transform, s); } else { return transformShape(transX, transY, s); } } public Shape untransformShape(Shape s) { if (s == null) { return null; } if (complexTransform) { try { return transformShape(transform.createInverse(), s); } catch (NoninvertibleTransformException e) { return null; } } else { return transformShape(-transX, -transY, s); } } protected static Shape transformShape(int tx, int ty, Shape s) { if (s == null) { return null; } if (s instanceof Rectangle) { Rectangle r = s.getBounds(); r.translate(tx, ty); return r; } if (s instanceof Rectangle2D) { Rectangle2D rect = (Rectangle2D) s; return new Rectangle2D.Double(rect.getX() + tx, rect.getY() + ty, rect.getWidth(), rect.getHeight()); } if (tx == 0 && ty == 0) { return cloneShape(s); } AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty); return mat.createTransformedShape(s); } protected static Shape transformShape(AffineTransform tx, Shape clip) { if (clip == null) { return null; } if (clip instanceof Rectangle2D && (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0) { Rectangle2D rect = (Rectangle2D) clip; double matrix[] = new double[4]; matrix[0] = rect.getX(); matrix[1] = rect.getY(); matrix[2] = matrix[0] + rect.getWidth(); matrix[3] = matrix[1] + rect.getHeight(); tx.transform(matrix, 0, matrix, 0, 2); rect = new Rectangle2D.Float(); rect.setFrameFromDiagonal(matrix[0], matrix[1], matrix[2], matrix[3]); return rect; } if (tx.isIdentity()) { return cloneShape(clip); } return tx.createTransformedShape(clip); } public void clipRect(int x, int y, int w, int h) { clip(new Rectangle(x, y, w, h)); } public void setClip(int x, int y, int w, int h) { setClip(new Rectangle(x, y, w, h)); } public Shape getClip() { return untransformShape(usrClip); } public void setClip(Shape sh) { usrClip = transformShape(sh); validateCompClip(); } /** * Intersects the current clip with the specified Path and sets the * current clip to the resulting intersection. The clip is transformed * with the current transform in the Graphics2D state before being * intersected with the current clip. This method is used to make the * current clip smaller. To make the clip larger, use any setClip method. * @param p The Path to be intersected with the current clip. */ public void clip(Shape s) { s = transformShape(s); if (usrClip != null) { s = intersectShapes(usrClip, s, true, true); } usrClip = s; validateCompClip(); } public void setPaintMode() { setComposite(AlphaComposite.SrcOver); } public void setXORMode(Color c) { if (c == null) { throw new IllegalArgumentException("null XORColor"); } setComposite(new XORComposite(c, surfaceData)); } Blit lastCAblit; Composite lastCAcomp; public void copyArea(int x, int y, int w, int h, int dx, int dy) { if (w <= 0 || h <= 0) { return; } SurfaceData theData = surfaceData; if (theData.copyArea(this, x, y, w, h, dx, dy)) { return; } if (transformState > TRANSFORM_TRANSLATEONLY) { throw new InternalError("transformed copyArea not implemented yet"); } if (clipState == CLIP_SHAPE) { throw new InternalError("clipped 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...) 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, null, 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, null, x, sy, x+dx, sy+dy, w, partH); } return; } ob.Blit(theData, theData, comp, null, x, y, x+dx, y+dy, w, h); } /* public void XcopyArea(int x, int y, int w, int h, int dx, int dy) { Rectangle rect = new Rectangle(x, y, w, h); rect = transformBounds(rect, transform); Point2D point = new Point2D.Float(dx, dy); Point2D root = new Point2D.Float(0, 0); point = transform.transform(point, point); root = transform.transform(root, root); int fdx = (int)(point.getX()-root.getX()); int fdy = (int)(point.getY()-root.getY()); Rectangle r = getCompBounds().intersection(rect.getBounds()); if (r.isEmpty()) { return; } // Begin Rasterizer for Clip Shape boolean skipClip = true; byte[] clipAlpha = null; if (clipState == CLIP_SHAPE) { int box[] = new int[4]; clipRegion.getBounds(box); Rectangle devR = new Rectangle(box[0], box[1], box[2] - box[0], box[3] - box[1]); if (!devR.isEmpty()) { OutputManager mgr = getOutputManager(); RegionIterator ri = clipRegion.getIterator(); while (ri.nextYRange(box)) { int spany = box[1]; int spanh = box[3] - spany; while (ri.nextXBand(box)) { int spanx = box[0]; int spanw = box[2] - spanx; mgr.copyArea(this, null, spanw, 0, spanx, spany, spanw, spanh, fdx, fdy, null); } } } return; } // End Rasterizer for Clip Shape getOutputManager().copyArea(this, null, r.width, 0, r.x, r.y, r.width, r.height, fdx, fdy, null); } */ public void drawLine(int x1, int y1, int x2, int y2) { try { drawpipe.drawLine(this, x1, y1, x2, y2); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawLine(this, x1, y1, x2, y2); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void drawRoundRect(int x, int y, int w, int h, int arcW, int arcH) { try { drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void fillRoundRect(int x, int y, int w, int h, int arcW, int arcH) { try { fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void drawOval(int x, int y, int w, int h) { try { drawpipe.drawOval(this, x, y, w, h); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawOval(this, x, y, w, h); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void fillOval(int x, int y, int w, int h) { try { fillpipe.fillOval(this, x, y, w, h); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillOval(this, x, y, w, h); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void drawArc(int x, int y, int w, int h, int startAngl, int arcAngl) { try { drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void fillArc(int x, int y, int w, int h, int startAngl, int arcAngl) { try { fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void drawPolyline(int xPoints[], int yPoints[], int nPoints) { try { drawpipe.drawPolyline(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawPolyline(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void drawPolygon(int xPoints[], int yPoints[], int nPoints) { try { drawpipe.drawPolygon(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawPolygon(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void fillPolygon(int xPoints[], int yPoints[], int nPoints) { try { fillpipe.fillPolygon(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillPolygon(this, xPoints, yPoints, nPoints); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void drawRect (int x, int y, int w, int h) { try { drawpipe.drawRect(this, x, y, w, h); } catch (InvalidPipeException e) { revalidateAll(); try { drawpipe.drawRect(this, x, y, w, h); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } public void fillRect (int x, int y, int w, int h) { try { fillpipe.fillRect(this, x, y, w, h); } catch (InvalidPipeException e) { revalidateAll(); try { fillpipe.fillRect(this, x, y, w, h); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } 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; setDevClip(surfaceData.getBounds()); pixel = surfaceData.pixelFor(rgb); if (composite instanceof XORComposite) { Color c = ((XORComposite) composite).getXorColor(); setComposite(new XORComposite(c, surfaceData)); } validatePipe(); } finally { // REMIND: No locking yet in screen SurfaceData objects! // surfaceData.unlock(); } } public void clearRect(int x, int y, int w, int h) { // REMIND: has some "interesting" consequences if threads are // not synchronized Composite c = composite; Paint p = paint; setComposite(AlphaComposite.Src); setColor(getBackground()); validatePipe(); fillRect(x, y, w, h); setPaint(p); setComposite(c); } /** * Strokes the outline of a Path using the settings of the current * graphics state. The rendering attributes applied include the * clip, transform, paint or color, composite and stroke attributes. * @param p The path to be drawn. * @see #setStroke * @see #setPaint * @see java.awt.Graphics#setColor * @see #transform * @see #setTransform * @see #clip * @see #setClip * @see #setComposite */ public void draw(Shape s) { try { shapepipe.draw(this, s); } catch (InvalidPipeException e) { revalidateAll(); try { shapepipe.draw(this, s); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } /** * Fills the interior of a Path using the settings of the current * graphics state. The rendering attributes applied include the * clip, transform, paint or color, and composite. * @see #setPaint * @see java.awt.Graphics#setColor * @see #transform * @see #setTransform * @see #setComposite * @see #clip * @see #setClip */ public void fill(Shape s) { try { shapepipe.fill(this, s); } catch (InvalidPipeException e) { revalidateAll(); try { shapepipe.fill(this, s); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } /** * 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. * @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) { // 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_ISIDENT) || ((transformState == TRANSFORM_TRANSLATEONLY) && (transform.getTranslateX() == (double)transX) && (transform.getTranslateY() == (double)transY))) && isIntegerTranslation(xform); // Include padding for interpolation/antialiasing if necessary int pad = isIntegerTranslate ? 0 : 3; // Determine the region of the image that may contribute to // the clipped drawing area Rectangle region = getImageRegion(img, getCompClip(), transform, xform, pad, pad); if (region.width <= 0 || region.height <= 0) { return; } // Attempt to optimize integer translation of tiled images. // Although theoretically we are O.K. if the concatenation of // the user transform and the device transform is an integer // translation, we'll play it safe and only optimize the case // where both are integer translations. if (isIntegerTranslate) { // Use optimized code // Note that drawTranslatedRenderedImage calls copyImage // which takes the user space to device space transform into // account, but we need to provide the image space to user space // translations. drawTranslatedRenderedImage(img, region, (int) xform.getTranslateX(), (int) xform.getTranslateY()); return; } // General case: cobble the necessary region into a single Raster Raster raster = img.getData(region); // Make a new Raster with the same contents as raster // but starting at (0, 0). This raster is thus in the same // coordinate system as the SampleModel of the original raster. WritableRaster wRaster = Raster.createWritableRaster(raster.getSampleModel(), raster.getDataBuffer(), null); // If the original raster was in a different coordinate // system than its SampleModel, we need to perform an // additional translation in order to get the (minX, minY) // pixel of raster to be pixel (0, 0) of wRaster. We also // have to have the correct width and height. int minX = raster.getMinX(); int minY = raster.getMinY(); int width = raster.getWidth(); int height = raster.getHeight(); int px = minX - raster.getSampleModelTranslateX(); int py = minY - raster.getSampleModelTranslateY(); if (px != 0 || py != 0 || width != wRaster.getWidth() || height != wRaster.getHeight()) { wRaster = wRaster.createWritableChild(px, py, width, height, 0, 0, null); } // Now we have a BufferedImage starting at (0, 0) // with the same contents that started at (minX, minY) // in raster. So we must draw the BufferedImage with a // translation of (minX, minY). AffineTransform transXform = (AffineTransform)xform.clone(); transXform.translate(minX, minY); ColorModel cm = img.getColorModel(); BufferedImage bufImg = new BufferedImage(cm, wRaster, cm.isAlphaPremultiplied(), null); drawImage(bufImg, transXform, null); } /** * Intersects <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) { 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"); } try { try { textpipe.drawString(this, str, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawString(this, str, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } catch (ShapingException e) { TextLayout tl = new TextLayout(str, getFont(), getFontRenderContext()); tl.draw(this, (float)x, (float)y); } } public void drawString(String str, float x, float y) { if (str == null) { throw new NullPointerException("string is null"); } try { try { textpipe.drawString(this, str, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawString(this, str, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } catch (ShapingException e) { TextLayout tl = new TextLayout(str, getFont(), getFontRenderContext()); tl.draw(this, x, y); } } public void drawString(AttributedCharacterIterator iterator, int x, int y) { if (iterator == null) { throw new NullPointerException("attributedcharacteriterator is null"); } 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"); } 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"); } StandardGlyphVector sgv = StandardGlyphVector.getStandardGV(gv); // ensure one that native understands if (sgv.positions == null && !sgv.getFontRenderContext().equals(getFontRenderContext())) { sgv.performDefaultLayout(); } try { textpipe.drawGlyphVector(this, sgv, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawGlyphVector(this, sgv, 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. } } } public void drawChars(char data[], int offset, int length, int x, int y) { if (data == null) { throw new NullPointerException("char data is null"); } try { try { textpipe.drawChars(this, data, offset, length, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawChars(this, data, offset, length, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } catch (ShapingException e) { TextLayout tl = new TextLayout(new String(data, offset, length), getFont(), getFontRenderContext()); tl.draw(this, (float)x, (float)y); } } public void drawBytes(byte data[], int offset, int length, int x, int y) { if (data == null) { throw new NullPointerException("byte data is null"); } try { try { textpipe.drawBytes(this, data, offset, length, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { textpipe.drawBytes(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. } } } catch (ShapingException e) { TextLayout tl = new TextLayout(new String(data, 0, offset, length), getFont(), getFontRenderContext()); tl.draw(this, (float)x, (float)y); } } // end of text rendering methods /** * Draws an image scaled to x,y,w,h in nonblocking mode with a * callback object. */ public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { return drawImage(img, x, y, width, height, null, observer); } /** * Not part of the advertised API but a useful utility method * to call internally. This is for the case where we are * drawing to/from given coordinates using a given width/height, * but we guarantee that the weidth/height of the src and dest * areas are equal (no scale needed). */ public boolean copyImage(Image img, int dx, int dy, int sx, int sy, int width, int height, Color bgcolor, ImageObserver observer) { try { return imagepipe.copyImage(this, img, dx, dy, sx, sy, width, height, bgcolor, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.copyImage(this, img, dx, dy, sx, sy, width, height, bgcolor, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } } /** * 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 ((width == 0) || (height == 0)) { return true; } if ((width == img.getWidth(null)) && (height == img.getHeight(null)) && (transformState < TRANSFORM_TRANSLATESCALE)) { return copyImage(img, x, y, 0, 0, width, height, bg, observer); } try { return imagepipe.scaleImage(this, img, x, y, width, height, bg, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.scaleImage(this, img, x, y, width, height, bg, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } } /** * 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) { try { return imagepipe.copyImage(this, img, x, y, bg, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.copyImage(this, img, x, y, bg, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } } /** * 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 (dx1 == dx2 || dy1 == dy2 || sx1 == sx2 || sy1 == sy2) { return true; } if (transformState < TRANSFORM_TRANSLATESCALE) { if (((sx2 - sx1) == (dx2 - dx1)) && ((sy2 - sy1) == (dy2 - dy1))) { // Not a scale - forward it to a copy routine int srcX, srcY, dstX, dstY, width, height; if (sx2 > sx1) { width = sx2 - sx1; srcX = sx1; dstX = dx1; } else { width = sx1 - sx2; srcX = sx2; dstX = dx2; } if (sy2 > sy1) { height = sy2-sy1; srcY = sy1; dstY = dy1; } else { height = sy1-sy2; srcY = sy2; dstY = dy2; } return copyImage(img, dstX, dstY, srcX, srcY, width, height, bgcolor, observer); } } try { return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } } /** * 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 ((transformState < TRANSFORM_TRANSLATESCALE) && ((xform == null) || (xform.isIdentity()))) { return drawImage(img, 0, 0, null, observer); } try { return imagepipe.transformImage(this, img, xform, observer); } catch (InvalidPipeException e) { revalidateAll(); try { return imagepipe.transformImage(this, img, xform, observer); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. return false; } } } public void drawImage(BufferedImage bImg, BufferedImageOp op, int x, int y) { try { imagepipe.transformImage(this, bImg, op, x, y); } catch (InvalidPipeException e) { revalidateAll(); try { imagepipe.transformImage(this, bImg, op, x, y); } catch (InvalidPipeException e2) { // Still catching the exception; we are not yet ready to // validate the surfaceData correctly. Fail for now and // try again next time around. } } } /** * Get the rendering context of the font * within this Graphics2D context. */ public FontRenderContext getFontRenderContext() { if (cachedFRC == null) { int aahint = (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT ? antialiasHint : textAntialiasHint); cachedFRC = new FontRenderContext ((transformState < TRANSFORM_TRANSLATESCALE) ? null : transform, (aahint == SunHints.INTVAL_ANTIALIAS_ON), (fractionalMetricsHint == SunHints.INTVAL_FRACTIONALMETRICS_ON)); } return cachedFRC; } private FontRenderContext cachedFRC; public void dispose() { surfaceData = NullSurfaceData.theInstance; invalidatePipe(); } public void finalize() { } }