| /* 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; |
| } |
| |
| } |