diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libjava/classpath/gnu/java/awt/java2d/AbstractGraphics2D.java | |
download | cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.bz2 cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libjava/classpath/gnu/java/awt/java2d/AbstractGraphics2D.java')
-rw-r--r-- | libjava/classpath/gnu/java/awt/java2d/AbstractGraphics2D.java | 2094 |
1 files changed, 2094 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/java/awt/java2d/AbstractGraphics2D.java b/libjava/classpath/gnu/java/awt/java2d/AbstractGraphics2D.java new file mode 100644 index 000000000..1334866f2 --- /dev/null +++ b/libjava/classpath/gnu/java/awt/java2d/AbstractGraphics2D.java @@ -0,0 +1,2094 @@ +/* AbstractGraphics2D.java -- Abstract Graphics2D implementation + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.awt.java2d; + +import gnu.java.util.LRUCache; + +import java.awt.AWTError; +import java.awt.AlphaComposite; +import java.awt.AWTPermission; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.CompositeContext; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Paint; +import java.awt.PaintContext; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.Toolkit; +import java.awt.RenderingHints.Key; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.FilteredImageSource; +import java.awt.image.ImageObserver; +import java.awt.image.ImageProducer; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.ReplicateScaleFilter; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.awt.image.renderable.RenderableImage; +import java.text.AttributedCharacterIterator; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * This is a 100% Java implementation of the Java2D rendering pipeline. It is + * meant as a base class for Graphics2D implementations. + * + * <h2>Backend interface</h2> + * <p> + * The backend must at the very least provide a Raster which the the rendering + * pipeline can paint into. This must be implemented in + * {@link #getDestinationRaster()}. For some backends that might be enough, like + * when the target surface can be directly access via the raster (like in + * BufferedImages). Other targets need some way to synchronize the raster with + * the surface, which can be achieved by implementing the + * {@link #updateRaster(Raster, int, int, int, int)} method, which always gets + * called after a chunk of data got painted into the raster. + * </p> + * <p>Alternativly the backend can provide a method for filling Shapes by + * overriding the protected method fillShape(). This can be accomplished + * by a polygon filling function of the backend. Keep in mind though that + * Shapes can be quite complex (i.e. non-convex and containing holes, etc) + * which is not supported by all polygon fillers. Also it must be noted + * that fillShape() is expected to handle painting and compositing as well as + * clipping and transformation. If your backend can't support this natively, + * then you can fallback to the implementation in this class. You'll need + * to provide a writable Raster then, see above.</p> + * <p>Another alternative is to implement fillScanline() which only requires + * the backend to be able to draw horizontal lines in device space, + * which is usually very cheap. + * The implementation should still handle painting and compositing, + * but no more clipping and transformation is required by the backend.</p> + * <p>The backend is free to provide implementations for the various raw* + * methods for optimized AWT 1.1 style painting of some primitives. This should + * accelerate painting of Swing greatly. When doing so, the backend must also + * keep track of the clip and translation, probably by overriding + * some clip and translate methods. Don't forget to message super in such a + * case.</p> + * + * <h2>Acceleration options</h2> + * <p> + * The fact that it is + * pure Java makes it a little slow. However, there are several ways of + * accelerating the rendering pipeline: + * <ol> + * <li><em>Optimization hooks for AWT 1.1 - like graphics operations.</em> + * The most important methods from the {@link java.awt.Graphics} class + * have a corresponding <code>raw*</code> method, which get called when + * several optimization conditions are fullfilled. These conditions are + * described below. Subclasses can override these methods and delegate + * it directly to a native backend.</li> + * <li><em>Native PaintContexts and CompositeContext.</em> The implementations + * for the 3 PaintContexts and AlphaCompositeContext can be accelerated + * using native code. These have proved to two of the most performance + * critical points in the rendering pipeline and cannot really be done quickly + * in plain Java because they involve lots of shuffling around with large + * arrays. In fact, you really would want to let the graphics card to the + * work, they are made for this.</li> + * <li>Provide an accelerated implementation for fillShape(). For instance, + * OpenGL can fill shapes very efficiently. There are some considerations + * to be made though, see above for details.</li> + * </ol> + * </p> + * + * @author Roman Kennke (kennke@aicas.com) + */ +public abstract class AbstractGraphics2D + extends Graphics2D + implements Cloneable, Pixelizer +{ + /** + * Caches scaled versions of an image. + * + * @see #drawImage(Image, int, int, int, int, ImageObserver) + */ + protected static final WeakHashMap<Image, HashMap<Dimension,Image>> imageCache = + new WeakHashMap<Image, HashMap<Dimension, Image>>(); + + /** + * Wether we use anti aliasing for rendering text by default or not. + */ + private static final boolean DEFAULT_TEXT_AA = + Boolean.getBoolean("gnu.java2d.default_text_aa"); + + /** + * The default font to use on the graphics object. + */ + private static final Font FONT = new Font("SansSerif", Font.PLAIN, 12); + + /** + * The size of the LRU cache used for caching GlyphVectors. + */ + private static final int GV_CACHE_SIZE = 50; + + /** + * Caches certain shapes to avoid massive creation of such Shapes in + * the various draw* and fill* methods. + */ + private static final ShapeCache shapeCache = new ShapeCache(); + + /** + * A pool of scanline converters. It is important to reuse scanline + * converters because they keep their datastructures in place. We pool them + * for use in multiple threads. + */ + private static final LinkedList<ScanlineConverter> scanlineConverters = + new LinkedList<ScanlineConverter>(); + + /** + * Caches glyph vectors for better drawing performance. + */ + private static final Map<TextCacheKey,GlyphVector> gvCache = + Collections.synchronizedMap(new LRUCache<TextCacheKey,GlyphVector>(GV_CACHE_SIZE)); + + /** + * This key is used to search in the gvCache without allocating a new + * key each time. + */ + private static final TextCacheKey searchTextKey = new TextCacheKey(); + + /** + * The transformation for this Graphics2D instance + */ + protected AffineTransform transform; + + /** + * The foreground. + */ + private Paint paint; + + /** + * The paint context during rendering. + */ + private PaintContext paintContext = null; + + /** + * The background. + */ + private Color background = Color.WHITE; + + /** + * Foreground color, as set by setColor. + */ + private Color foreground = Color.BLACK; + private boolean isForegroundColorNull = true; + + /** + * The current font. + */ + private Font font; + + /** + * The current composite setting. + */ + private Composite composite; + + /** + * The current stroke setting. + */ + private Stroke stroke; + + /** + * The current clip. This clip is in user coordinate space. + */ + private Shape clip; + + /** + * The rendering hints. + */ + private RenderingHints renderingHints; + + /** + * The raster of the destination surface. This is where the painting is + * performed. + */ + private WritableRaster destinationRaster; + + /** + * Indicates if certain graphics primitives can be rendered in an optimized + * fashion. This will be the case if the following conditions are met: + * - The transform may only be a translation, no rotation, shearing or + * scaling. + * - The paint must be a solid color. + * - The composite must be an AlphaComposite.SrcOver. + * - The clip must be a Rectangle. + * - The stroke must be a plain BasicStroke(). + * + * These conditions represent the standard settings of a new + * AbstractGraphics2D object and will be the most commonly used setting + * in Swing rendering and should therefore be optimized as much as possible. + */ + private boolean isOptimized = true; + + private static final BasicStroke STANDARD_STROKE = new BasicStroke(); + + private static final HashMap<Key, Object> STANDARD_HINTS; + static + { + + HashMap<Key, Object> hints = new HashMap<Key, Object>(); + hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); + hints.put(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_DEFAULT); + + STANDARD_HINTS = hints; + } + + /** + * Creates a new AbstractGraphics2D instance. + */ + protected AbstractGraphics2D() + { + transform = new AffineTransform(); + background = Color.WHITE; + composite = AlphaComposite.SrcOver; + stroke = STANDARD_STROKE; + renderingHints = new RenderingHints(STANDARD_HINTS); + } + + /** + * Draws the specified shape. The shape is passed through the current stroke + * and is then forwarded to {@link #fillShape}. + * + * @param shape the shape to draw + */ + public void draw(Shape shape) + { + // Stroke the shape. + Shape strokedShape = stroke.createStrokedShape(shape); + // Fill the stroked shape. + fillShape(strokedShape, false); + } + + + /** + * Draws the specified image and apply the transform for image space -> + * user space conversion. + * + * This method is implemented to special case RenderableImages and + * RenderedImages and delegate to + * {@link #drawRenderableImage(RenderableImage, AffineTransform)} and + * {@link #drawRenderedImage(RenderedImage, AffineTransform)} accordingly. + * Other image types are not yet handled. + * + * @param image the image to be rendered + * @param xform the transform from image space to user space + * @param obs the image observer to be notified + */ + public boolean drawImage(Image image, AffineTransform xform, + ImageObserver obs) + { + Rectangle areaOfInterest = new Rectangle(0, 0, image.getWidth(obs), + image.getHeight(obs)); + return drawImageImpl(image, xform, obs, areaOfInterest); + } + + /** + * Draws the specified image and apply the transform for image space -> + * user space conversion. This method only draw the part of the image + * specified by <code>areaOfInterest</code>. + * + * This method is implemented to special case RenderableImages and + * RenderedImages and delegate to + * {@link #drawRenderableImage(RenderableImage, AffineTransform)} and + * {@link #drawRenderedImage(RenderedImage, AffineTransform)} accordingly. + * Other image types are not yet handled. + * + * @param image the image to be rendered + * @param xform the transform from image space to user space + * @param obs the image observer to be notified + * @param areaOfInterest the area in image space that is rendered + */ + private boolean drawImageImpl(Image image, AffineTransform xform, + ImageObserver obs, Rectangle areaOfInterest) + { + boolean ret; + if (image == null) + { + ret = true; + } + else if (image instanceof RenderedImage) + { + // FIXME: Handle the ImageObserver. + drawRenderedImageImpl((RenderedImage) image, xform, areaOfInterest); + ret = true; + } + else if (image instanceof RenderableImage) + { + // FIXME: Handle the ImageObserver. + drawRenderableImageImpl((RenderableImage) image, xform, areaOfInterest); + ret = true; + } + else + { + // FIXME: Implement rendering of other Image types. + ret = false; + } + return ret; + } + + /** + * Renders a BufferedImage and applies the specified BufferedImageOp before + * to filter the BufferedImage somehow. The resulting BufferedImage is then + * passed on to {@link #drawRenderedImage(RenderedImage, AffineTransform)} + * to perform the final rendering. + * + * @param image the source buffered image + * @param op the filter to apply to the buffered image before rendering + * @param x the x coordinate to render the image to + * @param y the y coordinate to render the image to + */ + public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y) + { + BufferedImage filtered = + op.createCompatibleDestImage(image, image.getColorModel()); + AffineTransform t = new AffineTransform(); + t.translate(x, y); + drawRenderedImage(filtered, t); + } + + /** + * Renders the specified image to the destination raster. The specified + * transform is used to convert the image into user space. The transform + * of this AbstractGraphics2D object is used to transform from user space + * to device space. + * + * The rendering is performed using the scanline algorithm that performs the + * rendering of other shapes and a custom Paint implementation, that supplies + * the pixel values of the rendered image. + * + * @param image the image to render to the destination raster + * @param xform the transform from image space to user space + */ + public void drawRenderedImage(RenderedImage image, AffineTransform xform) + { + Rectangle areaOfInterest = new Rectangle(image.getMinX(), + image.getHeight(), + image.getWidth(), + image.getHeight()); + drawRenderedImageImpl(image, xform, areaOfInterest); + } + + /** + * Renders the specified image to the destination raster. The specified + * transform is used to convert the image into user space. The transform + * of this AbstractGraphics2D object is used to transform from user space + * to device space. Only the area specified by <code>areaOfInterest</code> + * is finally rendered to the target. + * + * The rendering is performed using the scanline algorithm that performs the + * rendering of other shapes and a custom Paint implementation, that supplies + * the pixel values of the rendered image. + * + * @param image the image to render to the destination raster + * @param xform the transform from image space to user space + */ + private void drawRenderedImageImpl(RenderedImage image, + AffineTransform xform, + Rectangle areaOfInterest) + { + // First we compute the transformation. This is made up of 3 parts: + // 1. The areaOfInterest -> image space transform. + // 2. The image space -> user space transform. + // 3. The user space -> device space transform. + AffineTransform t = new AffineTransform(); + t.translate(- areaOfInterest.x - image.getMinX(), + - areaOfInterest.y - image.getMinY()); + t.concatenate(xform); + t.concatenate(transform); + AffineTransform it = null; + try + { + it = t.createInverse(); + } + catch (NoninvertibleTransformException ex) + { + // Ignore -- we return if the transform is not invertible. + } + if (it != null) + { + // Transform the area of interest into user space. + GeneralPath aoi = new GeneralPath(areaOfInterest); + aoi.transform(xform); + // Render the shape using the standard renderer, but with a temporary + // ImagePaint. + ImagePaint p = new ImagePaint(image, it); + Paint savedPaint = paint; + try + { + paint = p; + fillShape(aoi, false); + } + finally + { + paint = savedPaint; + } + } + } + + /** + * Renders a renderable image. This produces a RenderedImage, which is + * then passed to {@link #drawRenderedImage(RenderedImage, AffineTransform)} + * to perform the final rendering. + * + * @param image the renderable image to be rendered + * @param xform the transform from image space to user space + */ + public void drawRenderableImage(RenderableImage image, AffineTransform xform) + { + Rectangle areaOfInterest = new Rectangle((int) image.getMinX(), + (int) image.getHeight(), + (int) image.getWidth(), + (int) image.getHeight()); + drawRenderableImageImpl(image, xform, areaOfInterest); + + } + + /** + * Renders a renderable image. This produces a RenderedImage, which is + * then passed to {@link #drawRenderedImage(RenderedImage, AffineTransform)} + * to perform the final rendering. Only the area of the image specified + * by <code>areaOfInterest</code> is rendered. + * + * @param image the renderable image to be rendered + * @param xform the transform from image space to user space + */ + private void drawRenderableImageImpl(RenderableImage image, + AffineTransform xform, + Rectangle areaOfInterest) + { + // TODO: Maybe make more clever usage of a RenderContext here. + RenderedImage rendered = image.createDefaultRendering(); + drawRenderedImageImpl(rendered, xform, areaOfInterest); + } + + /** + * Draws the specified string at the specified location. + * + * @param text the string to draw + * @param x the x location, relative to the bounding rectangle of the text + * @param y the y location, relative to the bounding rectangle of the text + */ + public void drawString(String text, int x, int y) + { + GlyphVector gv; + synchronized (searchTextKey) + { + TextCacheKey tck = searchTextKey; + FontRenderContext frc = getFontRenderContext(); + tck.setString(text); + tck.setFont(font); + tck.setFontRenderContext(frc); + if (gvCache.containsKey(tck)) + { + gv = gvCache.get(tck); + } + else + { + gv = font.createGlyphVector(frc, text.toCharArray()); + gvCache.put(new TextCacheKey(text, font, frc), gv); + } + } + drawGlyphVector(gv, x, y); + } + + /** + * Draws the specified string at the specified location. + * + * @param text the string to draw + * @param x the x location, relative to the bounding rectangle of the text + * @param y the y location, relative to the bounding rectangle of the text + */ + public void drawString(String text, float x, float y) + { + FontRenderContext ctx = getFontRenderContext(); + GlyphVector gv = font.createGlyphVector(ctx, text.toCharArray()); + drawGlyphVector(gv, x, y); + } + + /** + * Draws the specified string (as AttributedCharacterIterator) at the + * specified location. + * + * @param iterator the string to draw + * @param x the x location, relative to the bounding rectangle of the text + * @param y the y location, relative to the bounding rectangle of the text + */ + public void drawString(AttributedCharacterIterator iterator, int x, int y) + { + FontRenderContext ctx = getFontRenderContext(); + GlyphVector gv = font.createGlyphVector(ctx, iterator); + drawGlyphVector(gv, x, y); + } + + /** + * Draws the specified string (as AttributedCharacterIterator) at the + * specified location. + * + * @param iterator the string to draw + * @param x the x location, relative to the bounding rectangle of the text + * @param y the y location, relative to the bounding rectangle of the text + */ + public void drawString(AttributedCharacterIterator iterator, float x, float y) + { + FontRenderContext ctx = getFontRenderContext(); + GlyphVector gv = font.createGlyphVector(ctx, iterator); + drawGlyphVector(gv, x, y); + } + + /** + * Fills the specified shape with the current foreground. + * + * @param shape the shape to fill + */ + public void fill(Shape shape) + { + fillShape(shape, false); + } + + public boolean hit(Rectangle rect, Shape text, boolean onStroke) + { + // FIXME: Implement this. + throw new UnsupportedOperationException("Not yet implemented"); + } + + /** + * Sets the composite. + * + * @param comp the composite to set + */ + public void setComposite(Composite comp) + { + if (! (comp instanceof AlphaComposite)) + { + // FIXME: this check is only required "if this Graphics2D + // context is drawing to a Component on the display screen". + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(new AWTPermission("readDisplayPixels")); + } + + composite = comp; + if (! (comp.equals(AlphaComposite.SrcOver))) + isOptimized = false; + else + updateOptimization(); + } + + /** + * Sets the current foreground. + * + * @param p the foreground to set. + */ + public void setPaint(Paint p) + { + if (p != null) + { + paint = p; + + if (! (paint instanceof Color)) + { + isOptimized = false; + } + else + { + this.foreground = (Color) paint; + isForegroundColorNull = false; + updateOptimization(); + } + } + else + { + this.foreground = Color.BLACK; + isForegroundColorNull = true; + } + + // free resources if needed, then put the paint context to null + if (this.paintContext != null) + this.paintContext.dispose(); + + this.paintContext = null; + } + + /** + * Sets the stroke for this graphics object. + * + * @param s the stroke to set + */ + public void setStroke(Stroke s) + { + stroke = s; + if (! stroke.equals(new BasicStroke())) + isOptimized = false; + else + updateOptimization(); + } + + /** + * Sets the specified rendering hint. + * + * @param hintKey the key of the rendering hint + * @param hintValue the value + */ + public void setRenderingHint(Key hintKey, Object hintValue) + { + renderingHints.put(hintKey, hintValue); + } + + /** + * Returns the rendering hint for the specified key. + * + * @param hintKey the rendering hint key + * + * @return the rendering hint for the specified key + */ + public Object getRenderingHint(Key hintKey) + { + return renderingHints.get(hintKey); + } + + /** + * Sets the specified rendering hints. + * + * @param hints the rendering hints to set + */ + public void setRenderingHints(Map hints) + { + renderingHints.clear(); + renderingHints.putAll(hints); + } + + /** + * Adds the specified rendering hints. + * + * @param hints the rendering hints to add + */ + public void addRenderingHints(Map hints) + { + renderingHints.putAll(hints); + } + + /** + * Returns the current rendering hints. + * + * @return the current rendering hints + */ + public RenderingHints getRenderingHints() + { + return (RenderingHints) renderingHints.clone(); + } + + /** + * Translates the coordinate system by (x, y). + * + * @param x the translation X coordinate + * @param y the translation Y coordinate + */ + public void translate(int x, int y) + { + transform.translate(x, y); + + // Update the clip. We special-case rectangular clips here, because they + // are so common (e.g. in Swing). + if (clip != null) + { + if (clip instanceof Rectangle) + { + Rectangle r = (Rectangle) clip; + r.x -= x; + r.y -= y; + setClip(r); + } + else + { + AffineTransform clipTransform = new AffineTransform(); + clipTransform.translate(-x, -y); + updateClip(clipTransform); + } + } + } + + /** + * Translates the coordinate system by (tx, ty). + * + * @param tx the translation X coordinate + * @param ty the translation Y coordinate + */ + public void translate(double tx, double ty) + { + transform.translate(tx, ty); + + // Update the clip. We special-case rectangular clips here, because they + // are so common (e.g. in Swing). + if (clip != null) + { + if (clip instanceof Rectangle) + { + Rectangle r = (Rectangle) clip; + r.x -= tx; + r.y -= ty; + } + else + { + AffineTransform clipTransform = new AffineTransform(); + clipTransform.translate(-tx, -ty); + updateClip(clipTransform); + } + } + } + + /** + * Rotates the coordinate system by <code>theta</code> degrees. + * + * @param theta the angle be which to rotate the coordinate system + */ + public void rotate(double theta) + { + transform.rotate(theta); + if (clip != null) + { + AffineTransform clipTransform = new AffineTransform(); + clipTransform.rotate(-theta); + updateClip(clipTransform); + } + updateOptimization(); + } + + /** + * Rotates the coordinate system by <code>theta</code> around the point + * (x, y). + * + * @param theta the angle by which to rotate the coordinate system + * @param x the point around which to rotate, X coordinate + * @param y the point around which to rotate, Y coordinate + */ + public void rotate(double theta, double x, double y) + { + transform.rotate(theta, x, y); + if (clip != null) + { + AffineTransform clipTransform = new AffineTransform(); + clipTransform.rotate(-theta, x, y); + updateClip(clipTransform); + } + updateOptimization(); + } + + /** + * Scales the coordinate system by the factors <code>scaleX</code> and + * <code>scaleY</code>. + * + * @param scaleX the factor by which to scale the X axis + * @param scaleY the factor by which to scale the Y axis + */ + public void scale(double scaleX, double scaleY) + { + transform.scale(scaleX, scaleY); + if (clip != null) + { + AffineTransform clipTransform = new AffineTransform(); + clipTransform.scale(1 / scaleX, 1 / scaleY); + updateClip(clipTransform); + } + updateOptimization(); + } + + /** + * Shears the coordinate system by <code>shearX</code> and + * <code>shearY</code>. + * + * @param shearX the X shearing + * @param shearY the Y shearing + */ + public void shear(double shearX, double shearY) + { + transform.shear(shearX, shearY); + if (clip != null) + { + AffineTransform clipTransform = new AffineTransform(); + clipTransform.shear(-shearX, -shearY); + updateClip(clipTransform); + } + updateOptimization(); + } + + /** + * Transforms the coordinate system using the specified transform + * <code>t</code>. + * + * @param t the transform + */ + public void transform(AffineTransform t) + { + transform.concatenate(t); + try + { + AffineTransform clipTransform = t.createInverse(); + updateClip(clipTransform); + } + catch (NoninvertibleTransformException ex) + { + // TODO: How can we deal properly with this? + ex.printStackTrace(); + } + updateOptimization(); + } + + /** + * Sets the transformation for this Graphics object. + * + * @param t the transformation to set + */ + public void setTransform(AffineTransform t) + { + // Transform clip into target space using the old transform. + updateClip(transform); + transform.setTransform(t); + // Transform the clip back into user space using the inverse new transform. + try + { + updateClip(transform.createInverse()); + } + catch (NoninvertibleTransformException ex) + { + // TODO: How can we deal properly with this? + ex.printStackTrace(); + } + updateOptimization(); + } + + /** + * Returns the transformation of this coordinate system. + * + * @return the transformation of this coordinate system + */ + public AffineTransform getTransform() + { + return (AffineTransform) transform.clone(); + } + + /** + * Returns the current foreground. + * + * @return the current foreground + */ + public Paint getPaint() + { + return paint; + } + + + /** + * Returns the current composite. + * + * @return the current composite + */ + public Composite getComposite() + { + return composite; + } + + /** + * Sets the current background. + * + * @param color the background to set. + */ + public void setBackground(Color color) + { + background = color; + } + + /** + * Returns the current background. + * + * @return the current background + */ + public Color getBackground() + { + return background; + } + + /** + * Returns the current stroke. + * + * @return the current stroke + */ + public Stroke getStroke() + { + return stroke; + } + + /** + * Intersects the clip of this graphics object with the specified clip. + * + * @param s the clip with which the current clip should be intersected + */ + public void clip(Shape s) + { + // Initialize clip if not already present. + if (clip == null) + setClip(s); + + // This is so common, let's optimize this. + else if (clip instanceof Rectangle && s instanceof Rectangle) + { + Rectangle clipRect = (Rectangle) clip; + Rectangle r = (Rectangle) s; + computeIntersection(r.x, r.y, r.width, r.height, clipRect); + // Call setClip so that subclasses get notified. + setClip(clipRect); + } + else + { + Area current; + if (clip instanceof Area) + current = (Area) clip; + else + current = new Area(clip); + + Area intersect; + if (s instanceof Area) + intersect = (Area) s; + else + intersect = new Area(s); + + current.intersect(intersect); + clip = current; + isOptimized = false; + // Call setClip so that subclasses get notified. + setClip(clip); + } + } + + public FontRenderContext getFontRenderContext() + { + // Protect our own transform from beeing modified. + AffineTransform tf = new AffineTransform(transform); + // TODO: Determine antialias and fractionalmetrics parameters correctly. + return new FontRenderContext(tf, false, true); + } + + /** + * Draws the specified glyph vector at the specified location. + * + * @param gv the glyph vector to draw + * @param x the location, x coordinate + * @param y the location, y coordinate + */ + public void drawGlyphVector(GlyphVector gv, float x, float y) + { + translate(x, y); + fillShape(gv.getOutline(), true); + translate(-x, -y); + } + + /** + * Creates a copy of this graphics object. + * + * @return a copy of this graphics object + */ + public Graphics create() + { + AbstractGraphics2D copy = (AbstractGraphics2D) clone(); + return copy; + } + + /** + * Creates and returns a copy of this Graphics object. This should + * be overridden by subclasses if additional state must be handled when + * cloning. This is called by {@link #create()}. + * + * @return a copy of this Graphics object + */ + protected Object clone() + { + try + { + AbstractGraphics2D copy = (AbstractGraphics2D) super.clone(); + // Copy the clip. If it's a Rectangle, preserve that for optimization. + if (clip instanceof Rectangle) + copy.clip = new Rectangle((Rectangle) clip); + else if (clip != null) + copy.clip = new GeneralPath(clip); + else + copy.clip = null; + + copy.renderingHints = new RenderingHints(null); + copy.renderingHints.putAll(renderingHints); + copy.transform = new AffineTransform(transform); + // The remaining state is inmmutable and doesn't need to be copied. + return copy; + } + catch (CloneNotSupportedException ex) + { + AWTError err = new AWTError("Unexpected exception while cloning"); + err.initCause(ex); + throw err; + } + } + + /** + * Returns the current foreground. + */ + public Color getColor() + { + if (isForegroundColorNull) + return null; + + return this.foreground; + } + + /** + * Sets the current foreground. + * + * @param color the foreground to set + */ + public void setColor(Color color) + { + this.setPaint(color); + } + + public void setPaintMode() + { + // FIXME: Implement this. + throw new UnsupportedOperationException("Not yet implemented"); + } + + public void setXORMode(Color color) + { + // FIXME: Implement this. + throw new UnsupportedOperationException("Not yet implemented"); + } + + /** + * Returns the current font. + * + * @return the current font + */ + public Font getFont() + { + return font; + } + + /** + * Sets the font on this graphics object. When <code>f == null</code>, the + * current setting is not changed. + * + * @param f the font to set + */ + public void setFont(Font f) + { + if (f != null) + font = f; + } + + /** + * Returns the font metrics for the specified font. + * + * @param font the font for which to fetch the font metrics + * + * @return the font metrics for the specified font + */ + public FontMetrics getFontMetrics(Font font) + { + return Toolkit.getDefaultToolkit().getFontMetrics(font); + } + + /** + * Returns the bounds of the current clip. + * + * @return the bounds of the current clip + */ + public Rectangle getClipBounds() + { + Rectangle b = null; + if (clip != null) + b = clip.getBounds(); + return b; + } + + /** + * Intersects the current clipping region with the specified rectangle. + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param width the width of the rectangle + * @param height the height of the rectangle + */ + public void clipRect(int x, int y, int width, int height) + { + clip(new Rectangle(x, y, width, height)); + } + + /** + * Sets the clip to the specified rectangle. + * + * @param x the x coordinate of the clip rectangle + * @param y the y coordinate of the clip rectangle + * @param width the width of the clip rectangle + * @param height the height of the clip rectangle + */ + public void setClip(int x, int y, int width, int height) + { + setClip(new Rectangle(x, y, width, height)); + } + + /** + * Returns the current clip. + * + * @return the current clip + */ + public Shape getClip() + { + return clip; + } + + /** + * Sets the current clipping area to <code>clip</code>. + * + * @param c the clip to set + */ + public void setClip(Shape c) + { + clip = c; + if (! (clip instanceof Rectangle)) + isOptimized = false; + else + updateOptimization(); + } + + public void copyArea(int x, int y, int width, int height, int dx, int dy) + { + if (isOptimized) + rawCopyArea(x, y, width, height, dx, dy); + else + copyAreaImpl(x, y, width, height, dx, dy); + } + + /** + * Draws a line from (x1, y1) to (x2, y2). + * + * This implementation transforms the coordinates and forwards the call to + * {@link #rawDrawLine}. + */ + public void drawLine(int x1, int y1, int x2, int y2) + { + if (isOptimized) + { + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + rawDrawLine(x1 + tx, y1 + ty, x2 + tx, y2 + ty); + } + else + { + ShapeCache sc = shapeCache; + if (sc.line == null) + sc.line = new Line2D.Float(); + sc.line.setLine(x1, y1, x2, y2); + draw(sc.line); + } + } + + public void drawRect(int x, int y, int w, int h) + { + if (isOptimized) + { + int tx = (int) transform.getTranslateX(); + int ty = (int) transform.getTranslateY(); + rawDrawRect(x + tx, y + ty, w, h); + } + else + { + ShapeCache sc = shapeCache; + if (sc.rect == null) + sc.rect = new Rectangle(); + sc.rect.setBounds(x, y, w, h); + draw(sc.rect); + } + } + + /** + * Fills a rectangle with the current paint. + * + * @param x the upper left corner, X coordinate + * @param y the upper left corner, Y coordinate + * @param width the width of the rectangle + * @param height the height of the rectangle + */ + public void fillRect(int x, int y, int width, int height) + { + if (isOptimized) + { + rawFillRect(x + (int) transform.getTranslateX(), + y + (int) transform.getTranslateY(), width, height); + } + else + { + ShapeCache sc = shapeCache; + if (sc.rect == null) + sc.rect = new Rectangle(); + sc.rect.setBounds(x, y, width, height); + fill(sc.rect); + } + } + + /** + * Fills a rectangle with the current background color. + * + * This implementation temporarily sets the foreground color to the + * background and forwards the call to {@link #fillRect(int, int, int, int)}. + * + * @param x the upper left corner, X coordinate + * @param y the upper left corner, Y coordinate + * @param width the width of the rectangle + * @param height the height of the rectangle + */ + public void clearRect(int x, int y, int width, int height) + { + if (isOptimized) + rawClearRect(x, y, width, height); + else + { + Paint savedForeground = getPaint(); + setPaint(getBackground()); + fillRect(x, y, width, height); + setPaint(savedForeground); + } + } + + /** + * Draws a rounded rectangle. + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param width the width of the rectangle + * @param height the height of the rectangle + * @param arcWidth the width of the arcs + * @param arcHeight the height of the arcs + */ + public void drawRoundRect(int x, int y, int width, int height, int arcWidth, + int arcHeight) + { + ShapeCache sc = shapeCache; + if (sc.roundRect == null) + sc.roundRect = new RoundRectangle2D.Float(); + sc.roundRect.setRoundRect(x, y, width, height, arcWidth, arcHeight); + draw(sc.roundRect); + } + + /** + * Fills a rounded rectangle. + * + * @param x the x coordinate of the rectangle + * @param y the y coordinate of the rectangle + * @param width the width of the rectangle + * @param height the height of the rectangle + * @param arcWidth the width of the arcs + * @param arcHeight the height of the arcs + */ + public void fillRoundRect(int x, int y, int width, int height, int arcWidth, + int arcHeight) + { + ShapeCache sc = shapeCache; + if (sc.roundRect == null) + sc.roundRect = new RoundRectangle2D.Float(); + sc.roundRect.setRoundRect(x, y, width, height, arcWidth, arcHeight); + fill(sc.roundRect); + } + + /** + * Draws the outline of an oval. + * + * @param x the upper left corner of the bounding rectangle of the ellipse + * @param y the upper left corner of the bounding rectangle of the ellipse + * @param width the width of the ellipse + * @param height the height of the ellipse + */ + public void drawOval(int x, int y, int width, int height) + { + ShapeCache sc = shapeCache; + if (sc.ellipse == null) + sc.ellipse = new Ellipse2D.Float(); + sc.ellipse.setFrame(x, y, width, height); + draw(sc.ellipse); + } + + /** + * Fills an oval. + * + * @param x the upper left corner of the bounding rectangle of the ellipse + * @param y the upper left corner of the bounding rectangle of the ellipse + * @param width the width of the ellipse + * @param height the height of the ellipse + */ + public void fillOval(int x, int y, int width, int height) + { + ShapeCache sc = shapeCache; + if (sc.ellipse == null) + sc.ellipse = new Ellipse2D.Float(); + sc.ellipse.setFrame(x, y, width, height); + fill(sc.ellipse); + } + + /** + * Draws an arc. + */ + public void drawArc(int x, int y, int width, int height, int arcStart, + int arcAngle) + { + ShapeCache sc = shapeCache; + if (sc.arc == null) + sc.arc = new Arc2D.Float(); + sc.arc.setArc(x, y, width, height, arcStart, arcAngle, Arc2D.OPEN); + draw(sc.arc); + } + + /** + * Fills an arc. + */ + public void fillArc(int x, int y, int width, int height, int arcStart, + int arcAngle) + { + ShapeCache sc = shapeCache; + if (sc.arc == null) + sc.arc = new Arc2D.Float(); + sc.arc.setArc(x, y, width, height, arcStart, arcAngle, Arc2D.PIE); + draw(sc.arc); + } + + public void drawPolyline(int[] xPoints, int[] yPoints, int npoints) + { + ShapeCache sc = shapeCache; + if (sc.polyline == null) + sc.polyline = new GeneralPath(); + GeneralPath p = sc.polyline; + p.reset(); + if (npoints > 0) + p.moveTo(xPoints[0], yPoints[0]); + for (int i = 1; i < npoints; i++) + p.lineTo(xPoints[i], yPoints[i]); + fill(p); + } + + /** + * Draws the outline of a polygon. + */ + public void drawPolygon(int[] xPoints, int[] yPoints, int npoints) + { + ShapeCache sc = shapeCache; + if (sc.polygon == null) + sc.polygon = new Polygon(); + sc.polygon.reset(); + sc.polygon.xpoints = xPoints; + sc.polygon.ypoints = yPoints; + sc.polygon.npoints = npoints; + draw(sc.polygon); + } + + /** + * Fills the outline of a polygon. + */ + public void fillPolygon(int[] xPoints, int[] yPoints, int npoints) + { + ShapeCache sc = shapeCache; + if (sc.polygon == null) + sc.polygon = new Polygon(); + sc.polygon.reset(); + sc.polygon.xpoints = xPoints; + sc.polygon.ypoints = yPoints; + sc.polygon.npoints = npoints; + fill(sc.polygon); + } + + /** + * Draws the specified image at the specified location. This forwards + * to {@link #drawImage(Image, AffineTransform, ImageObserver)}. + * + * @param image the image to render + * @param x the x location to render to + * @param y the y location to render to + * @param observer the image observer to receive notification + */ + public boolean drawImage(Image image, int x, int y, ImageObserver observer) + { + boolean ret; + if (isOptimized) + { + ret = rawDrawImage(image, x + (int) transform.getTranslateX(), + y + (int) transform.getTranslateY(), observer); + } + else + { + AffineTransform t = new AffineTransform(); + t.translate(x, y); + ret = drawImage(image, t, observer); + } + return ret; + } + + /** + * Draws the specified image at the specified location. The image + * is scaled to the specified width and height. This forwards + * to {@link #drawImage(Image, AffineTransform, ImageObserver)}. + * + * @param image the image to render + * @param x the x location to render to + * @param y the y location to render to + * @param width the target width of the image + * @param height the target height of the image + * @param observer the image observer to receive notification + */ + public boolean drawImage(Image image, int x, int y, int width, int height, + ImageObserver observer) + { + AffineTransform t = new AffineTransform(); + int imWidth = image.getWidth(observer); + int imHeight = image.getHeight(observer); + if (imWidth == width && imHeight == height) + { + // No need to scale, fall back to non-scaling loops. + return drawImage(image, x, y, observer); + } + else + { + Image scaled = prepareImage(image, width, height); + // Ideally, this should notify the observer about the scaling progress. + return drawImage(scaled, x, y, observer); + } + } + + /** + * Draws the specified image at the specified location. This forwards + * to {@link #drawImage(Image, AffineTransform, ImageObserver)}. + * + * @param image the image to render + * @param x the x location to render to + * @param y the y location to render to + * @param bgcolor the background color to use for transparent pixels + * @param observer the image observer to receive notification + */ + public boolean drawImage(Image image, int x, int y, Color bgcolor, + ImageObserver observer) + { + AffineTransform t = new AffineTransform(); + t.translate(x, y); + // TODO: Somehow implement the background option. + return drawImage(image, t, observer); + } + + /** + * Draws the specified image at the specified location. The image + * is scaled to the specified width and height. This forwards + * to {@link #drawImage(Image, AffineTransform, ImageObserver)}. + * + * @param image the image to render + * @param x the x location to render to + * @param y the y location to render to + * @param width the target width of the image + * @param height the target height of the image + * @param bgcolor the background color to use for transparent pixels + * @param observer the image observer to receive notification + */ + public boolean drawImage(Image image, int x, int y, int width, int height, + Color bgcolor, ImageObserver observer) + { + AffineTransform t = new AffineTransform(); + t.translate(x, y); + double scaleX = (double) image.getWidth(observer) / (double) width; + double scaleY = (double) image.getHeight(observer) / (double) height; + t.scale(scaleX, scaleY); + // TODO: Somehow implement the background option. + return drawImage(image, t, observer); + } + + /** + * Draws an image fragment to a rectangular area of the target. + * + * @param image the image to render + * @param dx1 the first corner of the destination rectangle + * @param dy1 the first corner of the destination rectangle + * @param dx2 the second corner of the destination rectangle + * @param dy2 the second corner of the destination rectangle + * @param sx1 the first corner of the source rectangle + * @param sy1 the first corner of the source rectangle + * @param sx2 the second corner of the source rectangle + * @param sy2 the second corner of the source rectangle + * @param observer the image observer to be notified + */ + public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, + ImageObserver observer) + { + int sx = Math.min(sx1, sx1); + int sy = Math.min(sy1, sy2); + int sw = Math.abs(sx1 - sx2); + int sh = Math.abs(sy1 - sy2); + int dx = Math.min(dx1, dx1); + int dy = Math.min(dy1, dy2); + int dw = Math.abs(dx1 - dx2); + int dh = Math.abs(dy1 - dy2); + + AffineTransform t = new AffineTransform(); + t.translate(sx - dx, sy - dy); + double scaleX = (double) sw / (double) dw; + double scaleY = (double) sh / (double) dh; + t.scale(scaleX, scaleY); + Rectangle areaOfInterest = new Rectangle(sx, sy, sw, sh); + return drawImageImpl(image, t, observer, areaOfInterest); + } + + /** + * Draws an image fragment to a rectangular area of the target. + * + * @param image the image to render + * @param dx1 the first corner of the destination rectangle + * @param dy1 the first corner of the destination rectangle + * @param dx2 the second corner of the destination rectangle + * @param dy2 the second corner of the destination rectangle + * @param sx1 the first corner of the source rectangle + * @param sy1 the first corner of the source rectangle + * @param sx2 the second corner of the source rectangle + * @param sy2 the second corner of the source rectangle + * @param bgcolor the background color to use for transparent pixels + * @param observer the image observer to be notified + */ + public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, Color bgcolor, + ImageObserver observer) + { + // FIXME: Do something with bgcolor. + return drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); + } + + /** + * Disposes this graphics object. + */ + public void dispose() + { + // Nothing special to do here. + } + + /** + * Fills the specified shape. Override this if your backend can efficiently + * fill shapes. This is possible on many systems via a polygon fill + * method or something similar. But keep in mind that Shapes can be quite + * complex (non-convex, with holes etc), which is not necessarily supported + * by all polygon fillers. Also note that you must perform clipping + * before filling the shape. + * + * @param s the shape to fill + * @param isFont <code>true</code> if the shape is a font outline + */ + protected void fillShape(Shape s, boolean isFont) + { + // Determine if we need to antialias stuff. + boolean antialias = false; + if (isFont) + { + Object v = renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING); + // We default to antialiasing for text rendering. + antialias = v == RenderingHints.VALUE_TEXT_ANTIALIAS_ON + || (v == RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT + && DEFAULT_TEXT_AA); + } + else + { + Object v = renderingHints.get(RenderingHints.KEY_ANTIALIASING); + antialias = (v == RenderingHints.VALUE_ANTIALIAS_ON); + } + ScanlineConverter sc = getScanlineConverter(); + int resolution = 0; + int yRes = 0; + if (antialias) + { + // Adjust resolution according to rendering hints. + resolution = 2; + yRes = 4; + } + sc.renderShape(this, s, clip, transform, resolution, yRes, renderingHints); + freeScanlineConverter(sc); + } + + /** + * Returns the color model of this Graphics object. + * + * @return the color model of this Graphics object + */ + protected abstract ColorModel getColorModel(); + + /** + * Returns the bounds of the target. + * + * @return the bounds of the target + */ + protected abstract Rectangle getDeviceBounds(); + + /** + * Draws a line in optimization mode. The implementation should respect the + * clip and translation. It can assume that the clip is a rectangle and that + * the transform is only a translating transform. + * + * @param x0 the starting point, X coordinate + * @param y0 the starting point, Y coordinate + * @param x1 the end point, X coordinate + * @param y1 the end point, Y coordinate + */ + protected void rawDrawLine(int x0, int y0, int x1, int y1) + { + ShapeCache sc = shapeCache; + if (sc.line == null) + sc.line = new Line2D.Float(); + sc.line.setLine(x0, y0, x1, y1); + draw(sc.line); + } + + protected void rawDrawRect(int x, int y, int w, int h) + { + ShapeCache sc = shapeCache; + if (sc.rect == null) + sc.rect = new Rectangle(); + sc.rect.setBounds(x, y, w, h); + draw(sc.rect); + } + + /** + * Clears a rectangle in optimization mode. The implementation should respect the + * clip and translation. It can assume that the clip is a rectangle and that + * the transform is only a translating transform. + * + * @param x the upper left corner, X coordinate + * @param y the upper left corner, Y coordinate + * @param w the width + * @param h the height + */ + protected void rawClearRect(int x, int y, int w, int h) + { + Paint savedForeground = getPaint(); + setPaint(getBackground()); + rawFillRect(x, y, w, h); + setPaint(savedForeground); + } + + /** + * Fills a rectangle in optimization mode. The implementation should respect + * the clip but can assume that it is a rectangle. + * + * @param x the upper left corner, X coordinate + * @param y the upper left corner, Y coordinate + * @param w the width + * @param h the height + */ + protected void rawFillRect(int x, int y, int w, int h) + { + ShapeCache sc = shapeCache; + if (sc.rect == null) + sc.rect = new Rectangle(); + sc.rect.setBounds(x, y, w, h); + fill(sc.rect); + } + + /** + * Draws an image in optimization mode. The implementation should respect + * the clip but can assume that it is a rectangle. + * + * @param image the image to be painted + * @param x the location, X coordinate + * @param y the location, Y coordinate + * @param obs the image observer to be notified + * + * @return <code>true</code> when the image is painted completely, + * <code>false</code> if it is still rendered + */ + protected boolean rawDrawImage(Image image, int x, int y, ImageObserver obs) + { + AffineTransform t = new AffineTransform(); + t.translate(x, y); + return drawImage(image, t, obs); + } + + /** + * Copies a rectangular region to another location. + * + * @param x the upper left corner, X coordinate + * @param y the upper left corner, Y coordinate + * @param w the width + * @param h the height + * @param dx + * @param dy + */ + protected void rawCopyArea(int x, int y, int w, int h, int dx, int dy) + { + copyAreaImpl(x, y, w, h, dx, dy); + } + + // Private implementation methods. + + /** + * Copies a rectangular area of the target raster to a different location. + */ + private void copyAreaImpl(int x, int y, int w, int h, int dx, int dy) + { + // FIXME: Implement this properly. + throw new UnsupportedOperationException("Not implemented yet."); + } + + /** + * Paints a scanline between x0 and x1. Override this when your backend + * can efficiently draw/fill horizontal lines. + * + * @param x0 the left offset + * @param x1 the right offset + * @param y the scanline + */ + public void renderScanline(int y, ScanlineCoverage c) + { + PaintContext pCtx = getPaintContext(); + + int x0 = c.getMinX(); + int x1 = c.getMaxX(); + Raster paintRaster = pCtx.getRaster(x0, y, x1 - x0, 1); + + // Do the anti aliasing thing. + float coverageAlpha = 0; + float maxCoverage = c.getMaxCoverage(); + ColorModel cm = pCtx.getColorModel(); + DataBuffer db = paintRaster.getDataBuffer(); + Point loc = new Point(paintRaster.getMinX(), paintRaster.getMinY()); + SampleModel sm = paintRaster.getSampleModel(); + WritableRaster writeRaster = Raster.createWritableRaster(sm, db, loc); + WritableRaster alphaRaster = cm.getAlphaRaster(writeRaster); + int pixel; + ScanlineCoverage.Iterator iter = c.iterate(); + while (iter.hasNext()) + { + ScanlineCoverage.Range range = iter.next(); + coverageAlpha = range.getCoverage() / maxCoverage; + if (coverageAlpha < 1.0) + { + for (int x = range.getXPos(); x < range.getXPosEnd(); x++) + { + pixel = alphaRaster.getSample(x, y, 0); + pixel = (int) (pixel * coverageAlpha); + alphaRaster.setSample(x, y, 0, pixel); + } + } + } + ColorModel paintColorModel = pCtx.getColorModel(); + CompositeContext cCtx = composite.createContext(paintColorModel, + getColorModel(), + renderingHints); + WritableRaster raster = getDestinationRaster(); + WritableRaster targetChild = raster.createWritableTranslatedChild(-x0, -y); + + cCtx.compose(paintRaster, targetChild, targetChild); + updateRaster(raster, x0, y, x1 - x0, 1); + cCtx.dispose(); + } + + + /** + * Initializes this graphics object. This must be called by subclasses in + * order to correctly initialize the state of this object. + */ + protected void init() + { + setPaint(Color.BLACK); + setFont(FONT); + isOptimized = true; + } + + /** + * Returns a WritableRaster that is used by this class to perform the + * rendering in. It is not necessary that the target surface immediately + * reflects changes in the raster. Updates to the raster are notified via + * {@link #updateRaster}. + * + * @return the destination raster + */ + protected WritableRaster getDestinationRaster() + { + // TODO: Ideally we would fetch the xdrawable's surface pixels for + // initialization of the raster. + Rectangle db = getDeviceBounds(); + if (destinationRaster == null) + { + int[] bandMasks = new int[]{ 0xFF0000, 0xFF00, 0xFF }; + destinationRaster = Raster.createPackedRaster(DataBuffer.TYPE_INT, + db.width, db.height, + bandMasks, null); + // Initialize raster with white. + int x0 = destinationRaster.getMinX(); + int x1 = destinationRaster.getWidth() + x0; + int y0 = destinationRaster.getMinY(); + int y1 = destinationRaster.getHeight() + y0; + int numBands = destinationRaster.getNumBands(); + for (int y = y0; y < y1; y++) + { + for (int x = x0; x < x1; x++) + { + for (int b = 0; b < numBands; b++) + destinationRaster.setSample(x, y, b, 255); + } + } + } + return destinationRaster; + } + + /** + * Notifies the backend that the raster has changed in the specified + * rectangular area. The raster that is provided in this method is always + * the same as the one returned in {@link #getDestinationRaster}. + * Backends that reflect changes to this raster directly don't need to do + * anything here. + * + * @param raster the updated raster, identical to the raster returned + * by {@link #getDestinationRaster()} + * @param x the upper left corner of the updated region, X coordinate + * @param y the upper lef corner of the updated region, Y coordinate + * @param w the width of the updated region + * @param h the height of the updated region + */ + protected void updateRaster(Raster raster, int x, int y, int w, int h) + { + // Nothing to do here. Backends that need to update their surface + // to reflect the change should override this method. + } + + // Some helper methods. + + /** + * Helper method to check and update the optimization conditions. + */ + private void updateOptimization() + { + int transformType = transform.getType(); + boolean optimizedTransform = false; + if (transformType == AffineTransform.TYPE_IDENTITY + || transformType == AffineTransform.TYPE_TRANSLATION) + optimizedTransform = true; + + boolean optimizedClip = (clip == null || clip instanceof Rectangle); + isOptimized = optimizedClip + && optimizedTransform && paint instanceof Color + && composite == AlphaComposite.SrcOver + && stroke.equals(new BasicStroke()); + } + + /** + * Calculates the intersection of two rectangles. The result is stored + * in <code>rect</code>. This is basically the same + * like {@link Rectangle#intersection(Rectangle)}, only that it does not + * create new Rectangle instances. The tradeoff is that you loose any data in + * <code>rect</code>. + * + * @param x upper-left x coodinate of first rectangle + * @param y upper-left y coodinate of first rectangle + * @param w width of first rectangle + * @param h height of first rectangle + * @param rect a Rectangle object of the second rectangle + * + * @throws NullPointerException if rect is null + * + * @return a rectangle corresponding to the intersection of the + * two rectangles. An empty rectangle is returned if the rectangles + * do not overlap + */ + private static Rectangle computeIntersection(int x, int y, int w, int h, + Rectangle rect) + { + int x2 = rect.x; + int y2 = rect.y; + int w2 = rect.width; + int h2 = rect.height; + + int dx = (x > x2) ? x : x2; + int dy = (y > y2) ? y : y2; + int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx); + int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy); + + if (dw >= 0 && dh >= 0) + rect.setBounds(dx, dy, dw, dh); + else + rect.setBounds(0, 0, 0, 0); + + return rect; + } + + /** + * Helper method to transform the clip. This is called by the various + * transformation-manipulation methods to update the clip (which is in + * userspace) accordingly. + * + * The transform usually is the inverse transform that was applied to the + * graphics object. + * + * @param t the transform to apply to the clip + */ + private void updateClip(AffineTransform t) + { + if (! (clip instanceof GeneralPath)) + clip = new GeneralPath(clip); + + GeneralPath p = (GeneralPath) clip; + p.transform(t); + } + + /** + * Returns a free scanline converter from the pool. + * + * @return a scanline converter + */ + private ScanlineConverter getScanlineConverter() + { + synchronized (scanlineConverters) + { + ScanlineConverter sc; + if (scanlineConverters.size() > 0) + { + sc = scanlineConverters.removeFirst(); + } + else + { + sc = new ScanlineConverter(); + } + return sc; + } + } + + /** + * Puts a scanline converter back in the pool. + * + * @param sc + */ + private void freeScanlineConverter(ScanlineConverter sc) + { + synchronized (scanlineConverters) + { + scanlineConverters.addLast(sc); + } + } + + private PaintContext getPaintContext() + { + if (this.paintContext == null) + { + this.paintContext = + this.foreground.createContext(getColorModel(), + getDeviceBounds(), + getClipBounds(), + getTransform(), + getRenderingHints()); + } + + return this.paintContext; + } + + /** + * Scales an image to the specified width and height. This should also + * be used to implement + * {@link Toolkit#prepareImage(Image, int, int, ImageObserver)}. + * This uses {@link Toolkit#createImage(ImageProducer)} to create the actual + * image. + * + * @param image the image to prepare + * @param w the width + * @param h the height + * + * @return the scaled image + */ + public static Image prepareImage(Image image, int w, int h) + { + // Try to find cached scaled image. + HashMap<Dimension,Image> scaledTable = imageCache.get(image); + Dimension size = new Dimension(w, h); + Image scaled = null; + if (scaledTable != null) + { + scaled = scaledTable.get(size); + } + if (scaled == null) + { + // No cached scaled image. Start scaling image now. + ImageProducer source = image.getSource(); + ReplicateScaleFilter scaler = new ReplicateScaleFilter(w, h); + FilteredImageSource filteredSource = + new FilteredImageSource(source, scaler); + // Ideally, this should asynchronously scale the image. + Image scaledImage = + Toolkit.getDefaultToolkit().createImage(filteredSource); + scaled = scaledImage; + // Put scaled image in cache. + if (scaledTable == null) + { + scaledTable = new HashMap<Dimension,Image>(); + imageCache.put(image, scaledTable); + } + scaledTable.put(size, scaledImage); + } + return scaled; + } + +} |