0001: /*
0002: * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package sun.java2d;
0027:
0028: import java.awt.Graphics;
0029: import java.awt.Graphics2D;
0030: import java.awt.RenderingHints;
0031: import java.awt.RenderingHints.Key;
0032: import java.awt.geom.Area;
0033: import java.awt.geom.AffineTransform;
0034: import java.awt.geom.NoninvertibleTransformException;
0035: import java.awt.AlphaComposite;
0036: import java.awt.BasicStroke;
0037: import java.awt.image.BufferedImage;
0038: import java.awt.image.BufferedImageOp;
0039: import java.awt.image.RenderedImage;
0040: import java.awt.image.renderable.RenderableImage;
0041: import java.awt.image.renderable.RenderContext;
0042: import java.awt.image.AffineTransformOp;
0043: import java.awt.image.Raster;
0044: import java.awt.image.SampleModel;
0045: import java.awt.image.VolatileImage;
0046: import java.awt.image.WritableRaster;
0047: import java.awt.Image;
0048: import java.awt.Composite;
0049: import java.awt.Color;
0050: import java.awt.color.ColorSpace;
0051: import java.awt.image.DataBuffer;
0052: import java.awt.image.ColorModel;
0053: import java.awt.image.IndexColorModel;
0054: import java.awt.image.DirectColorModel;
0055: import java.awt.GraphicsConfiguration;
0056: import java.awt.Paint;
0057: import java.awt.GradientPaint;
0058: import java.awt.LinearGradientPaint;
0059: import java.awt.RadialGradientPaint;
0060: import java.awt.TexturePaint;
0061: import java.awt.geom.Point2D;
0062: import java.awt.geom.Rectangle2D;
0063: import java.awt.geom.PathIterator;
0064: import java.awt.geom.GeneralPath;
0065: import java.awt.Shape;
0066: import java.awt.Stroke;
0067: import java.awt.FontMetrics;
0068: import java.awt.Rectangle;
0069: import java.text.AttributedCharacterIterator;
0070: import java.awt.Font;
0071: import java.awt.image.ImageObserver;
0072: import java.awt.image.ColorConvertOp;
0073: import java.awt.Transparency;
0074: import java.awt.font.GlyphVector;
0075: import java.awt.font.TextLayout;
0076: import sun.font.FontDesignMetrics;
0077: import sun.font.StandardGlyphVector;
0078: import sun.java2d.pipe.PixelDrawPipe;
0079: import sun.java2d.pipe.PixelFillPipe;
0080: import sun.java2d.pipe.ShapeDrawPipe;
0081: import sun.java2d.pipe.ValidatePipe;
0082: import sun.java2d.pipe.ShapeSpanIterator;
0083: import sun.java2d.pipe.Region;
0084: import sun.java2d.pipe.RegionIterator;
0085: import sun.java2d.pipe.TextPipe;
0086: import sun.java2d.pipe.DrawImagePipe;
0087: import sun.java2d.loops.FontInfo;
0088: import sun.java2d.loops.RenderLoops;
0089: import sun.java2d.loops.CompositeType;
0090: import sun.java2d.loops.SurfaceType;
0091: import sun.java2d.loops.Blit;
0092: import sun.java2d.loops.BlitBg;
0093: import sun.java2d.loops.MaskFill;
0094: import sun.font.FontManager;
0095: import java.awt.font.FontRenderContext;
0096: import sun.java2d.loops.XORComposite;
0097: import sun.awt.ConstrainableGraphics;
0098: import sun.awt.SunHints;
0099: import java.util.Map;
0100: import java.util.Iterator;
0101: import sun.awt.image.OffScreenImage;
0102: import sun.misc.PerformanceLogger;
0103:
0104: /**
0105: * This is a the master Graphics2D superclass for all of the Sun
0106: * Graphics implementations. This class relies on subclasses to
0107: * manage the various device information, but provides an overall
0108: * general framework for performing all of the requests in the
0109: * Graphics and Graphics2D APIs.
0110: *
0111: * @version 1.211 05/07/98
0112: * @author Jim Graham
0113: */
0114: public final class SunGraphics2D extends Graphics2D implements
0115: ConstrainableGraphics, Cloneable {
0116: /*
0117: * Attribute States
0118: */
0119: /* Paint */
0120: public static final int PAINT_CUSTOM = 6; /* Any other Paint object */
0121: public static final int PAINT_TEXTURE = 5; /* Tiled Image */
0122: public static final int PAINT_RAD_GRADIENT = 4; /* Color RadialGradient */
0123: public static final int PAINT_LIN_GRADIENT = 3; /* Color LinearGradient */
0124: public static final int PAINT_GRADIENT = 2; /* Color Gradient */
0125: public static final int PAINT_ALPHACOLOR = 1; /* Non-opaque Color */
0126: public static final int PAINT_OPAQUECOLOR = 0; /* Opaque Color */
0127:
0128: /* Composite*/
0129: public static final int COMP_CUSTOM = 3;/* Custom Composite */
0130: public static final int COMP_XOR = 2;/* XOR Mode Composite */
0131: public static final int COMP_ALPHA = 1;/* AlphaComposite */
0132: public static final int COMP_ISCOPY = 0;/* simple stores into destination,
0133: * i.e. Src, SrcOverNoEa, and other
0134: * alpha modes which replace
0135: * the destination.
0136: */
0137:
0138: /* Stroke */
0139: public static final int STROKE_CUSTOM = 3; /* custom Stroke */
0140: public static final int STROKE_WIDE = 2; /* BasicStroke */
0141: public static final int STROKE_THINDASHED = 1; /* BasicStroke */
0142: public static final int STROKE_THIN = 0; /* BasicStroke */
0143:
0144: /* Transform */
0145: public static final int TRANSFORM_GENERIC = 4; /* any 3x2 */
0146: public static final int TRANSFORM_TRANSLATESCALE = 3; /* scale XY */
0147: public static final int TRANSFORM_ANY_TRANSLATE = 2; /* non-int translate */
0148: public static final int TRANSFORM_INT_TRANSLATE = 1; /* int translate */
0149: public static final int TRANSFORM_ISIDENT = 0; /* Identity */
0150:
0151: /* Clipping */
0152: public static final int CLIP_SHAPE = 2; /* arbitrary clip */
0153: public static final int CLIP_RECTANGULAR = 1; /* rectangular clip */
0154: public static final int CLIP_DEVICE = 0; /* no clipping set */
0155:
0156: /* The following fields are used when the current Paint is a Color. */
0157: public int eargb; // ARGB value with ExtraAlpha baked in
0158: public int pixel; // pixel value for eargb
0159:
0160: public SurfaceData surfaceData;
0161:
0162: public PixelDrawPipe drawpipe;
0163: public PixelFillPipe fillpipe;
0164: public DrawImagePipe imagepipe;
0165: public ShapeDrawPipe shapepipe;
0166: public TextPipe textpipe;
0167: public MaskFill alphafill;
0168:
0169: public RenderLoops loops;
0170:
0171: public CompositeType imageComp; /* Image Transparency checked on fly */
0172:
0173: public int paintState;
0174: public int compositeState;
0175: public int strokeState;
0176: public int transformState;
0177: public int clipState;
0178:
0179: public Color foregroundColor;
0180: public Color backgroundColor;
0181:
0182: public AffineTransform transform;
0183: public int transX;
0184: public int transY;
0185:
0186: protected static final Stroke defaultStroke = new BasicStroke();
0187: protected static final Composite defaultComposite = AlphaComposite.SrcOver;
0188: private static final Font defaultFont = new Font(Font.DIALOG,
0189: Font.PLAIN, 12);
0190:
0191: public Paint paint;
0192: public Stroke stroke;
0193: public Composite composite;
0194: protected Font font;
0195: protected FontMetrics fontMetrics;
0196:
0197: public int renderHint;
0198: public int antialiasHint;
0199: public int textAntialiasHint;
0200: private int fractionalMetricsHint;
0201:
0202: /* A gamma adjustment to the colour used in lcd text blitting */
0203: public int lcdTextContrast;
0204: private static int lcdTextContrastDefaultValue = 140;
0205:
0206: private int interpolationHint; // raw value of rendering Hint
0207: public int strokeHint;
0208:
0209: public int interpolationType; // algorithm choice based on
0210: // interpolation and render Hints
0211:
0212: public RenderingHints hints;
0213:
0214: public Region constrainClip; // lightweight bounds
0215: public int constrainX;
0216: public int constrainY;
0217:
0218: public Region clipRegion;
0219: public Shape usrClip;
0220: protected Region devClip; // Actual physical drawable
0221:
0222: // cached state for text rendering
0223: private boolean validFontInfo;
0224: private FontInfo fontInfo;
0225: private FontInfo glyphVectorFontInfo;
0226: private FontRenderContext glyphVectorFRC;
0227:
0228: private final static int slowTextTransformMask = AffineTransform.TYPE_GENERAL_TRANSFORM
0229: | AffineTransform.TYPE_MASK_ROTATION
0230: | AffineTransform.TYPE_FLIP;
0231:
0232: static {
0233: if (PerformanceLogger.loggingEnabled()) {
0234: PerformanceLogger
0235: .setTime("SunGraphics2D static initialization");
0236: }
0237: }
0238:
0239: public SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f) {
0240: surfaceData = sd;
0241: foregroundColor = fg;
0242: backgroundColor = bg;
0243:
0244: transform = new AffineTransform();
0245: stroke = defaultStroke;
0246: composite = defaultComposite;
0247: paint = foregroundColor;
0248:
0249: imageComp = CompositeType.SrcOverNoEa;
0250:
0251: renderHint = SunHints.INTVAL_RENDER_DEFAULT;
0252: antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
0253: textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
0254: fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
0255: lcdTextContrast = lcdTextContrastDefaultValue;
0256: interpolationHint = -1;
0257: strokeHint = SunHints.INTVAL_STROKE_DEFAULT;
0258:
0259: interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
0260:
0261: validateColor();
0262:
0263: font = f;
0264: if (font == null) {
0265: font = defaultFont;
0266: }
0267:
0268: loops = sd.getRenderLoops(this );
0269: setDevClip(sd.getBounds());
0270: invalidatePipe();
0271: }
0272:
0273: protected Object clone() {
0274: try {
0275: SunGraphics2D g = (SunGraphics2D) super .clone();
0276: g.transform = new AffineTransform(this .transform);
0277: if (hints != null) {
0278: g.hints = (RenderingHints) this .hints.clone();
0279: }
0280: /* FontInfos are re-used, so must be cloned too, if they
0281: * are valid, and be nulled out if invalid.
0282: * The implied trade-off is that there is more to be gained
0283: * from re-using these objects than is lost by having to
0284: * clone them when the SG2D is cloned.
0285: */
0286: if (this .fontInfo != null) {
0287: if (this .validFontInfo) {
0288: g.fontInfo = (FontInfo) this .fontInfo.clone();
0289: } else {
0290: g.fontInfo = null;
0291: }
0292: }
0293: if (this .glyphVectorFontInfo != null) {
0294: g.glyphVectorFontInfo = (FontInfo) this .glyphVectorFontInfo
0295: .clone();
0296: g.glyphVectorFRC = this .glyphVectorFRC;
0297: }
0298: //g.invalidatePipe();
0299: return g;
0300: } catch (CloneNotSupportedException e) {
0301: }
0302: return null;
0303: }
0304:
0305: /**
0306: * Create a new SunGraphics2D based on this one.
0307: */
0308: public Graphics create() {
0309: return (Graphics) clone();
0310: }
0311:
0312: public void setDevClip(int x, int y, int w, int h) {
0313: Region c = constrainClip;
0314: if (c == null) {
0315: devClip = Region.getInstanceXYWH(x, y, w, h);
0316: } else {
0317: devClip = c.getIntersectionXYWH(x, y, w, h);
0318: }
0319: validateCompClip();
0320: }
0321:
0322: public void setDevClip(Rectangle r) {
0323: setDevClip(r.x, r.y, r.width, r.height);
0324: }
0325:
0326: /**
0327: * Constrain rendering for lightweight objects.
0328: *
0329: * REMIND: This method will back off to the "workaround"
0330: * of using translate and clipRect if the Graphics
0331: * to be constrained has a complex transform. The
0332: * drawback of the workaround is that the resulting
0333: * clip and device origin cannot be "enforced".
0334: *
0335: * @exception IllegalStateException If the Graphics
0336: * to be constrained has a complex transform.
0337: */
0338: public void constrain(int x, int y, int w, int h) {
0339: if ((x | y) != 0) {
0340: translate(x, y);
0341: }
0342: if (transformState >= TRANSFORM_TRANSLATESCALE) {
0343: clipRect(0, 0, w, h);
0344: return;
0345: }
0346: x = constrainX = transX;
0347: y = constrainY = transY;
0348: w = Region.dimAdd(x, w);
0349: h = Region.dimAdd(y, h);
0350: Region c = constrainClip;
0351: if (c == null) {
0352: c = Region.getInstanceXYXY(x, y, w, h);
0353: } else {
0354: c = c.getIntersectionXYXY(x, y, w, h);
0355: if (c == constrainClip) {
0356: // Common case to ignore
0357: return;
0358: }
0359: }
0360: constrainClip = c;
0361: if (!devClip.isInsideQuickCheck(c)) {
0362: devClip = devClip.getIntersection(c);
0363: validateCompClip();
0364: }
0365: }
0366:
0367: protected static ValidatePipe invalidpipe = new ValidatePipe();
0368:
0369: /*
0370: * Invalidate the pipeline
0371: */
0372: protected void invalidatePipe() {
0373: drawpipe = invalidpipe;
0374: fillpipe = invalidpipe;
0375: shapepipe = invalidpipe;
0376: textpipe = invalidpipe;
0377: imagepipe = invalidpipe;
0378: }
0379:
0380: public void validatePipe() {
0381: surfaceData.validatePipe(this );
0382: }
0383:
0384: /*
0385: * Intersect two Shapes by the simplest method, attempting to produce
0386: * a simplified result.
0387: * The boolean arguments keep1 and keep2 specify whether or not
0388: * the first or second shapes can be modified during the operation
0389: * or whether that shape must be "kept" unmodified.
0390: */
0391: Shape intersectShapes(Shape s1, Shape s2, boolean keep1,
0392: boolean keep2) {
0393: if (s1 instanceof Rectangle && s2 instanceof Rectangle) {
0394: return ((Rectangle) s1).intersection((Rectangle) s2);
0395: }
0396: if (s1 instanceof Rectangle2D) {
0397: return intersectRectShape((Rectangle2D) s1, s2, keep1,
0398: keep2);
0399: } else if (s2 instanceof Rectangle2D) {
0400: return intersectRectShape((Rectangle2D) s2, s1, keep2,
0401: keep1);
0402: }
0403: return intersectByArea(s1, s2, keep1, keep2);
0404: }
0405:
0406: /*
0407: * Intersect a Rectangle with a Shape by the simplest method,
0408: * attempting to produce a simplified result.
0409: * The boolean arguments keep1 and keep2 specify whether or not
0410: * the first or second shapes can be modified during the operation
0411: * or whether that shape must be "kept" unmodified.
0412: */
0413: Shape intersectRectShape(Rectangle2D r, Shape s, boolean keep1,
0414: boolean keep2) {
0415: if (s instanceof Rectangle2D) {
0416: Rectangle2D r2 = (Rectangle2D) s;
0417: Rectangle2D outrect;
0418: if (!keep1) {
0419: outrect = r;
0420: } else if (!keep2) {
0421: outrect = r2;
0422: } else {
0423: outrect = new Rectangle2D.Float();
0424: }
0425: double x1 = Math.max(r.getX(), r2.getX());
0426: double x2 = Math.min(r.getX() + r.getWidth(), r2.getX()
0427: + r2.getWidth());
0428: double y1 = Math.max(r.getY(), r2.getY());
0429: double y2 = Math.min(r.getY() + r.getHeight(), r2.getY()
0430: + r2.getHeight());
0431:
0432: if (((x2 - x1) < 0) || ((y2 - y1) < 0))
0433: // Width or height is negative. No intersection.
0434: outrect.setFrameFromDiagonal(0, 0, 0, 0);
0435: else
0436: outrect.setFrameFromDiagonal(x1, y1, x2, y2);
0437: return outrect;
0438: }
0439: if (r.contains(s.getBounds2D())) {
0440: if (keep2) {
0441: s = cloneShape(s);
0442: }
0443: return s;
0444: }
0445: return intersectByArea(r, s, keep1, keep2);
0446: }
0447:
0448: protected static Shape cloneShape(Shape s) {
0449: return new GeneralPath(s);
0450: }
0451:
0452: /*
0453: * Intersect two Shapes using the Area class. Presumably other
0454: * attempts at simpler intersection methods proved fruitless.
0455: * The boolean arguments keep1 and keep2 specify whether or not
0456: * the first or second shapes can be modified during the operation
0457: * or whether that shape must be "kept" unmodified.
0458: * @see #intersectShapes
0459: * @see #intersectRectShape
0460: */
0461: Shape intersectByArea(Shape s1, Shape s2, boolean keep1,
0462: boolean keep2) {
0463: Area a1, a2;
0464:
0465: // First see if we can find an overwriteable source shape
0466: // to use as our destination area to avoid duplication.
0467: if (!keep1 && (s1 instanceof Area)) {
0468: a1 = (Area) s1;
0469: } else if (!keep2 && (s2 instanceof Area)) {
0470: a1 = (Area) s2;
0471: s2 = s1;
0472: } else {
0473: a1 = new Area(s1);
0474: }
0475:
0476: if (s2 instanceof Area) {
0477: a2 = (Area) s2;
0478: } else {
0479: a2 = new Area(s2);
0480: }
0481:
0482: a1.intersect(a2);
0483: if (a1.isRectangular()) {
0484: return a1.getBounds();
0485: }
0486:
0487: return a1;
0488: }
0489:
0490: /*
0491: * Intersect usrClip bounds and device bounds to determine the composite
0492: * rendering boundaries.
0493: */
0494: public Region getCompClip() {
0495: if (!surfaceData.isValid()) {
0496: // revalidateAll() implicitly recalculcates the composite clip
0497: revalidateAll();
0498: }
0499:
0500: return clipRegion;
0501: }
0502:
0503: public Font getFont() {
0504: if (font == null) {
0505: font = defaultFont;
0506: }
0507: return font;
0508: }
0509:
0510: private static final double[] IDENT_MATRIX = { 1, 0, 0, 1 };
0511: private static final AffineTransform IDENT_ATX = new AffineTransform();
0512:
0513: private static final int MINALLOCATED = 8;
0514: private static final int TEXTARRSIZE = 17;
0515: private static double[][] textTxArr = new double[TEXTARRSIZE][];
0516: private static AffineTransform[] textAtArr = new AffineTransform[TEXTARRSIZE];
0517:
0518: static {
0519: for (int i = MINALLOCATED; i < TEXTARRSIZE; i++) {
0520: textTxArr[i] = new double[] { i, 0, 0, i };
0521: textAtArr[i] = new AffineTransform(textTxArr[i]);
0522: }
0523: }
0524:
0525: // cached state for various draw[String,Char,Byte] optimizations
0526: public FontInfo checkFontInfo(FontInfo info, Font font,
0527: FontRenderContext frc) {
0528: /* Do not create a FontInfo object as part of construction of an
0529: * SG2D as its possible it may never be needed - ie if no text
0530: * is drawn using this SG2D.
0531: */
0532: if (info == null) {
0533: info = new FontInfo();
0534: }
0535:
0536: float ptSize = font.getSize2D();
0537: int txFontType;
0538: AffineTransform devAt, textAt = null;
0539: if (font.isTransformed()) {
0540: textAt = font.getTransform();
0541: textAt.scale(ptSize, ptSize);
0542: txFontType = textAt.getType();
0543: info.originX = (float) textAt.getTranslateX();
0544: info.originY = (float) textAt.getTranslateY();
0545: textAt.translate(-info.originX, -info.originY);
0546: if (transformState >= TRANSFORM_TRANSLATESCALE) {
0547: transform.getMatrix(info.devTx = new double[4]);
0548: devAt = new AffineTransform(info.devTx);
0549: textAt.preConcatenate(devAt);
0550: } else {
0551: info.devTx = IDENT_MATRIX;
0552: devAt = IDENT_ATX;
0553: }
0554: textAt.getMatrix(info.glyphTx = new double[4]);
0555: double shearx = textAt.getShearX();
0556: double scaley = textAt.getScaleY();
0557: if (shearx != 0) {
0558: scaley = Math.sqrt(shearx * shearx + scaley * scaley);
0559: }
0560: info.pixelHeight = (int) (Math.abs(scaley) + 0.5);
0561: } else {
0562: txFontType = AffineTransform.TYPE_IDENTITY;
0563: info.originX = info.originY = 0;
0564: if (transformState >= TRANSFORM_TRANSLATESCALE) {
0565: transform.getMatrix(info.devTx = new double[4]);
0566: devAt = new AffineTransform(info.devTx);
0567: info.glyphTx = new double[4];
0568: for (int i = 0; i < 4; i++) {
0569: info.glyphTx[i] = info.devTx[i] * ptSize;
0570: }
0571: textAt = new AffineTransform(info.glyphTx);
0572: double shearx = transform.getShearX();
0573: double scaley = transform.getScaleY();
0574: if (shearx != 0) {
0575: scaley = Math.sqrt(shearx * shearx + scaley
0576: * scaley);
0577: }
0578: info.pixelHeight = (int) (Math.abs(scaley * ptSize) + 0.5);
0579: } else {
0580: /* If the double represents a common integral, we
0581: * may have pre-allocated objects.
0582: * A "sparse" array be seems to be as fast as a switch
0583: * even for 3 or 4 pt sizes, and is more flexible.
0584: * This should perform comparably in single-threaded
0585: * rendering to the old code which synchronized on the
0586: * class and scale better on MP systems.
0587: */
0588: int pszInt = (int) ptSize;
0589: if (ptSize == pszInt && pszInt >= MINALLOCATED
0590: && pszInt < TEXTARRSIZE) {
0591: info.glyphTx = textTxArr[pszInt];
0592: textAt = textAtArr[pszInt];
0593: info.pixelHeight = pszInt;
0594: } else {
0595: info.pixelHeight = (int) (ptSize + 0.5);
0596: }
0597: if (textAt == null) {
0598: info.glyphTx = new double[] { ptSize, 0, 0, ptSize };
0599: textAt = new AffineTransform(info.glyphTx);
0600: }
0601:
0602: info.devTx = IDENT_MATRIX;
0603: devAt = IDENT_ATX;
0604: }
0605: }
0606:
0607: info.font2D = FontManager.getFont2D(font);
0608:
0609: int fmhint = fractionalMetricsHint;
0610: if (fmhint == SunHints.INTVAL_FRACTIONALMETRICS_DEFAULT) {
0611: fmhint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
0612: }
0613: info.lcdSubPixPos = false; // conditionally set true in LCD mode.
0614:
0615: /* The text anti-aliasing hints that are set by the client need
0616: * to be interpreted for the current state and stored in the
0617: * FontInfo.aahint which is what will actually be used and
0618: * will be one of OFF, ON, LCD_HRGB or LCD_VRGB.
0619: * This is what pipe selection code should typically refer to, not
0620: * textAntialiasHint. This means we are now evaluating the meaning
0621: * of "default" here. Any pipe that really cares about that will
0622: * also need to consult that variable.
0623: * Otherwise these are being used only as args to getStrike,
0624: * and are encapsulated in that object which is part of the
0625: * FontInfo, so we do not need to store them directly as fields
0626: * in the FontInfo object.
0627: * That could change if FontInfo's were more selectively
0628: * revalidated when graphics state changed. Presently this
0629: * method re-evaluates all fields in the fontInfo.
0630: * The strike doesn't need to know the RGB subpixel order. Just
0631: * if its H or V orientation, so if an LCD option is specified we
0632: * always pass in the RGB hint to the strike.
0633: * frc is non-null only if this is a GlyphVector. For reasons
0634: * which are probably a historical mistake the AA hint in a GV
0635: * is honoured when we render, overriding the Graphics setting.
0636: */
0637: int aahint;
0638: if (frc == null) {
0639: aahint = textAntialiasHint;
0640: } else {
0641: aahint = ((SunHints.Value) frc.getAntiAliasingHint())
0642: .getIndex();
0643: }
0644: if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT) {
0645: if (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
0646: aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
0647: } else {
0648: aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
0649: }
0650: } else {
0651: /* If we are in checkFontInfo because a rendering hint has been
0652: * set then all pipes are revalidated. But we can also
0653: * be here because setFont() has been called when the 'gasp'
0654: * hint is set, as then the font size determines the text pipe.
0655: * See comments in SunGraphics2d.setFont(Font).
0656: */
0657: if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP) {
0658: if (info.font2D.useAAForPtSize(info.pixelHeight)) {
0659: aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
0660: } else {
0661: aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
0662: }
0663: } else if (aahint >= SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB) {
0664: /* loops for default rendering modes are installed in the SG2D
0665: * constructor. If there are none this will be null.
0666: * Not all compositing modes update the render loops, so
0667: * we also test that this is a mode we know should support
0668: * this. One minor issue is that the loops aren't necessarily
0669: * installed for a new rendering mode until after this
0670: * method is called during pipeline validation. So it is
0671: * theoretically possible that it was set to null for a
0672: * compositing mode, the composite is then set back to Src,
0673: * but the loop is still null when this is called and AA=ON
0674: * is installed instead of an LCD mode.
0675: * However this is done in the right order in SurfaceData.java
0676: * so this is not likely to be a problem - but not
0677: * guaranteed.
0678: */
0679: if (!surfaceData.canRenderLCDText(this )
0680: // loops.drawGlyphListLCDLoop == null ||
0681: // compositeState > COMP_ISCOPY ||
0682: // paintState > PAINT_ALPHACOLOR
0683: ) {
0684: aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
0685: } else {
0686: info.lcdRGBOrder = true;
0687: /* Collapse these into just HRGB or VRGB.
0688: * Pipe selection code needs only to test for these two.
0689: * Since these both select the same pipe anyway its
0690: * tempting to collapse into one value. But they are
0691: * different strikes (glyph caches) so the distinction
0692: * needs to be made for that purpose.
0693: */
0694: if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HBGR) {
0695: aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
0696: info.lcdRGBOrder = false;
0697: } else if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VBGR) {
0698: aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB;
0699: info.lcdRGBOrder = false;
0700: }
0701: /* Support subpixel positioning only for the case in
0702: * which the horizontal resolution is increased
0703: */
0704: info.lcdSubPixPos = fmhint == SunHints.INTVAL_FRACTIONALMETRICS_ON
0705: && aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
0706: }
0707: }
0708: }
0709: info.aaHint = aahint;
0710: info.fontStrike = info.font2D.getStrike(font, devAt, textAt,
0711: aahint, fmhint);
0712: return info;
0713: }
0714:
0715: public static boolean isRotated(double[] mtx) {
0716: if ((mtx[0] == mtx[3]) && (mtx[1] == 0.0) && (mtx[2] == 0.0)
0717: && (mtx[0] > 0.0)) {
0718: return false;
0719: }
0720:
0721: return true;
0722: }
0723:
0724: public void setFont(Font font) {
0725: /* replacing the reference equality test font != this.font with
0726: * !font.equals(this.font) did not yield any measurable difference
0727: * in testing, but there may be yet to be identified cases where it
0728: * is beneficial.
0729: */
0730: if (font != null && font != this .font/*!font.equals(this.font)*/) {
0731: /* In the GASP AA case the textpipe depends on the glyph size
0732: * as determined by graphics and font transforms as well as the
0733: * font size, and information in the font. But we may invalidate
0734: * the pipe only to find that it made no difference.
0735: * Deferring pipe invalidation to checkFontInfo won't work because
0736: * when called we may already be rendering to the wrong pipe.
0737: * So, if the font is transformed, or the graphics has more than
0738: * a simple scale, we'll take that as enough of a hint to
0739: * revalidate everything. But if they aren't we will
0740: * use the font's point size to query the gasp table and see if
0741: * what it says matches what's currently being used, in which
0742: * case there's no need to invalidate the textpipe.
0743: * This should be sufficient for all typical uses cases.
0744: */
0745: if (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP
0746: && textpipe != invalidpipe
0747: && (transformState > TRANSFORM_ANY_TRANSLATE
0748: || font.isTransformed() || fontInfo == null || // Precaution, if true shouldn't get here
0749: (fontInfo.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) != FontManager
0750: .getFont2D(font).useAAForPtSize(
0751: font.getSize()))) {
0752: textpipe = invalidpipe;
0753: }
0754: this .font = font;
0755: this .fontMetrics = null;
0756: this .validFontInfo = false;
0757: }
0758: }
0759:
0760: public FontInfo getFontInfo() {
0761: if (!validFontInfo) {
0762: this .fontInfo = checkFontInfo(this .fontInfo, font, null);
0763: validFontInfo = true;
0764: }
0765: return this .fontInfo;
0766: }
0767:
0768: /* Used by drawGlyphVector which specifies its own font. */
0769: public FontInfo getGVFontInfo(Font font, FontRenderContext frc) {
0770: if (glyphVectorFontInfo != null
0771: && glyphVectorFontInfo.font == font
0772: && glyphVectorFRC == frc) {
0773: return glyphVectorFontInfo;
0774: } else {
0775: glyphVectorFRC = frc;
0776: return glyphVectorFontInfo = checkFontInfo(
0777: glyphVectorFontInfo, font, frc);
0778: }
0779: }
0780:
0781: public FontMetrics getFontMetrics() {
0782: if (this .fontMetrics != null) {
0783: return this .fontMetrics;
0784: }
0785: /* NB the constructor and the setter disallow "font" being null */
0786: return this .fontMetrics = FontDesignMetrics.getMetrics(font,
0787: getFontRenderContext());
0788: }
0789:
0790: public FontMetrics getFontMetrics(Font font) {
0791: if ((this .fontMetrics != null) && (font == this .font)) {
0792: return this .fontMetrics;
0793: }
0794: FontMetrics fm = FontDesignMetrics.getMetrics(font,
0795: getFontRenderContext());
0796:
0797: if (this .font == font) {
0798: this .fontMetrics = fm;
0799: }
0800: return fm;
0801: }
0802:
0803: /**
0804: * Checks to see if a Path intersects the specified Rectangle in device
0805: * space. The rendering attributes taken into account include the
0806: * clip, transform, and stroke attributes.
0807: * @param rect The area in device space to check for a hit.
0808: * @param p The path to check for a hit.
0809: * @param onStroke Flag to choose between testing the stroked or
0810: * the filled path.
0811: * @return True if there is a hit, false otherwise.
0812: * @see #setStroke
0813: * @see #fillPath
0814: * @see #drawPath
0815: * @see #transform
0816: * @see #setTransform
0817: * @see #clip
0818: * @see #setClip
0819: */
0820: public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
0821: if (onStroke) {
0822: s = stroke.createStrokedShape(s);
0823: }
0824:
0825: s = transformShape(s);
0826: if ((constrainX | constrainY) != 0) {
0827: rect = new Rectangle(rect);
0828: rect.translate(constrainX, constrainY);
0829: }
0830:
0831: return s.intersects(rect);
0832: }
0833:
0834: /**
0835: * Return the ColorModel associated with this Graphics2D.
0836: */
0837: public ColorModel getDeviceColorModel() {
0838: return surfaceData.getColorModel();
0839: }
0840:
0841: /**
0842: * Return the device configuration associated with this Graphics2D.
0843: */
0844: public GraphicsConfiguration getDeviceConfiguration() {
0845: return surfaceData.getDeviceConfiguration();
0846: }
0847:
0848: /**
0849: * Return the SurfaceData object assigned to manage the destination
0850: * drawable surface of this Graphics2D.
0851: */
0852: public final SurfaceData getSurfaceData() {
0853: return surfaceData;
0854: }
0855:
0856: /**
0857: * Sets the Composite in the current graphics state. Composite is used
0858: * in all drawing methods such as drawImage, drawString, drawPath,
0859: * and fillPath. It specifies how new pixels are to be combined with
0860: * the existing pixels on the graphics device in the rendering process.
0861: * @param comp The Composite object to be used for drawing.
0862: * @see java.awt.Graphics#setXORMode
0863: * @see java.awt.Graphics#setPaintMode
0864: * @see AlphaComposite
0865: */
0866: public void setComposite(Composite comp) {
0867: if (composite == comp) {
0868: return;
0869: }
0870: int newCompState;
0871: CompositeType newCompType;
0872: if (comp instanceof AlphaComposite) {
0873: AlphaComposite alphacomp = (AlphaComposite) comp;
0874: newCompType = CompositeType.forAlphaComposite(alphacomp);
0875: if (newCompType == CompositeType.SrcOverNoEa) {
0876: if (paintState == PAINT_OPAQUECOLOR
0877: || (paintState > PAINT_ALPHACOLOR && paint
0878: .getTransparency() == Transparency.OPAQUE)) {
0879: newCompState = COMP_ISCOPY;
0880: } else {
0881: newCompState = COMP_ALPHA;
0882: }
0883: } else if (newCompType == CompositeType.SrcNoEa
0884: || newCompType == CompositeType.Src
0885: || newCompType == CompositeType.Clear) {
0886: newCompState = COMP_ISCOPY;
0887: } else if (surfaceData.getTransparency() == Transparency.OPAQUE
0888: && newCompType == CompositeType.SrcIn) {
0889: newCompState = COMP_ISCOPY;
0890: } else {
0891: newCompState = COMP_ALPHA;
0892: }
0893: } else if (comp instanceof XORComposite) {
0894: newCompState = COMP_XOR;
0895: newCompType = CompositeType.Xor;
0896: } else if (comp == null) {
0897: throw new IllegalArgumentException("null Composite");
0898: } else {
0899: surfaceData.checkCustomComposite();
0900: newCompState = COMP_CUSTOM;
0901: newCompType = CompositeType.General;
0902: }
0903: if (compositeState != newCompState || imageComp != newCompType) {
0904: compositeState = newCompState;
0905: imageComp = newCompType;
0906: invalidatePipe();
0907: validFontInfo = false;
0908: }
0909: composite = comp;
0910: if (paintState <= PAINT_ALPHACOLOR) {
0911: validateColor();
0912: }
0913: }
0914:
0915: /**
0916: * Sets the Paint in the current graphics state.
0917: * @param paint The Paint object to be used to generate color in
0918: * the rendering process.
0919: * @see java.awt.Graphics#setColor
0920: * @see GradientPaint
0921: * @see TexturePaint
0922: */
0923: public void setPaint(Paint paint) {
0924: if (paint instanceof Color) {
0925: setColor((Color) paint);
0926: return;
0927: }
0928: if (paint == null || this .paint == paint) {
0929: return;
0930: }
0931: this .paint = paint;
0932: if (imageComp == CompositeType.SrcOverNoEa) {
0933: // special case where compState depends on opacity of paint
0934: if (paint.getTransparency() == Transparency.OPAQUE) {
0935: if (compositeState != COMP_ISCOPY) {
0936: compositeState = COMP_ISCOPY;
0937: }
0938: } else {
0939: if (compositeState == COMP_ISCOPY) {
0940: compositeState = COMP_ALPHA;
0941: }
0942: }
0943: }
0944: Class paintClass = paint.getClass();
0945: if (paintClass == GradientPaint.class) {
0946: paintState = PAINT_GRADIENT;
0947: } else if (paintClass == LinearGradientPaint.class) {
0948: paintState = PAINT_LIN_GRADIENT;
0949: } else if (paintClass == RadialGradientPaint.class) {
0950: paintState = PAINT_RAD_GRADIENT;
0951: } else if (paintClass == TexturePaint.class) {
0952: paintState = PAINT_TEXTURE;
0953: } else {
0954: paintState = PAINT_CUSTOM;
0955: }
0956: validFontInfo = false;
0957: invalidatePipe();
0958: }
0959:
0960: static final int NON_UNIFORM_SCALE_MASK = (AffineTransform.TYPE_GENERAL_TRANSFORM | AffineTransform.TYPE_GENERAL_SCALE);
0961: public static final double MinPenSizeAA = sun.java2d.pipe.RenderingEngine
0962: .getInstance().getMinimumAAPenSize();
0963: public static final double MinPenSizeAASquared = (MinPenSizeAA * MinPenSizeAA);
0964: // Since inaccuracies in the trig package can cause us to
0965: // calculated a rotated pen width of just slightly greater
0966: // than 1.0, we add a fudge factor to our comparison value
0967: // here so that we do not misclassify single width lines as
0968: // wide lines under certain rotations.
0969: public static final double MinPenSizeSquared = 1.000000001;
0970:
0971: private void validateBasicStroke(BasicStroke bs) {
0972: boolean aa = (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON);
0973: if (transformState < TRANSFORM_TRANSLATESCALE) {
0974: if (aa) {
0975: if (bs.getLineWidth() <= MinPenSizeAA) {
0976: if (bs.getDashArray() == null) {
0977: strokeState = STROKE_THIN;
0978: } else {
0979: strokeState = STROKE_THINDASHED;
0980: }
0981: } else {
0982: strokeState = STROKE_WIDE;
0983: }
0984: } else {
0985: if (bs == defaultStroke) {
0986: strokeState = STROKE_THIN;
0987: } else if (bs.getLineWidth() <= 1.0f) {
0988: if (bs.getDashArray() == null) {
0989: strokeState = STROKE_THIN;
0990: } else {
0991: strokeState = STROKE_THINDASHED;
0992: }
0993: } else {
0994: strokeState = STROKE_WIDE;
0995: }
0996: }
0997: } else {
0998: double widthsquared;
0999: if ((transform.getType() & NON_UNIFORM_SCALE_MASK) == 0) {
1000: /* sqrt omitted, compare to squared limits below. */
1001: widthsquared = Math.abs(transform.getDeterminant());
1002: } else {
1003: /* First calculate the "maximum scale" of this transform. */
1004: double A = transform.getScaleX(); // m00
1005: double C = transform.getShearX(); // m01
1006: double B = transform.getShearY(); // m10
1007: double D = transform.getScaleY(); // m11
1008:
1009: /*
1010: * Given a 2 x 2 affine matrix [ A B ] such that
1011: * [ C D ]
1012: * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
1013: * find the maximum magnitude (norm) of the vector v'
1014: * with the constraint (x^2 + y^2 = 1).
1015: * The equation to maximize is
1016: * |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
1017: * or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
1018: * Since sqrt is monotonic we can maximize |v'|^2
1019: * instead and plug in the substitution y = sqrt(1 - x^2).
1020: * Trigonometric equalities can then be used to get
1021: * rid of most of the sqrt terms.
1022: */
1023: double EA = A * A + B * B; // x^2 coefficient
1024: double EB = 2 * (A * C + B * D); // xy coefficient
1025: double EC = C * C + D * D; // y^2 coefficient
1026:
1027: /*
1028: * There is a lot of calculus omitted here.
1029: *
1030: * Conceptually, in the interests of understanding the
1031: * terms that the calculus produced we can consider
1032: * that EA and EC end up providing the lengths along
1033: * the major axes and the hypot term ends up being an
1034: * adjustment for the additional length along the off-axis
1035: * angle of rotated or sheared ellipses as well as an
1036: * adjustment for the fact that the equation below
1037: * averages the two major axis lengths. (Notice that
1038: * the hypot term contains a part which resolves to the
1039: * difference of these two axis lengths in the absence
1040: * of rotation.)
1041: *
1042: * In the calculus, the ratio of the EB and (EA-EC) terms
1043: * ends up being the tangent of 2*theta where theta is
1044: * the angle that the long axis of the ellipse makes
1045: * with the horizontal axis. Thus, this equation is
1046: * calculating the length of the hypotenuse of a triangle
1047: * along that axis.
1048: */
1049: double hypot = Math.sqrt(EB * EB + (EA - EC)
1050: * (EA - EC));
1051:
1052: /* sqrt omitted, compare to squared limits below. */
1053: widthsquared = ((EA + EC + hypot) / 2.0);
1054: }
1055: if (bs != defaultStroke) {
1056: widthsquared *= bs.getLineWidth() * bs.getLineWidth();
1057: }
1058: if (widthsquared <= (aa ? MinPenSizeAASquared
1059: : MinPenSizeSquared)) {
1060: if (bs.getDashArray() == null) {
1061: strokeState = STROKE_THIN;
1062: } else {
1063: strokeState = STROKE_THINDASHED;
1064: }
1065: } else {
1066: strokeState = STROKE_WIDE;
1067: }
1068: }
1069: }
1070:
1071: /*
1072: * Sets the Stroke in the current graphics state.
1073: * @param s The Stroke object to be used to stroke a Path in
1074: * the rendering process.
1075: * @see BasicStroke
1076: */
1077: public void setStroke(Stroke s) {
1078: if (s == null) {
1079: throw new IllegalArgumentException("null Stroke");
1080: }
1081: int saveStrokeState = strokeState;
1082: stroke = s;
1083: if (s instanceof BasicStroke) {
1084: validateBasicStroke((BasicStroke) s);
1085: } else {
1086: strokeState = STROKE_CUSTOM;
1087: }
1088: if (strokeState != saveStrokeState) {
1089: invalidatePipe();
1090: }
1091: }
1092:
1093: /**
1094: * Sets the preferences for the rendering algorithms.
1095: * Hint categories include controls for rendering quality and
1096: * overall time/quality trade-off in the rendering process.
1097: * @param hintKey The key of hint to be set. The strings are
1098: * defined in the RenderingHints class.
1099: * @param hintValue The value indicating preferences for the specified
1100: * hint category. These strings are defined in the RenderingHints
1101: * class.
1102: * @see RenderingHints
1103: */
1104: public void setRenderingHint(Key hintKey, Object hintValue) {
1105: // If we recognize the key, we must recognize the value
1106: // otherwise throw an IllegalArgumentException
1107: // and do not change the Hints object
1108: // If we do not recognize the key, just pass it through
1109: // to the Hints object untouched
1110: if (!hintKey.isCompatibleValue(hintValue)) {
1111: throw new IllegalArgumentException(hintValue
1112: + " is not compatible with " + hintKey);
1113: }
1114: if (hintKey instanceof SunHints.Key) {
1115: boolean stateChanged;
1116: boolean textStateChanged = false;
1117: boolean recognized = true;
1118: SunHints.Key sunKey = (SunHints.Key) hintKey;
1119: int newHint;
1120: if (sunKey == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST) {
1121: newHint = ((Integer) hintValue).intValue();
1122: } else {
1123: newHint = ((SunHints.Value) hintValue).getIndex();
1124: }
1125: switch (sunKey.getIndex()) {
1126: case SunHints.INTKEY_RENDERING:
1127: stateChanged = (renderHint != newHint);
1128: if (stateChanged) {
1129: renderHint = newHint;
1130: if (interpolationHint == -1) {
1131: interpolationType = (newHint == SunHints.INTVAL_RENDER_QUALITY ? AffineTransformOp.TYPE_BILINEAR
1132: : AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
1133: }
1134: }
1135: break;
1136: case SunHints.INTKEY_ANTIALIASING:
1137: stateChanged = (antialiasHint != newHint);
1138: antialiasHint = newHint;
1139: if (stateChanged) {
1140: textStateChanged = (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT);
1141: if (strokeState != STROKE_CUSTOM) {
1142: validateBasicStroke((BasicStroke) stroke);
1143: }
1144: }
1145: break;
1146: case SunHints.INTKEY_TEXT_ANTIALIASING:
1147: stateChanged = (textAntialiasHint != newHint);
1148: textStateChanged = stateChanged;
1149: textAntialiasHint = newHint;
1150: break;
1151: case SunHints.INTKEY_FRACTIONALMETRICS:
1152: stateChanged = (fractionalMetricsHint != newHint);
1153: textStateChanged = stateChanged;
1154: fractionalMetricsHint = newHint;
1155: break;
1156: case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1157: stateChanged = false;
1158: /* Already have validated it is an int 100 <= newHint <= 250 */
1159: lcdTextContrast = newHint;
1160: break;
1161: case SunHints.INTKEY_INTERPOLATION:
1162: interpolationHint = newHint;
1163: switch (newHint) {
1164: case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1165: newHint = AffineTransformOp.TYPE_BICUBIC;
1166: break;
1167: case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1168: newHint = AffineTransformOp.TYPE_BILINEAR;
1169: break;
1170: default:
1171: case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1172: newHint = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1173: break;
1174: }
1175: stateChanged = (interpolationType != newHint);
1176: interpolationType = newHint;
1177: break;
1178: case SunHints.INTKEY_STROKE_CONTROL:
1179: stateChanged = (strokeHint != newHint);
1180: strokeHint = newHint;
1181: break;
1182: default:
1183: recognized = false;
1184: stateChanged = false;
1185: break;
1186: }
1187: if (recognized) {
1188: if (stateChanged) {
1189: invalidatePipe();
1190: if (textStateChanged) {
1191: fontMetrics = null;
1192: this .cachedFRC = null;
1193: validFontInfo = false;
1194: this .glyphVectorFontInfo = null;
1195: }
1196: }
1197: if (hints != null) {
1198: hints.put(hintKey, hintValue);
1199: }
1200: return;
1201: }
1202: }
1203: // Nothing we recognize so none of "our state" has changed
1204: if (hints == null) {
1205: hints = makeHints(null);
1206: }
1207: hints.put(hintKey, hintValue);
1208: }
1209:
1210: /**
1211: * Returns the preferences for the rendering algorithms.
1212: * @param hintCategory The category of hint to be set. The strings
1213: * are defined in the RenderingHints class.
1214: * @return The preferences for rendering algorithms. The strings
1215: * are defined in the RenderingHints class.
1216: * @see RenderingHints
1217: */
1218: public Object getRenderingHint(Key hintKey) {
1219: if (hints != null) {
1220: return hints.get(hintKey);
1221: }
1222: if (!(hintKey instanceof SunHints.Key)) {
1223: return null;
1224: }
1225: int keyindex = ((SunHints.Key) hintKey).getIndex();
1226: switch (keyindex) {
1227: case SunHints.INTKEY_RENDERING:
1228: return SunHints.Value.get(SunHints.INTKEY_RENDERING,
1229: renderHint);
1230: case SunHints.INTKEY_ANTIALIASING:
1231: return SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,
1232: antialiasHint);
1233: case SunHints.INTKEY_TEXT_ANTIALIASING:
1234: return SunHints.Value.get(
1235: SunHints.INTKEY_TEXT_ANTIALIASING,
1236: textAntialiasHint);
1237: case SunHints.INTKEY_FRACTIONALMETRICS:
1238: return SunHints.Value.get(
1239: SunHints.INTKEY_FRACTIONALMETRICS,
1240: fractionalMetricsHint);
1241: case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1242: return new Integer(lcdTextContrast);
1243: case SunHints.INTKEY_INTERPOLATION:
1244: switch (interpolationHint) {
1245: case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1246: return SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1247: case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1248: return SunHints.VALUE_INTERPOLATION_BILINEAR;
1249: case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1250: return SunHints.VALUE_INTERPOLATION_BICUBIC;
1251: }
1252: return null;
1253: case SunHints.INTKEY_STROKE_CONTROL:
1254: return SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,
1255: strokeHint);
1256: }
1257: return null;
1258: }
1259:
1260: /**
1261: * Sets the preferences for the rendering algorithms.
1262: * Hint categories include controls for rendering quality and
1263: * overall time/quality trade-off in the rendering process.
1264: * @param hints The rendering hints to be set
1265: * @see RenderingHints
1266: */
1267: public void setRenderingHints(Map<?, ?> hints) {
1268: this .hints = null;
1269: renderHint = SunHints.INTVAL_RENDER_DEFAULT;
1270: antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
1271: textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
1272: fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
1273: lcdTextContrast = lcdTextContrastDefaultValue;
1274: interpolationHint = -1;
1275: interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1276: boolean customHintPresent = false;
1277: Iterator iter = hints.keySet().iterator();
1278: while (iter.hasNext()) {
1279: Object key = iter.next();
1280: if (key == SunHints.KEY_RENDERING
1281: || key == SunHints.KEY_ANTIALIASING
1282: || key == SunHints.KEY_TEXT_ANTIALIASING
1283: || key == SunHints.KEY_FRACTIONALMETRICS
1284: || key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST
1285: || key == SunHints.KEY_STROKE_CONTROL
1286: || key == SunHints.KEY_INTERPOLATION) {
1287: setRenderingHint((Key) key, hints.get(key));
1288: } else {
1289: customHintPresent = true;
1290: }
1291: }
1292: if (customHintPresent) {
1293: this .hints = makeHints(hints);
1294: }
1295: invalidatePipe();
1296: }
1297:
1298: /**
1299: * Adds a number of preferences for the rendering algorithms.
1300: * Hint categories include controls for rendering quality and
1301: * overall time/quality trade-off in the rendering process.
1302: * @param hints The rendering hints to be set
1303: * @see RenderingHints
1304: */
1305: public void addRenderingHints(Map<?, ?> hints) {
1306: boolean customHintPresent = false;
1307: Iterator iter = hints.keySet().iterator();
1308: while (iter.hasNext()) {
1309: Object key = iter.next();
1310: if (key == SunHints.KEY_RENDERING
1311: || key == SunHints.KEY_ANTIALIASING
1312: || key == SunHints.KEY_TEXT_ANTIALIASING
1313: || key == SunHints.KEY_FRACTIONALMETRICS
1314: || key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST
1315: || key == SunHints.KEY_STROKE_CONTROL
1316: || key == SunHints.KEY_INTERPOLATION) {
1317: setRenderingHint((Key) key, hints.get(key));
1318: } else {
1319: customHintPresent = true;
1320: }
1321: }
1322: if (customHintPresent) {
1323: if (this .hints == null) {
1324: this .hints = makeHints(hints);
1325: } else {
1326: this .hints.putAll(hints);
1327: }
1328: }
1329: }
1330:
1331: /**
1332: * Gets the preferences for the rendering algorithms.
1333: * Hint categories include controls for rendering quality and
1334: * overall time/quality trade-off in the rendering process.
1335: * @see RenderingHints
1336: */
1337: public RenderingHints getRenderingHints() {
1338: if (hints == null) {
1339: return makeHints(null);
1340: } else {
1341: return (RenderingHints) hints.clone();
1342: }
1343: }
1344:
1345: RenderingHints makeHints(Map hints) {
1346: RenderingHints model = new RenderingHints(hints);
1347: model.put(SunHints.KEY_RENDERING, SunHints.Value.get(
1348: SunHints.INTKEY_RENDERING, renderHint));
1349: model.put(SunHints.KEY_ANTIALIASING, SunHints.Value.get(
1350: SunHints.INTKEY_ANTIALIASING, antialiasHint));
1351: model.put(SunHints.KEY_TEXT_ANTIALIASING, SunHints.Value.get(
1352: SunHints.INTKEY_TEXT_ANTIALIASING, textAntialiasHint));
1353: model.put(SunHints.KEY_FRACTIONALMETRICS, SunHints.Value.get(
1354: SunHints.INTKEY_FRACTIONALMETRICS,
1355: fractionalMetricsHint));
1356: model.put(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST,
1357: new Integer(lcdTextContrast));
1358: Object value;
1359: switch (interpolationHint) {
1360: case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1361: value = SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1362: break;
1363: case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1364: value = SunHints.VALUE_INTERPOLATION_BILINEAR;
1365: break;
1366: case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1367: value = SunHints.VALUE_INTERPOLATION_BICUBIC;
1368: break;
1369: default:
1370: value = null;
1371: break;
1372: }
1373: if (value != null) {
1374: model.put(SunHints.KEY_INTERPOLATION, value);
1375: }
1376: model.put(SunHints.KEY_STROKE_CONTROL, SunHints.Value.get(
1377: SunHints.INTKEY_STROKE_CONTROL, strokeHint));
1378: return model;
1379: }
1380:
1381: /**
1382: * Concatenates the current transform of this Graphics2D with a
1383: * translation transformation.
1384: * This is equivalent to calling transform(T), where T is an
1385: * AffineTransform represented by the following matrix:
1386: * <pre>
1387: * [ 1 0 tx ]
1388: * [ 0 1 ty ]
1389: * [ 0 0 1 ]
1390: * </pre>
1391: */
1392: public void translate(double tx, double ty) {
1393: transform.translate(tx, ty);
1394: invalidateTransform();
1395: }
1396:
1397: /**
1398: * Concatenates the current transform of this Graphics2D with a
1399: * rotation transformation.
1400: * This is equivalent to calling transform(R), where R is an
1401: * AffineTransform represented by the following matrix:
1402: * <pre>
1403: * [ cos(theta) -sin(theta) 0 ]
1404: * [ sin(theta) cos(theta) 0 ]
1405: * [ 0 0 1 ]
1406: * </pre>
1407: * Rotating with a positive angle theta rotates points on the positive
1408: * x axis toward the positive y axis.
1409: * @param theta The angle of rotation in radians.
1410: */
1411: public void rotate(double theta) {
1412: transform.rotate(theta);
1413: invalidateTransform();
1414: }
1415:
1416: /**
1417: * Concatenates the current transform of this Graphics2D with a
1418: * translated rotation transformation.
1419: * This is equivalent to the following sequence of calls:
1420: * <pre>
1421: * translate(x, y);
1422: * rotate(theta);
1423: * translate(-x, -y);
1424: * </pre>
1425: * Rotating with a positive angle theta rotates points on the positive
1426: * x axis toward the positive y axis.
1427: * @param theta The angle of rotation in radians.
1428: * @param x The x coordinate of the origin of the rotation
1429: * @param y The x coordinate of the origin of the rotation
1430: */
1431: public void rotate(double theta, double x, double y) {
1432: transform.rotate(theta, x, y);
1433: invalidateTransform();
1434: }
1435:
1436: /**
1437: * Concatenates the current transform of this Graphics2D with a
1438: * scaling transformation.
1439: * This is equivalent to calling transform(S), where S is an
1440: * AffineTransform represented by the following matrix:
1441: * <pre>
1442: * [ sx 0 0 ]
1443: * [ 0 sy 0 ]
1444: * [ 0 0 1 ]
1445: * </pre>
1446: */
1447: public void scale(double sx, double sy) {
1448: transform.scale(sx, sy);
1449: invalidateTransform();
1450: }
1451:
1452: /**
1453: * Concatenates the current transform of this Graphics2D with a
1454: * shearing transformation.
1455: * This is equivalent to calling transform(SH), where SH is an
1456: * AffineTransform represented by the following matrix:
1457: * <pre>
1458: * [ 1 shx 0 ]
1459: * [ shy 1 0 ]
1460: * [ 0 0 1 ]
1461: * </pre>
1462: * @param shx The factor by which coordinates are shifted towards the
1463: * positive X axis direction according to their Y coordinate
1464: * @param shy The factor by which coordinates are shifted towards the
1465: * positive Y axis direction according to their X coordinate
1466: */
1467: public void shear(double shx, double shy) {
1468: transform.shear(shx, shy);
1469: invalidateTransform();
1470: }
1471:
1472: /**
1473: * Composes a Transform object with the transform in this
1474: * Graphics2D according to the rule last-specified-first-applied.
1475: * If the currrent transform is Cx, the result of composition
1476: * with Tx is a new transform Cx'. Cx' becomes the current
1477: * transform for this Graphics2D.
1478: * Transforming a point p by the updated transform Cx' is
1479: * equivalent to first transforming p by Tx and then transforming
1480: * the result by the original transform Cx. In other words,
1481: * Cx'(p) = Cx(Tx(p)).
1482: * A copy of the Tx is made, if necessary, so further
1483: * modifications to Tx do not affect rendering.
1484: * @param Tx The Transform object to be composed with the current
1485: * transform.
1486: * @see #setTransform
1487: * @see AffineTransform
1488: */
1489: public void transform(AffineTransform xform) {
1490: this .transform.concatenate(xform);
1491: invalidateTransform();
1492: }
1493:
1494: /**
1495: * Translate
1496: */
1497: public void translate(int x, int y) {
1498: transform.translate(x, y);
1499: if (transformState <= TRANSFORM_INT_TRANSLATE) {
1500: transX += x;
1501: transY += y;
1502: transformState = (((transX | transY) == 0) ? TRANSFORM_ISIDENT
1503: : TRANSFORM_INT_TRANSLATE);
1504: } else {
1505: invalidateTransform();
1506: }
1507: }
1508:
1509: /**
1510: * Sets the Transform in the current graphics state.
1511: * @param Tx The Transform object to be used in the rendering process.
1512: * @see #transform
1513: * @see TransformChain
1514: * @see AffineTransform
1515: */
1516: public void setTransform(AffineTransform Tx) {
1517: if ((constrainX | constrainY) == 0) {
1518: transform.setTransform(Tx);
1519: } else {
1520: transform.setToTranslation(constrainX, constrainY);
1521: transform.concatenate(Tx);
1522: }
1523: invalidateTransform();
1524: }
1525:
1526: protected void invalidateTransform() {
1527: int type = transform.getType();
1528: int origTransformState = transformState;
1529: if (type == AffineTransform.TYPE_IDENTITY) {
1530: transformState = TRANSFORM_ISIDENT;
1531: transX = transY = 0;
1532: } else if (type == AffineTransform.TYPE_TRANSLATION) {
1533: double dtx = transform.getTranslateX();
1534: double dty = transform.getTranslateY();
1535: transX = (int) Math.floor(dtx + 0.5);
1536: transY = (int) Math.floor(dty + 0.5);
1537: if (dtx == transX && dty == transY) {
1538: transformState = TRANSFORM_INT_TRANSLATE;
1539: } else {
1540: transformState = TRANSFORM_ANY_TRANSLATE;
1541: }
1542: } else if ((type & (AffineTransform.TYPE_FLIP
1543: | AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0) {
1544: transformState = TRANSFORM_TRANSLATESCALE;
1545: transX = transY = 0;
1546: } else {
1547: transformState = TRANSFORM_GENERIC;
1548: transX = transY = 0;
1549: }
1550:
1551: if (transformState >= TRANSFORM_TRANSLATESCALE
1552: || origTransformState >= TRANSFORM_TRANSLATESCALE) {
1553: /* Its only in this case that the previous or current transform
1554: * was more than a translate that font info is invalidated
1555: */
1556: cachedFRC = null;
1557: this .validFontInfo = false;
1558: this .fontMetrics = null;
1559: this .glyphVectorFontInfo = null;
1560:
1561: if (transformState != origTransformState) {
1562: invalidatePipe();
1563: }
1564: }
1565: if (strokeState != STROKE_CUSTOM) {
1566: validateBasicStroke((BasicStroke) stroke);
1567: }
1568: }
1569:
1570: /**
1571: * Returns the current Transform in the Graphics2D state.
1572: * @see #transform
1573: * @see #setTransform
1574: */
1575: public AffineTransform getTransform() {
1576: if ((constrainX | constrainY) == 0) {
1577: return new AffineTransform(transform);
1578: }
1579: AffineTransform tx = AffineTransform.getTranslateInstance(
1580: -constrainX, -constrainY);
1581: tx.concatenate(transform);
1582: return tx;
1583: }
1584:
1585: /**
1586: * Returns the current Transform ignoring the "constrain"
1587: * rectangle.
1588: */
1589: public AffineTransform cloneTransform() {
1590: return new AffineTransform(transform);
1591: }
1592:
1593: /**
1594: * Returns the current Paint in the Graphics2D state.
1595: * @see #setPaint
1596: * @see java.awt.Graphics#setColor
1597: */
1598: public Paint getPaint() {
1599: return paint;
1600: }
1601:
1602: /**
1603: * Returns the current Composite in the Graphics2D state.
1604: * @see #setComposite
1605: */
1606: public Composite getComposite() {
1607: return composite;
1608: }
1609:
1610: public Color getColor() {
1611: return foregroundColor;
1612: }
1613:
1614: /*
1615: * Validate the eargb and pixel fields against the current color.
1616: *
1617: * The eargb field must take into account the extraAlpha
1618: * value of an AlphaComposite. It may also take into account
1619: * the Fsrc Porter-Duff blending function if such a function is
1620: * a constant (see handling of Clear mode below). For instance,
1621: * by factoring in the (Fsrc == 0) state of the Clear mode we can
1622: * use a SrcNoEa loop just as easily as a general Alpha loop
1623: * since the math will be the same in both cases.
1624: *
1625: * The pixel field will always be the best pixel data choice for
1626: * the final result of all calculations applied to the eargb field.
1627: *
1628: * Note that this method is only necessary under the following
1629: * conditions:
1630: * (paintState <= PAINT_ALPHA_COLOR &&
1631: * compositeState <= COMP_CUSTOM)
1632: * though nothing bad will happen if it is run in other states.
1633: */
1634: final void validateColor() {
1635: int eargb;
1636: if (imageComp == CompositeType.Clear) {
1637: eargb = 0;
1638: } else {
1639: eargb = foregroundColor.getRGB();
1640: if (compositeState <= COMP_ALPHA
1641: && imageComp != CompositeType.SrcNoEa
1642: && imageComp != CompositeType.SrcOverNoEa) {
1643: AlphaComposite alphacomp = (AlphaComposite) composite;
1644: int a = Math.round(alphacomp.getAlpha()
1645: * (eargb >>> 24));
1646: eargb = (eargb & 0x00ffffff) | (a << 24);
1647: }
1648: }
1649: this .eargb = eargb;
1650: this .pixel = surfaceData.pixelFor(eargb);
1651: }
1652:
1653: public void setColor(Color color) {
1654: if (color == null || color == paint) {
1655: return;
1656: }
1657: this .paint = foregroundColor = color;
1658: validateColor();
1659: if ((eargb >> 24) == -1) {
1660: if (paintState == PAINT_OPAQUECOLOR) {
1661: return;
1662: }
1663: paintState = PAINT_OPAQUECOLOR;
1664: if (imageComp == CompositeType.SrcOverNoEa) {
1665: // special case where compState depends on opacity of paint
1666: compositeState = COMP_ISCOPY;
1667: }
1668: } else {
1669: if (paintState == PAINT_ALPHACOLOR) {
1670: return;
1671: }
1672: paintState = PAINT_ALPHACOLOR;
1673: if (imageComp == CompositeType.SrcOverNoEa) {
1674: // special case where compState depends on opacity of paint
1675: compositeState = COMP_ALPHA;
1676: }
1677: }
1678: validFontInfo = false;
1679: invalidatePipe();
1680: }
1681:
1682: /**
1683: * Sets the background color in this context used for clearing a region.
1684: * When Graphics2D is constructed for a component, the backgroung color is
1685: * inherited from the component. Setting the background color in the
1686: * Graphics2D context only affects the subsequent clearRect() calls and
1687: * not the background color of the component. To change the background
1688: * of the component, use appropriate methods of the component.
1689: * @param color The background color that should be used in
1690: * subsequent calls to clearRect().
1691: * @see getBackground
1692: * @see Graphics.clearRect()
1693: */
1694: public void setBackground(Color color) {
1695: backgroundColor = color;
1696: }
1697:
1698: /**
1699: * Returns the background color used for clearing a region.
1700: * @see setBackground
1701: */
1702: public Color getBackground() {
1703: return backgroundColor;
1704: }
1705:
1706: /**
1707: * Returns the current Stroke in the Graphics2D state.
1708: * @see setStroke
1709: */
1710: public Stroke getStroke() {
1711: return stroke;
1712: }
1713:
1714: public Rectangle getClipBounds() {
1715: Rectangle r;
1716: if (clipState == CLIP_DEVICE) {
1717: r = null;
1718: } else if (transformState <= TRANSFORM_INT_TRANSLATE) {
1719: if (usrClip instanceof Rectangle) {
1720: r = new Rectangle((Rectangle) usrClip);
1721: } else {
1722: r = usrClip.getBounds();
1723: }
1724: r.translate(-transX, -transY);
1725: } else {
1726: r = getClip().getBounds();
1727: }
1728: return r;
1729: }
1730:
1731: public Rectangle getClipBounds(Rectangle r) {
1732: if (clipState != CLIP_DEVICE) {
1733: if (transformState <= TRANSFORM_INT_TRANSLATE) {
1734: if (usrClip instanceof Rectangle) {
1735: r.setBounds((Rectangle) usrClip);
1736: } else {
1737: r.setBounds(usrClip.getBounds());
1738: }
1739: r.translate(-transX, -transY);
1740: } else {
1741: r.setBounds(getClip().getBounds());
1742: }
1743: } else if (r == null) {
1744: throw new NullPointerException("null rectangle parameter");
1745: }
1746: return r;
1747: }
1748:
1749: public boolean hitClip(int x, int y, int width, int height) {
1750: if (width <= 0 || height <= 0) {
1751: return false;
1752: }
1753: if (transformState > TRANSFORM_INT_TRANSLATE) {
1754: // Note: Technically the most accurate test would be to
1755: // raster scan the parallelogram of the transformed rectangle
1756: // and do a span for span hit test against the clip, but for
1757: // speed we approximate the test with a bounding box of the
1758: // transformed rectangle. The cost of rasterizing the
1759: // transformed rectangle is probably high enough that it is
1760: // not worth doing so to save the caller from having to call
1761: // a rendering method where we will end up discovering the
1762: // same answer in about the same amount of time anyway.
1763: // This logic breaks down if this hit test is being performed
1764: // on the bounds of a group of shapes in which case it might
1765: // be beneficial to be a little more accurate to avoid lots
1766: // of subsequent rendering calls. In either case, this relaxed
1767: // test should not be significantly less accurate than the
1768: // optimal test for most transforms and so the conservative
1769: // answer should not cause too much extra work.
1770:
1771: double d[] = { x, y, x + width, y, x, y + height,
1772: x + width, y + height };
1773: transform.transform(d, 0, d, 0, 4);
1774: x = (int) Math.floor(Math.min(Math.min(d[0], d[2]), Math
1775: .min(d[4], d[6])));
1776: y = (int) Math.floor(Math.min(Math.min(d[1], d[3]), Math
1777: .min(d[5], d[7])));
1778: width = (int) Math.ceil(Math.max(Math.max(d[0], d[2]), Math
1779: .max(d[4], d[6])));
1780: height = (int) Math.ceil(Math.max(Math.max(d[1], d[3]),
1781: Math.max(d[5], d[7])));
1782: } else {
1783: x += transX;
1784: y += transY;
1785: width += x;
1786: height += y;
1787: }
1788: if (!getCompClip()
1789: .intersectsQuickCheckXYXY(x, y, width, height)) {
1790: return false;
1791: }
1792: // REMIND: We could go one step further here and examine the
1793: // non-rectangular clip shape more closely if there is one.
1794: // Since the clip has already been rasterized, the performance
1795: // penalty of doing the scan is probably still within the bounds
1796: // of a good tradeoff between speed and quality of the answer.
1797: return true;
1798: }
1799:
1800: protected void validateCompClip() {
1801: int origClipState = clipState;
1802: if (usrClip == null) {
1803: clipState = CLIP_DEVICE;
1804: clipRegion = devClip;
1805: } else if (usrClip instanceof Rectangle2D) {
1806: clipState = CLIP_RECTANGULAR;
1807: if (usrClip instanceof Rectangle) {
1808: clipRegion = devClip
1809: .getIntersection((Rectangle) usrClip);
1810: } else {
1811: clipRegion = devClip.getIntersection(usrClip
1812: .getBounds());
1813: }
1814: } else {
1815: PathIterator cpi = usrClip.getPathIterator(null);
1816: int box[] = new int[4];
1817: ShapeSpanIterator sr = new ShapeSpanIterator(this , false);
1818: try {
1819: sr.setOutputArea(devClip);
1820: sr.appendPath(cpi);
1821: sr.getPathBox(box);
1822: Region r = Region.getInstance(box);
1823: r.appendSpans(sr);
1824: clipRegion = r;
1825: clipState = r.isRectangular() ? CLIP_RECTANGULAR
1826: : CLIP_SHAPE;
1827: } finally {
1828: sr.dispose();
1829: }
1830: }
1831: if (origClipState != clipState
1832: && (clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE)) {
1833: validFontInfo = false;
1834: invalidatePipe();
1835: }
1836: }
1837:
1838: static final int NON_RECTILINEAR_TRANSFORM_MASK = (AffineTransform.TYPE_GENERAL_TRANSFORM | AffineTransform.TYPE_GENERAL_ROTATION);
1839:
1840: protected Shape transformShape(Shape s) {
1841: if (s == null) {
1842: return null;
1843: }
1844: if (transformState > TRANSFORM_INT_TRANSLATE) {
1845: return transformShape(transform, s);
1846: } else {
1847: return transformShape(transX, transY, s);
1848: }
1849: }
1850:
1851: public Shape untransformShape(Shape s) {
1852: if (s == null) {
1853: return null;
1854: }
1855: if (transformState > TRANSFORM_INT_TRANSLATE) {
1856: try {
1857: return transformShape(transform.createInverse(), s);
1858: } catch (NoninvertibleTransformException e) {
1859: return null;
1860: }
1861: } else {
1862: return transformShape(-transX, -transY, s);
1863: }
1864: }
1865:
1866: protected static Shape transformShape(int tx, int ty, Shape s) {
1867: if (s == null) {
1868: return null;
1869: }
1870:
1871: if (s instanceof Rectangle) {
1872: Rectangle r = s.getBounds();
1873: r.translate(tx, ty);
1874: return r;
1875: }
1876: if (s instanceof Rectangle2D) {
1877: Rectangle2D rect = (Rectangle2D) s;
1878: return new Rectangle2D.Double(rect.getX() + tx, rect.getY()
1879: + ty, rect.getWidth(), rect.getHeight());
1880: }
1881:
1882: if (tx == 0 && ty == 0) {
1883: return cloneShape(s);
1884: }
1885:
1886: AffineTransform mat = AffineTransform.getTranslateInstance(tx,
1887: ty);
1888: return mat.createTransformedShape(s);
1889: }
1890:
1891: protected static Shape transformShape(AffineTransform tx, Shape clip) {
1892: if (clip == null) {
1893: return null;
1894: }
1895:
1896: if (clip instanceof Rectangle2D
1897: && (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0) {
1898: Rectangle2D rect = (Rectangle2D) clip;
1899: double matrix[] = new double[4];
1900: matrix[0] = rect.getX();
1901: matrix[1] = rect.getY();
1902: matrix[2] = matrix[0] + rect.getWidth();
1903: matrix[3] = matrix[1] + rect.getHeight();
1904: tx.transform(matrix, 0, matrix, 0, 2);
1905: rect = new Rectangle2D.Float();
1906: rect.setFrameFromDiagonal(matrix[0], matrix[1], matrix[2],
1907: matrix[3]);
1908: return rect;
1909: }
1910:
1911: if (tx.isIdentity()) {
1912: return cloneShape(clip);
1913: }
1914:
1915: return tx.createTransformedShape(clip);
1916: }
1917:
1918: public void clipRect(int x, int y, int w, int h) {
1919: clip(new Rectangle(x, y, w, h));
1920: }
1921:
1922: public void setClip(int x, int y, int w, int h) {
1923: setClip(new Rectangle(x, y, w, h));
1924: }
1925:
1926: public Shape getClip() {
1927: return untransformShape(usrClip);
1928: }
1929:
1930: public void setClip(Shape sh) {
1931: usrClip = transformShape(sh);
1932: validateCompClip();
1933: }
1934:
1935: /**
1936: * Intersects the current clip with the specified Path and sets the
1937: * current clip to the resulting intersection. The clip is transformed
1938: * with the current transform in the Graphics2D state before being
1939: * intersected with the current clip. This method is used to make the
1940: * current clip smaller. To make the clip larger, use any setClip method.
1941: * @param p The Path to be intersected with the current clip.
1942: */
1943: public void clip(Shape s) {
1944: s = transformShape(s);
1945: if (usrClip != null) {
1946: s = intersectShapes(usrClip, s, true, true);
1947: }
1948: usrClip = s;
1949: validateCompClip();
1950: }
1951:
1952: public void setPaintMode() {
1953: setComposite(AlphaComposite.SrcOver);
1954: }
1955:
1956: public void setXORMode(Color c) {
1957: if (c == null) {
1958: throw new IllegalArgumentException("null XORColor");
1959: }
1960: setComposite(new XORComposite(c, surfaceData));
1961: }
1962:
1963: Blit lastCAblit;
1964: Composite lastCAcomp;
1965:
1966: public void copyArea(int x, int y, int w, int h, int dx, int dy) {
1967: try {
1968: doCopyArea(x, y, w, h, dx, dy);
1969: } catch (InvalidPipeException e) {
1970: revalidateAll();
1971: try {
1972: doCopyArea(x, y, w, h, dx, dy);
1973: } catch (InvalidPipeException e2) {
1974: // Still catching the exception; we are not yet ready to
1975: // validate the surfaceData correctly. Fail for now and
1976: // try again next time around.
1977: }
1978: } finally {
1979: surfaceData.markDirty();
1980: }
1981: }
1982:
1983: private void doCopyArea(int x, int y, int w, int h, int dx, int dy) {
1984: if (w <= 0 || h <= 0) {
1985: return;
1986: }
1987: SurfaceData theData = surfaceData;
1988: if (theData.copyArea(this , x, y, w, h, dx, dy)) {
1989: return;
1990: }
1991: if (transformState >= TRANSFORM_TRANSLATESCALE) {
1992: throw new InternalError(
1993: "transformed copyArea not implemented yet");
1994: }
1995: // REMIND: This method does not deal with missing data from the
1996: // source object (i.e. it does not send exposure events...)
1997:
1998: Region clip = getCompClip();
1999:
2000: Composite comp = composite;
2001: if (lastCAcomp != comp) {
2002: SurfaceType dsttype = theData.getSurfaceType();
2003: CompositeType comptype = imageComp;
2004: if (CompositeType.SrcOverNoEa.equals(comptype)
2005: && theData.getTransparency() == Transparency.OPAQUE) {
2006: comptype = CompositeType.SrcNoEa;
2007: }
2008: lastCAblit = Blit.locate(dsttype, comptype, dsttype);
2009: lastCAcomp = comp;
2010: }
2011:
2012: x += transX;
2013: y += transY;
2014:
2015: Blit ob = lastCAblit;
2016: if (dy == 0 && dx > 0 && dx < w) {
2017: while (w > 0) {
2018: int partW = Math.min(w, dx);
2019: w -= partW;
2020: int sx = x + w;
2021: ob.Blit(theData, theData, comp, clip, sx, y, sx + dx, y
2022: + dy, partW, h);
2023: }
2024: return;
2025: }
2026: if (dy > 0 && dy < h && dx > -w && dx < w) {
2027: while (h > 0) {
2028: int partH = Math.min(h, dy);
2029: h -= partH;
2030: int sy = y + h;
2031: ob.Blit(theData, theData, comp, clip, x, sy, x + dx, sy
2032: + dy, w, partH);
2033: }
2034: return;
2035: }
2036: ob.Blit(theData, theData, comp, clip, x, y, x + dx, y + dy, w,
2037: h);
2038: }
2039:
2040: /*
2041: public void XcopyArea(int x, int y, int w, int h, int dx, int dy) {
2042: Rectangle rect = new Rectangle(x, y, w, h);
2043: rect = transformBounds(rect, transform);
2044: Point2D point = new Point2D.Float(dx, dy);
2045: Point2D root = new Point2D.Float(0, 0);
2046: point = transform.transform(point, point);
2047: root = transform.transform(root, root);
2048: int fdx = (int)(point.getX()-root.getX());
2049: int fdy = (int)(point.getY()-root.getY());
2050:
2051: Rectangle r = getCompBounds().intersection(rect.getBounds());
2052:
2053: if (r.isEmpty()) {
2054: return;
2055: }
2056:
2057: // Begin Rasterizer for Clip Shape
2058: boolean skipClip = true;
2059: byte[] clipAlpha = null;
2060:
2061: if (clipState == CLIP_SHAPE) {
2062:
2063: int box[] = new int[4];
2064:
2065: clipRegion.getBounds(box);
2066: Rectangle devR = new Rectangle(box[0], box[1],
2067: box[2] - box[0],
2068: box[3] - box[1]);
2069: if (!devR.isEmpty()) {
2070: OutputManager mgr = getOutputManager();
2071: RegionIterator ri = clipRegion.getIterator();
2072: while (ri.nextYRange(box)) {
2073: int spany = box[1];
2074: int spanh = box[3] - spany;
2075: while (ri.nextXBand(box)) {
2076: int spanx = box[0];
2077: int spanw = box[2] - spanx;
2078: mgr.copyArea(this, null,
2079: spanw, 0,
2080: spanx, spany,
2081: spanw, spanh,
2082: fdx, fdy,
2083: null);
2084: }
2085: }
2086: }
2087: return;
2088: }
2089: // End Rasterizer for Clip Shape
2090:
2091: getOutputManager().copyArea(this, null,
2092: r.width, 0,
2093: r.x, r.y, r.width,
2094: r.height, fdx, fdy,
2095: null);
2096: }
2097: */
2098:
2099: public void drawLine(int x1, int y1, int x2, int y2) {
2100: try {
2101: drawpipe.drawLine(this , x1, y1, x2, y2);
2102: } catch (InvalidPipeException e) {
2103: revalidateAll();
2104: try {
2105: drawpipe.drawLine(this , x1, y1, x2, y2);
2106: } catch (InvalidPipeException e2) {
2107: // Still catching the exception; we are not yet ready to
2108: // validate the surfaceData correctly. Fail for now and
2109: // try again next time around.
2110: }
2111: } finally {
2112: surfaceData.markDirty();
2113: }
2114: }
2115:
2116: public void drawRoundRect(int x, int y, int w, int h, int arcW,
2117: int arcH) {
2118: try {
2119: drawpipe.drawRoundRect(this , x, y, w, h, arcW, arcH);
2120: } catch (InvalidPipeException e) {
2121: revalidateAll();
2122: try {
2123: drawpipe.drawRoundRect(this , x, y, w, h, arcW, arcH);
2124: } catch (InvalidPipeException e2) {
2125: // Still catching the exception; we are not yet ready to
2126: // validate the surfaceData correctly. Fail for now and
2127: // try again next time around.
2128: }
2129: } finally {
2130: surfaceData.markDirty();
2131: }
2132: }
2133:
2134: public void fillRoundRect(int x, int y, int w, int h, int arcW,
2135: int arcH) {
2136: try {
2137: fillpipe.fillRoundRect(this , x, y, w, h, arcW, arcH);
2138: } catch (InvalidPipeException e) {
2139: revalidateAll();
2140: try {
2141: fillpipe.fillRoundRect(this , x, y, w, h, arcW, arcH);
2142: } catch (InvalidPipeException e2) {
2143: // Still catching the exception; we are not yet ready to
2144: // validate the surfaceData correctly. Fail for now and
2145: // try again next time around.
2146: }
2147: } finally {
2148: surfaceData.markDirty();
2149: }
2150: }
2151:
2152: public void drawOval(int x, int y, int w, int h) {
2153: try {
2154: drawpipe.drawOval(this , x, y, w, h);
2155: } catch (InvalidPipeException e) {
2156: revalidateAll();
2157: try {
2158: drawpipe.drawOval(this , x, y, w, h);
2159: } catch (InvalidPipeException e2) {
2160: // Still catching the exception; we are not yet ready to
2161: // validate the surfaceData correctly. Fail for now and
2162: // try again next time around.
2163: }
2164: } finally {
2165: surfaceData.markDirty();
2166: }
2167: }
2168:
2169: public void fillOval(int x, int y, int w, int h) {
2170: try {
2171: fillpipe.fillOval(this , x, y, w, h);
2172: } catch (InvalidPipeException e) {
2173: revalidateAll();
2174: try {
2175: fillpipe.fillOval(this , x, y, w, h);
2176: } catch (InvalidPipeException e2) {
2177: // Still catching the exception; we are not yet ready to
2178: // validate the surfaceData correctly. Fail for now and
2179: // try again next time around.
2180: }
2181: } finally {
2182: surfaceData.markDirty();
2183: }
2184: }
2185:
2186: public void drawArc(int x, int y, int w, int h, int startAngl,
2187: int arcAngl) {
2188: try {
2189: drawpipe.drawArc(this , x, y, w, h, startAngl, arcAngl);
2190: } catch (InvalidPipeException e) {
2191: revalidateAll();
2192: try {
2193: drawpipe.drawArc(this , x, y, w, h, startAngl, arcAngl);
2194: } catch (InvalidPipeException e2) {
2195: // Still catching the exception; we are not yet ready to
2196: // validate the surfaceData correctly. Fail for now and
2197: // try again next time around.
2198: }
2199: } finally {
2200: surfaceData.markDirty();
2201: }
2202: }
2203:
2204: public void fillArc(int x, int y, int w, int h, int startAngl,
2205: int arcAngl) {
2206: try {
2207: fillpipe.fillArc(this , x, y, w, h, startAngl, arcAngl);
2208: } catch (InvalidPipeException e) {
2209: revalidateAll();
2210: try {
2211: fillpipe.fillArc(this , x, y, w, h, startAngl, arcAngl);
2212: } catch (InvalidPipeException e2) {
2213: // Still catching the exception; we are not yet ready to
2214: // validate the surfaceData correctly. Fail for now and
2215: // try again next time around.
2216: }
2217: } finally {
2218: surfaceData.markDirty();
2219: }
2220: }
2221:
2222: public void drawPolyline(int xPoints[], int yPoints[], int nPoints) {
2223: try {
2224: drawpipe.drawPolyline(this , xPoints, yPoints, nPoints);
2225: } catch (InvalidPipeException e) {
2226: revalidateAll();
2227: try {
2228: drawpipe.drawPolyline(this , xPoints, yPoints, nPoints);
2229: } catch (InvalidPipeException e2) {
2230: // Still catching the exception; we are not yet ready to
2231: // validate the surfaceData correctly. Fail for now and
2232: // try again next time around.
2233: }
2234: } finally {
2235: surfaceData.markDirty();
2236: }
2237: }
2238:
2239: public void drawPolygon(int xPoints[], int yPoints[], int nPoints) {
2240: try {
2241: drawpipe.drawPolygon(this , xPoints, yPoints, nPoints);
2242: } catch (InvalidPipeException e) {
2243: revalidateAll();
2244: try {
2245: drawpipe.drawPolygon(this , xPoints, yPoints, nPoints);
2246: } catch (InvalidPipeException e2) {
2247: // Still catching the exception; we are not yet ready to
2248: // validate the surfaceData correctly. Fail for now and
2249: // try again next time around.
2250: }
2251: } finally {
2252: surfaceData.markDirty();
2253: }
2254: }
2255:
2256: public void fillPolygon(int xPoints[], int yPoints[], int nPoints) {
2257: try {
2258: fillpipe.fillPolygon(this , xPoints, yPoints, nPoints);
2259: } catch (InvalidPipeException e) {
2260: revalidateAll();
2261: try {
2262: fillpipe.fillPolygon(this , xPoints, yPoints, nPoints);
2263: } catch (InvalidPipeException e2) {
2264: // Still catching the exception; we are not yet ready to
2265: // validate the surfaceData correctly. Fail for now and
2266: // try again next time around.
2267: }
2268: } finally {
2269: surfaceData.markDirty();
2270: }
2271: }
2272:
2273: public void drawRect(int x, int y, int w, int h) {
2274: try {
2275: drawpipe.drawRect(this , x, y, w, h);
2276: } catch (InvalidPipeException e) {
2277: revalidateAll();
2278: try {
2279: drawpipe.drawRect(this , x, y, w, h);
2280: } catch (InvalidPipeException e2) {
2281: // Still catching the exception; we are not yet ready to
2282: // validate the surfaceData correctly. Fail for now and
2283: // try again next time around.
2284: }
2285: } finally {
2286: surfaceData.markDirty();
2287: }
2288: }
2289:
2290: public void fillRect(int x, int y, int w, int h) {
2291: try {
2292: fillpipe.fillRect(this , x, y, w, h);
2293: } catch (InvalidPipeException e) {
2294: revalidateAll();
2295: try {
2296: fillpipe.fillRect(this , x, y, w, h);
2297: } catch (InvalidPipeException e2) {
2298: // Still catching the exception; we are not yet ready to
2299: // validate the surfaceData correctly. Fail for now and
2300: // try again next time around.
2301: }
2302: } finally {
2303: surfaceData.markDirty();
2304: }
2305: }
2306:
2307: private void revalidateAll() {
2308: try {
2309: // REMIND: This locking needs to be done around the
2310: // caller of this method so that the pipe stays valid
2311: // long enough to call the new primitive.
2312: // REMIND: No locking yet in screen SurfaceData objects!
2313: // surfaceData.lock();
2314: surfaceData = surfaceData.getReplacement();
2315: if (surfaceData == null) {
2316: surfaceData = NullSurfaceData.theInstance;
2317: }
2318:
2319: // this will recalculate the composite clip
2320: setDevClip(surfaceData.getBounds());
2321:
2322: if (paintState <= PAINT_ALPHACOLOR) {
2323: validateColor();
2324: }
2325: if (composite instanceof XORComposite) {
2326: Color c = ((XORComposite) composite).getXorColor();
2327: setComposite(new XORComposite(c, surfaceData));
2328: }
2329: validatePipe();
2330: } finally {
2331: // REMIND: No locking yet in screen SurfaceData objects!
2332: // surfaceData.unlock();
2333: }
2334: }
2335:
2336: public void clearRect(int x, int y, int w, int h) {
2337: // REMIND: has some "interesting" consequences if threads are
2338: // not synchronized
2339: Composite c = composite;
2340: Paint p = paint;
2341: setComposite(AlphaComposite.Src);
2342: setColor(getBackground());
2343: validatePipe();
2344: fillRect(x, y, w, h);
2345: setPaint(p);
2346: setComposite(c);
2347: }
2348:
2349: /**
2350: * Strokes the outline of a Path using the settings of the current
2351: * graphics state. The rendering attributes applied include the
2352: * clip, transform, paint or color, composite and stroke attributes.
2353: * @param p The path to be drawn.
2354: * @see #setStroke
2355: * @see #setPaint
2356: * @see java.awt.Graphics#setColor
2357: * @see #transform
2358: * @see #setTransform
2359: * @see #clip
2360: * @see #setClip
2361: * @see #setComposite
2362: */
2363: public void draw(Shape s) {
2364: try {
2365: shapepipe.draw(this , s);
2366: } catch (InvalidPipeException e) {
2367: revalidateAll();
2368: try {
2369: shapepipe.draw(this , s);
2370: } catch (InvalidPipeException e2) {
2371: // Still catching the exception; we are not yet ready to
2372: // validate the surfaceData correctly. Fail for now and
2373: // try again next time around.
2374: }
2375: } finally {
2376: surfaceData.markDirty();
2377: }
2378: }
2379:
2380: /**
2381: * Fills the interior of a Path using the settings of the current
2382: * graphics state. The rendering attributes applied include the
2383: * clip, transform, paint or color, and composite.
2384: * @see #setPaint
2385: * @see java.awt.Graphics#setColor
2386: * @see #transform
2387: * @see #setTransform
2388: * @see #setComposite
2389: * @see #clip
2390: * @see #setClip
2391: */
2392: public void fill(Shape s) {
2393: try {
2394: shapepipe.fill(this , s);
2395: } catch (InvalidPipeException e) {
2396: revalidateAll();
2397: try {
2398: shapepipe.fill(this , s);
2399: } catch (InvalidPipeException e2) {
2400: // Still catching the exception; we are not yet ready to
2401: // validate the surfaceData correctly. Fail for now and
2402: // try again next time around.
2403: }
2404: } finally {
2405: surfaceData.markDirty();
2406: }
2407: }
2408:
2409: /**
2410: * Returns true if the given AffineTransform is an integer
2411: * translation.
2412: */
2413: private static boolean isIntegerTranslation(AffineTransform xform) {
2414: if (xform.isIdentity()) {
2415: return true;
2416: }
2417: if (xform.getType() == AffineTransform.TYPE_TRANSLATION) {
2418: double tx = xform.getTranslateX();
2419: double ty = xform.getTranslateY();
2420: return (tx == (int) tx && ty == (int) ty);
2421: }
2422: return false;
2423: }
2424:
2425: /**
2426: * Returns the index of the tile corresponding to the supplied position
2427: * given the tile grid offset and size along the same axis.
2428: */
2429: private static int getTileIndex(int p, int tileGridOffset,
2430: int tileSize) {
2431: p -= tileGridOffset;
2432: if (p < 0) {
2433: p += 1 - tileSize; // force round to -infinity (ceiling)
2434: }
2435: return p / tileSize;
2436: }
2437:
2438: /**
2439: * Returns a rectangle in image coordinates that may be required
2440: * in order to draw the given image into the given clipping region
2441: * through a pair of AffineTransforms. In addition, horizontal and
2442: * vertical padding factors for antialising and interpolation may
2443: * be used.
2444: */
2445: private static Rectangle getImageRegion(RenderedImage img,
2446: Region compClip, AffineTransform transform,
2447: AffineTransform xform, int padX, int padY) {
2448: Rectangle imageRect = new Rectangle(img.getMinX(), img
2449: .getMinY(), img.getWidth(), img.getHeight());
2450:
2451: Rectangle result = null;
2452: try {
2453: double p[] = new double[8];
2454: p[0] = p[2] = compClip.getLoX();
2455: p[4] = p[6] = compClip.getHiX();
2456: p[1] = p[5] = compClip.getLoY();
2457: p[3] = p[7] = compClip.getHiY();
2458:
2459: // Inverse transform the output bounding rect
2460: transform.inverseTransform(p, 0, p, 0, 4);
2461: xform.inverseTransform(p, 0, p, 0, 4);
2462:
2463: // Determine a bounding box for the inverse transformed region
2464: double x0, x1, y0, y1;
2465: x0 = x1 = p[0];
2466: y0 = y1 = p[1];
2467:
2468: for (int i = 2; i < 8;) {
2469: double pt = p[i++];
2470: if (pt < x0) {
2471: x0 = pt;
2472: } else if (pt > x1) {
2473: x1 = pt;
2474: }
2475: pt = p[i++];
2476: if (pt < y0) {
2477: y0 = pt;
2478: } else if (pt > y1) {
2479: y1 = pt;
2480: }
2481: }
2482:
2483: // This is padding for anti-aliasing and such. It may
2484: // be more than is needed.
2485: int x = (int) x0 - padX;
2486: int w = (int) (x1 - x0 + 2 * padX);
2487: int y = (int) y0 - padY;
2488: int h = (int) (y1 - y0 + 2 * padY);
2489:
2490: Rectangle clipRect = new Rectangle(x, y, w, h);
2491: result = clipRect.intersection(imageRect);
2492: } catch (NoninvertibleTransformException nte) {
2493: // Worst case bounds are the bounds of the image.
2494: result = imageRect;
2495: }
2496:
2497: return result;
2498: }
2499:
2500: /**
2501: * Draws an image, applying a transform from image space into user space
2502: * before drawing.
2503: * The transformation from user space into device space is done with
2504: * the current transform in the Graphics2D.
2505: * The given transformation is applied to the image before the
2506: * transform attribute in the Graphics2D state is applied.
2507: * The rendering attributes applied include the clip, transform,
2508: * and composite attributes. Note that the result is
2509: * undefined, if the given transform is noninvertible.
2510: * @param img The image to be drawn. Does nothing if img is null.
2511: * @param xform The transformation from image space into user space.
2512: * @see #transform
2513: * @see #setTransform
2514: * @see #setComposite
2515: * @see #clip
2516: * @see #setClip
2517: */
2518: public void drawRenderedImage(RenderedImage img,
2519: AffineTransform xform) {
2520:
2521: if (img == null) {
2522: return;
2523: }
2524:
2525: // BufferedImage case: use a simple drawImage call
2526: if (img instanceof BufferedImage) {
2527: BufferedImage bufImg = (BufferedImage) img;
2528: drawImage(bufImg, xform, null);
2529: return;
2530: }
2531:
2532: // transformState tracks the state of transform and
2533: // transX, transY contain the integer casts of the
2534: // translation factors
2535: boolean isIntegerTranslate = (transformState <= TRANSFORM_INT_TRANSLATE)
2536: && isIntegerTranslation(xform);
2537:
2538: // Include padding for interpolation/antialiasing if necessary
2539: int pad = isIntegerTranslate ? 0 : 3;
2540:
2541: // Determine the region of the image that may contribute to
2542: // the clipped drawing area
2543: Rectangle region = getImageRegion(img, getCompClip(),
2544: transform, xform, pad, pad);
2545: if (region.width <= 0 || region.height <= 0) {
2546: return;
2547: }
2548:
2549: // Attempt to optimize integer translation of tiled images.
2550: // Although theoretically we are O.K. if the concatenation of
2551: // the user transform and the device transform is an integer
2552: // translation, we'll play it safe and only optimize the case
2553: // where both are integer translations.
2554: if (isIntegerTranslate) {
2555: // Use optimized code
2556: // Note that drawTranslatedRenderedImage calls copyImage
2557: // which takes the user space to device space transform into
2558: // account, but we need to provide the image space to user space
2559: // translations.
2560:
2561: drawTranslatedRenderedImage(img, region, (int) xform
2562: .getTranslateX(), (int) xform.getTranslateY());
2563: return;
2564: }
2565:
2566: // General case: cobble the necessary region into a single Raster
2567: Raster raster = img.getData(region);
2568:
2569: // Make a new Raster with the same contents as raster
2570: // but starting at (0, 0). This raster is thus in the same
2571: // coordinate system as the SampleModel of the original raster.
2572: WritableRaster wRaster = Raster.createWritableRaster(raster
2573: .getSampleModel(), raster.getDataBuffer(), null);
2574:
2575: // If the original raster was in a different coordinate
2576: // system than its SampleModel, we need to perform an
2577: // additional translation in order to get the (minX, minY)
2578: // pixel of raster to be pixel (0, 0) of wRaster. We also
2579: // have to have the correct width and height.
2580: int minX = raster.getMinX();
2581: int minY = raster.getMinY();
2582: int width = raster.getWidth();
2583: int height = raster.getHeight();
2584: int px = minX - raster.getSampleModelTranslateX();
2585: int py = minY - raster.getSampleModelTranslateY();
2586: if (px != 0 || py != 0 || width != wRaster.getWidth()
2587: || height != wRaster.getHeight()) {
2588: wRaster = wRaster.createWritableChild(px, py, width,
2589: height, 0, 0, null);
2590: }
2591:
2592: // Now we have a BufferedImage starting at (0, 0)
2593: // with the same contents that started at (minX, minY)
2594: // in raster. So we must draw the BufferedImage with a
2595: // translation of (minX, minY).
2596: AffineTransform transXform = (AffineTransform) xform.clone();
2597: transXform.translate(minX, minY);
2598:
2599: ColorModel cm = img.getColorModel();
2600: BufferedImage bufImg = new BufferedImage(cm, wRaster, cm
2601: .isAlphaPremultiplied(), null);
2602: drawImage(bufImg, transXform, null);
2603: }
2604:
2605: /**
2606: * Intersects <code>destRect</code> with <code>clip</code> and
2607: * overwrites <code>destRect</code> with the result.
2608: * Returns false if the intersection was empty, true otherwise.
2609: */
2610: private boolean clipTo(Rectangle destRect, Rectangle clip) {
2611: int x1 = Math.max(destRect.x, clip.x);
2612: int x2 = Math.min(destRect.x + destRect.width, clip.x
2613: + clip.width);
2614: int y1 = Math.max(destRect.y, clip.y);
2615: int y2 = Math.min(destRect.y + destRect.height, clip.y
2616: + clip.height);
2617: if (((x2 - x1) < 0) || ((y2 - y1) < 0)) {
2618: destRect.width = -1; // Set both just to be safe
2619: destRect.height = -1;
2620: return false;
2621: } else {
2622: destRect.x = x1;
2623: destRect.y = y1;
2624: destRect.width = x2 - x1;
2625: destRect.height = y2 - y1;
2626: return true;
2627: }
2628: }
2629:
2630: /**
2631: * Draw a portion of a RenderedImage tile-by-tile with a given
2632: * integer image to user space translation. The user to
2633: * device transform must also be an integer translation.
2634: */
2635: private void drawTranslatedRenderedImage(RenderedImage img,
2636: Rectangle region, int i2uTransX, int i2uTransY) {
2637: // Cache tile grid info
2638: int tileGridXOffset = img.getTileGridXOffset();
2639: int tileGridYOffset = img.getTileGridYOffset();
2640: int tileWidth = img.getTileWidth();
2641: int tileHeight = img.getTileHeight();
2642:
2643: // Determine the tile index extrema in each direction
2644: int minTileX = getTileIndex(region.x, tileGridXOffset,
2645: tileWidth);
2646: int minTileY = getTileIndex(region.y, tileGridYOffset,
2647: tileHeight);
2648: int maxTileX = getTileIndex(region.x + region.width - 1,
2649: tileGridXOffset, tileWidth);
2650: int maxTileY = getTileIndex(region.y + region.height - 1,
2651: tileGridYOffset, tileHeight);
2652:
2653: // Create a single ColorModel to use for all BufferedImages
2654: ColorModel colorModel = img.getColorModel();
2655:
2656: // Reuse the same Rectangle for each iteration
2657: Rectangle tileRect = new Rectangle();
2658:
2659: for (int ty = minTileY; ty <= maxTileY; ty++) {
2660: for (int tx = minTileX; tx <= maxTileX; tx++) {
2661: // Get the current tile.
2662: Raster raster = img.getTile(tx, ty);
2663:
2664: // Fill in tileRect with the tile bounds
2665: tileRect.x = tx * tileWidth + tileGridXOffset;
2666: tileRect.y = ty * tileHeight + tileGridYOffset;
2667: tileRect.width = tileWidth;
2668: tileRect.height = tileHeight;
2669:
2670: // Clip the tile against the image bounds and
2671: // backwards mapped clip region
2672: // The result can't be empty
2673: clipTo(tileRect, region);
2674:
2675: // Create a WritableRaster containing the tile
2676: WritableRaster wRaster = null;
2677: if (raster instanceof WritableRaster) {
2678: wRaster = (WritableRaster) raster;
2679: } else {
2680: // Create a WritableRaster in the same coordinate system
2681: // as the original raster.
2682: wRaster = Raster.createWritableRaster(raster
2683: .getSampleModel(), raster.getDataBuffer(),
2684: null);
2685: }
2686:
2687: // Translate wRaster to start at (0, 0) and to contain
2688: // only the relevent portion of the tile
2689: wRaster = wRaster.createWritableChild(tileRect.x,
2690: tileRect.y, tileRect.width, tileRect.height, 0,
2691: 0, null);
2692:
2693: // Wrap wRaster in a BufferedImage
2694: BufferedImage bufImg = new BufferedImage(colorModel,
2695: wRaster, colorModel.isAlphaPremultiplied(),
2696: null);
2697: // Now we have a BufferedImage starting at (0, 0) that
2698: // represents data from a Raster starting at
2699: // (tileRect.x, tileRect.y). Additionally, it needs
2700: // to be translated by (i2uTransX, i2uTransY). We call
2701: // copyImage to draw just the region of interest
2702: // without needing to create a child image.
2703: copyImage(bufImg, tileRect.x + i2uTransX, tileRect.y
2704: + i2uTransY, 0, 0, tileRect.width,
2705: tileRect.height, null, null);
2706: }
2707: }
2708: }
2709:
2710: public void drawRenderableImage(RenderableImage img,
2711: AffineTransform xform) {
2712:
2713: if (img == null) {
2714: return;
2715: }
2716:
2717: AffineTransform pipeTransform = transform;
2718: AffineTransform concatTransform = new AffineTransform(xform);
2719: concatTransform.concatenate(pipeTransform);
2720: AffineTransform reverseTransform;
2721:
2722: RenderContext rc = new RenderContext(concatTransform);
2723:
2724: try {
2725: reverseTransform = pipeTransform.createInverse();
2726: } catch (NoninvertibleTransformException nte) {
2727: rc = new RenderContext(pipeTransform);
2728: reverseTransform = new AffineTransform();
2729: }
2730:
2731: RenderedImage rendering = img.createRendering(rc);
2732: drawRenderedImage(rendering, reverseTransform);
2733: }
2734:
2735: /*
2736: * Transform the bounding box of the BufferedImage
2737: */
2738: protected Rectangle transformBounds(Rectangle rect,
2739: AffineTransform tx) {
2740: if (tx.isIdentity()) {
2741: return rect;
2742: }
2743:
2744: Shape s = transformShape(tx, rect);
2745: return s.getBounds();
2746: }
2747:
2748: // text rendering methods
2749: public void drawString(String str, int x, int y) {
2750: if (str == null) {
2751: throw new NullPointerException("String is null");
2752: }
2753:
2754: if (font.hasLayoutAttributes()) {
2755: new TextLayout(str, font, getFontRenderContext()).draw(
2756: this , x, y);
2757: return;
2758: }
2759:
2760: try {
2761: textpipe.drawString(this , str, x, y);
2762: } catch (InvalidPipeException e) {
2763: revalidateAll();
2764: try {
2765: textpipe.drawString(this , str, x, y);
2766: } catch (InvalidPipeException e2) {
2767: // Still catching the exception; we are not yet ready to
2768: // validate the surfaceData correctly. Fail for now and
2769: // try again next time around.
2770: }
2771: } finally {
2772: surfaceData.markDirty();
2773: }
2774: }
2775:
2776: public void drawString(String str, float x, float y) {
2777: if (str == null) {
2778: throw new NullPointerException("String is null");
2779: }
2780:
2781: if (font.hasLayoutAttributes()) {
2782: new TextLayout(str, font, getFontRenderContext()).draw(
2783: this , x, y);
2784: return;
2785: }
2786:
2787: try {
2788: textpipe.drawString(this , str, x, y);
2789: } catch (InvalidPipeException e) {
2790: revalidateAll();
2791: try {
2792: textpipe.drawString(this , str, x, y);
2793: } catch (InvalidPipeException e2) {
2794: // Still catching the exception; we are not yet ready to
2795: // validate the surfaceData correctly. Fail for now and
2796: // try again next time around.
2797: }
2798: } finally {
2799: surfaceData.markDirty();
2800: }
2801: }
2802:
2803: public void drawString(AttributedCharacterIterator iterator, int x,
2804: int y) {
2805: if (iterator == null) {
2806: throw new NullPointerException(
2807: "AttributedCharacterIterator is null");
2808: }
2809: TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2810: tl.draw(this , (float) x, (float) y);
2811: }
2812:
2813: public void drawString(AttributedCharacterIterator iterator,
2814: float x, float y) {
2815: if (iterator == null) {
2816: throw new NullPointerException(
2817: "AttributedCharacterIterator is null");
2818: }
2819: TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2820: tl.draw(this , x, y);
2821: }
2822:
2823: public void drawGlyphVector(GlyphVector gv, float x, float y) {
2824: if (gv == null) {
2825: throw new NullPointerException("GlyphVector is null");
2826: }
2827:
2828: try {
2829: textpipe.drawGlyphVector(this , gv, x, y);
2830: } catch (InvalidPipeException e) {
2831: revalidateAll();
2832: try {
2833: textpipe.drawGlyphVector(this , gv, x, y);
2834: } catch (InvalidPipeException e2) {
2835: // Still catching the exception; we are not yet ready to
2836: // validate the surfaceData correctly. Fail for now and
2837: // try again next time around.
2838: }
2839: } finally {
2840: surfaceData.markDirty();
2841: }
2842: }
2843:
2844: public void drawChars(char data[], int offset, int length, int x,
2845: int y) {
2846:
2847: if (data == null) {
2848: throw new NullPointerException("char data is null");
2849: }
2850: if (offset < 0 || length < 0 || offset + length > data.length) {
2851: throw new ArrayIndexOutOfBoundsException(
2852: "bad offset/length");
2853: }
2854: if (font.hasLayoutAttributes()) {
2855: new TextLayout(new String(data, offset, length), font,
2856: getFontRenderContext()).draw(this , x, y);
2857: return;
2858: }
2859:
2860: try {
2861: textpipe.drawChars(this , data, offset, length, x, y);
2862: } catch (InvalidPipeException e) {
2863: revalidateAll();
2864: try {
2865: textpipe.drawChars(this , data, offset, length, x, y);
2866: } catch (InvalidPipeException e2) {
2867: // Still catching the exception; we are not yet ready to
2868: // validate the surfaceData correctly. Fail for now and
2869: // try again next time around.
2870: }
2871: } finally {
2872: surfaceData.markDirty();
2873: }
2874: }
2875:
2876: public void drawBytes(byte data[], int offset, int length, int x,
2877: int y) {
2878: if (data == null) {
2879: throw new NullPointerException("byte data is null");
2880: }
2881: if (offset < 0 || length < 0 || offset + length > data.length) {
2882: throw new ArrayIndexOutOfBoundsException(
2883: "bad offset/length");
2884: }
2885: /* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */
2886: char chData[] = new char[length];
2887: for (int i = length; i-- > 0;) {
2888: chData[i] = (char) (data[i + offset] & 0xff);
2889: }
2890: if (font.hasLayoutAttributes()) {
2891: new TextLayout(new String(chData), font,
2892: getFontRenderContext()).draw(this , x, y);
2893: return;
2894: }
2895:
2896: try {
2897: textpipe.drawChars(this , chData, 0, length, x, y);
2898: } catch (InvalidPipeException e) {
2899: revalidateAll();
2900: try {
2901: textpipe.drawChars(this , chData, 0, length, x, y);
2902: } catch (InvalidPipeException e2) {
2903: // Still catching the exception; we are not yet ready to
2904: // validate the surfaceData correctly. Fail for now and
2905: // try again next time around.
2906: }
2907: } finally {
2908: surfaceData.markDirty();
2909: }
2910: }
2911:
2912: // end of text rendering methods
2913:
2914: /**
2915: * Draws an image scaled to x,y,w,h in nonblocking mode with a
2916: * callback object.
2917: */
2918: public boolean drawImage(Image img, int x, int y, int width,
2919: int height, ImageObserver observer) {
2920: return drawImage(img, x, y, width, height, null, observer);
2921: }
2922:
2923: /**
2924: * Not part of the advertised API but a useful utility method
2925: * to call internally. This is for the case where we are
2926: * drawing to/from given coordinates using a given width/height,
2927: * but we guarantee that the weidth/height of the src and dest
2928: * areas are equal (no scale needed).
2929: */
2930: public boolean copyImage(Image img, int dx, int dy, int sx, int sy,
2931: int width, int height, Color bgcolor, ImageObserver observer) {
2932: try {
2933: return imagepipe.copyImage(this , img, dx, dy, sx, sy,
2934: width, height, bgcolor, observer);
2935: } catch (InvalidPipeException e) {
2936: revalidateAll();
2937: try {
2938: return imagepipe.copyImage(this , img, dx, dy, sx, sy,
2939: width, height, bgcolor, observer);
2940: } catch (InvalidPipeException e2) {
2941: // Still catching the exception; we are not yet ready to
2942: // validate the surfaceData correctly. Fail for now and
2943: // try again next time around.
2944: return false;
2945: }
2946: } finally {
2947: surfaceData.markDirty();
2948: }
2949: }
2950:
2951: /**
2952: * Draws an image scaled to x,y,w,h in nonblocking mode with a
2953: * solid background color and a callback object.
2954: */
2955: public boolean drawImage(Image img, int x, int y, int width,
2956: int height, Color bg, ImageObserver observer) {
2957:
2958: if (img == null) {
2959: return true;
2960: }
2961:
2962: if ((width == 0) || (height == 0)) {
2963: return true;
2964: }
2965: if (width == img.getWidth(null)
2966: && height == img.getHeight(null)) {
2967: return copyImage(img, x, y, 0, 0, width, height, bg,
2968: observer);
2969: }
2970:
2971: try {
2972: return imagepipe.scaleImage(this , img, x, y, width, height,
2973: bg, observer);
2974: } catch (InvalidPipeException e) {
2975: revalidateAll();
2976: try {
2977: return imagepipe.scaleImage(this , img, x, y, width,
2978: height, bg, observer);
2979: } catch (InvalidPipeException e2) {
2980: // Still catching the exception; we are not yet ready to
2981: // validate the surfaceData correctly. Fail for now and
2982: // try again next time around.
2983: return false;
2984: }
2985: } finally {
2986: surfaceData.markDirty();
2987: }
2988: }
2989:
2990: /**
2991: * Draws an image at x,y in nonblocking mode.
2992: */
2993: public boolean drawImage(Image img, int x, int y,
2994: ImageObserver observer) {
2995: return drawImage(img, x, y, null, observer);
2996: }
2997:
2998: /**
2999: * Draws an image at x,y in nonblocking mode with a solid background
3000: * color and a callback object.
3001: */
3002: public boolean drawImage(Image img, int x, int y, Color bg,
3003: ImageObserver observer) {
3004:
3005: if (img == null) {
3006: return true;
3007: }
3008:
3009: try {
3010: return imagepipe.copyImage(this , img, x, y, bg, observer);
3011: } catch (InvalidPipeException e) {
3012: revalidateAll();
3013: try {
3014: return imagepipe.copyImage(this , img, x, y, bg,
3015: observer);
3016: } catch (InvalidPipeException e2) {
3017: // Still catching the exception; we are not yet ready to
3018: // validate the surfaceData correctly. Fail for now and
3019: // try again next time around.
3020: return false;
3021: }
3022: } finally {
3023: surfaceData.markDirty();
3024: }
3025: }
3026:
3027: /**
3028: * Draws a subrectangle of an image scaled to a destination rectangle
3029: * in nonblocking mode with a callback object.
3030: */
3031: public boolean drawImage(Image img, int dx1, int dy1, int dx2,
3032: int dy2, int sx1, int sy1, int sx2, int sy2,
3033: ImageObserver observer) {
3034: return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2,
3035: null, observer);
3036: }
3037:
3038: /**
3039: * Draws a subrectangle of an image scaled to a destination rectangle in
3040: * nonblocking mode with a solid background color and a callback object.
3041: */
3042: public boolean drawImage(Image img, int dx1, int dy1, int dx2,
3043: int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor,
3044: ImageObserver observer) {
3045:
3046: if (img == null) {
3047: return true;
3048: }
3049:
3050: if (dx1 == dx2 || dy1 == dy2 || sx1 == sx2 || sy1 == sy2) {
3051: return true;
3052: }
3053:
3054: if (((sx2 - sx1) == (dx2 - dx1))
3055: && ((sy2 - sy1) == (dy2 - dy1))) {
3056: // Not a scale - forward it to a copy routine
3057: int srcX, srcY, dstX, dstY, width, height;
3058: if (sx2 > sx1) {
3059: width = sx2 - sx1;
3060: srcX = sx1;
3061: dstX = dx1;
3062: } else {
3063: width = sx1 - sx2;
3064: srcX = sx2;
3065: dstX = dx2;
3066: }
3067: if (sy2 > sy1) {
3068: height = sy2 - sy1;
3069: srcY = sy1;
3070: dstY = dy1;
3071: } else {
3072: height = sy1 - sy2;
3073: srcY = sy2;
3074: dstY = dy2;
3075: }
3076: return copyImage(img, dstX, dstY, srcX, srcY, width,
3077: height, bgcolor, observer);
3078: }
3079:
3080: try {
3081: return imagepipe.scaleImage(this , img, dx1, dy1, dx2, dy2,
3082: sx1, sy1, sx2, sy2, bgcolor, observer);
3083: } catch (InvalidPipeException e) {
3084: revalidateAll();
3085: try {
3086: return imagepipe.scaleImage(this , img, dx1, dy1, dx2,
3087: dy2, sx1, sy1, sx2, sy2, bgcolor, observer);
3088: } catch (InvalidPipeException e2) {
3089: // Still catching the exception; we are not yet ready to
3090: // validate the surfaceData correctly. Fail for now and
3091: // try again next time around.
3092: return false;
3093: }
3094: } finally {
3095: surfaceData.markDirty();
3096: }
3097: }
3098:
3099: /**
3100: * Draw an image, applying a transform from image space into user space
3101: * before drawing.
3102: * The transformation from user space into device space is done with
3103: * the current transform in the Graphics2D.
3104: * The given transformation is applied to the image before the
3105: * transform attribute in the Graphics2D state is applied.
3106: * The rendering attributes applied include the clip, transform,
3107: * paint or color and composite attributes. Note that the result is
3108: * undefined, if the given transform is non-invertible.
3109: * @param img The image to be drawn.
3110: * @param xform The transformation from image space into user space.
3111: * @param observer The image observer to be notified on the image producing
3112: * progress.
3113: * @see #transform
3114: * @see #setComposite
3115: * @see #setClip
3116: */
3117: public boolean drawImage(Image img, AffineTransform xform,
3118: ImageObserver observer) {
3119:
3120: if (img == null) {
3121: return true;
3122: }
3123:
3124: if (xform == null || xform.isIdentity()) {
3125: return drawImage(img, 0, 0, null, observer);
3126: }
3127:
3128: try {
3129: return imagepipe.transformImage(this , img, xform, observer);
3130: } catch (InvalidPipeException e) {
3131: revalidateAll();
3132: try {
3133: return imagepipe.transformImage(this , img, xform,
3134: observer);
3135: } catch (InvalidPipeException e2) {
3136: // Still catching the exception; we are not yet ready to
3137: // validate the surfaceData correctly. Fail for now and
3138: // try again next time around.
3139: return false;
3140: }
3141: } finally {
3142: surfaceData.markDirty();
3143: }
3144: }
3145:
3146: public void drawImage(BufferedImage bImg, BufferedImageOp op,
3147: int x, int y) {
3148:
3149: if (bImg == null) {
3150: return;
3151: }
3152:
3153: try {
3154: imagepipe.transformImage(this , bImg, op, x, y);
3155: } catch (InvalidPipeException e) {
3156: revalidateAll();
3157: try {
3158: imagepipe.transformImage(this , bImg, op, x, y);
3159: } catch (InvalidPipeException e2) {
3160: // Still catching the exception; we are not yet ready to
3161: // validate the surfaceData correctly. Fail for now and
3162: // try again next time around.
3163: }
3164: } finally {
3165: surfaceData.markDirty();
3166: }
3167: }
3168:
3169: /**
3170: * Get the rendering context of the font
3171: * within this Graphics2D context.
3172: */
3173: public FontRenderContext getFontRenderContext() {
3174: if (cachedFRC == null) {
3175: int aahint = textAntialiasHint;
3176: if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT
3177: && antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
3178: aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
3179: }
3180: // Translation components should be excluded from the FRC transform
3181: AffineTransform tx = null;
3182: if (transformState >= TRANSFORM_TRANSLATESCALE) {
3183: if (transform.getTranslateX() == 0
3184: && transform.getTranslateY() == 0) {
3185: tx = transform;
3186: } else {
3187: tx = new AffineTransform(transform.getScaleX(),
3188: transform.getShearY(), transform
3189: .getShearX(),
3190: transform.getScaleY(), 0, 0);
3191: }
3192: }
3193: cachedFRC = new FontRenderContext(tx, SunHints.Value.get(
3194: SunHints.INTKEY_TEXT_ANTIALIASING, aahint),
3195: SunHints.Value.get(
3196: SunHints.INTKEY_FRACTIONALMETRICS,
3197: fractionalMetricsHint));
3198: }
3199: return cachedFRC;
3200: }
3201:
3202: private FontRenderContext cachedFRC;
3203:
3204: public void dispose() {
3205: surfaceData = NullSurfaceData.theInstance;
3206: invalidatePipe();
3207: }
3208:
3209: public void finalize() {
3210: }
3211:
3212: /**
3213: * Returns destination that this Graphics renders to. This could be
3214: * either an Image or a Component; subclasses of SurfaceData are
3215: * responsible for returning the appropriate object.
3216: */
3217: public Object getDestination() {
3218: return surfaceData.getDestination();
3219: }
3220: }
|