| /* 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 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.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.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.PathIterator; |
| import java.awt.geom.Rectangle2D; |
| 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.ImageObserver; |
| import java.awt.image.Raster; |
| import java.awt.image.RenderedImage; |
| import java.awt.image.WritableRaster; |
| import java.awt.image.renderable.RenderableImage; |
| import java.text.AttributedCharacterIterator; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| /** |
| * 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>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> |
| * </ol> |
| * </p> |
| * |
| * @author Roman Kennke (kennke@aicas.com) |
| */ |
| public abstract class AbstractGraphics2D |
| extends Graphics2D |
| implements Cloneable |
| { |
| |
| /** |
| * Accuracy of the sampling in the anti-aliasing shape filler. |
| * Lower values give more speed, while higher values give more quality. |
| * It is advisable to choose powers of two. |
| */ |
| private static final int AA_SAMPLING = 8; |
| |
| /** |
| * The transformation for this Graphics2D instance |
| */ |
| protected AffineTransform transform; |
| |
| /** |
| * The foreground. |
| */ |
| private Paint paint; |
| |
| /** |
| * The background. |
| */ |
| private Color background; |
| |
| /** |
| * 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 paint raster. |
| */ |
| private Raster paintRaster; |
| |
| /** |
| * The raster of the destination surface. This is where the painting is |
| * performed. |
| */ |
| private WritableRaster destinationRaster; |
| |
| /** |
| * Stores the alpha values for a scanline in the anti-aliasing shape |
| * renderer. |
| */ |
| private transient int[] alpha; |
| |
| /** |
| * The edge table for the scanline conversion algorithms. |
| */ |
| private transient ArrayList[] edgeTable; |
| |
| /** |
| * 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; |
| |
| /** |
| * Creates a new AbstractGraphics2D instance. |
| */ |
| protected AbstractGraphics2D() |
| { |
| transform = new AffineTransform(); |
| background = Color.WHITE; |
| composite = AlphaComposite.SrcOver; |
| stroke = new BasicStroke(); |
| HashMap hints = new HashMap(); |
| hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, |
| RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); |
| hints.put(RenderingHints.KEY_ANTIALIASING, |
| RenderingHints.VALUE_ANTIALIAS_DEFAULT); |
| renderingHints = new RenderingHints(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) |
| { |
| boolean ret = false; |
| 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) |
| { |
| if (isOptimized) |
| rawDrawString(text, x, y); |
| else |
| { |
| FontRenderContext ctx = getFontRenderContext(); |
| GlyphVector gv = font.createGlyphVector(ctx, text.toCharArray()); |
| 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 |
| { |
| updateOptimization(); |
| } |
| } |
| } |
| |
| /** |
| * 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) |
| clip = 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() |
| { |
| return new FontRenderContext(transform, 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) |
| { |
| int numGlyphs = gv.getNumGlyphs(); |
| translate(x, y); |
| // TODO: We could use fill(gv.getOutline()), but that seems to be |
| // slightly more inefficient. |
| for (int i = 0; i < numGlyphs; i++) |
| { |
| Shape o = gv.getGlyphOutline(i); |
| fillShape(o, 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 |
| copy.clip = new GeneralPath(clip); |
| |
| copy.renderingHints = new RenderingHints(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() |
| { |
| Color c = null; |
| if (paint instanceof Color) |
| c = (Color) paint; |
| return c; |
| } |
| |
| /** |
| * Sets the current foreground. |
| * |
| * @param color the foreground to set |
| */ |
| public void setColor(Color color) |
| { |
| 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 |
| { |
| Line2D line = new Line2D.Double(x1, y1, x2, y2); |
| draw(line); |
| } |
| } |
| |
| /** |
| * 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) |
| { |
| int tx = (int) transform.getTranslateX(); |
| int ty = (int) transform.getTranslateY(); |
| rawFillRect(x + tx, y + ty, width, height); |
| } |
| else |
| { |
| fill(new Rectangle(x, y, width, height)); |
| } |
| } |
| |
| /** |
| * 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) |
| { |
| draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, |
| arcHeight)); |
| } |
| |
| /** |
| * 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) |
| { |
| fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, |
| arcHeight)); |
| } |
| |
| /** |
| * 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) |
| { |
| draw(new Ellipse2D.Double(x, y, width, height)); |
| } |
| |
| /** |
| * 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) |
| { |
| fill(new Ellipse2D.Double(x, y, width, height)); |
| } |
| |
| /** |
| * Draws an arc. |
| */ |
| public void drawArc(int x, int y, int width, int height, int arcStart, |
| int arcAngle) |
| { |
| draw(new Arc2D.Double(x, y, width, height, arcStart, arcAngle, |
| Arc2D.OPEN)); |
| } |
| |
| /** |
| * Fills an arc. |
| */ |
| public void fillArc(int x, int y, int width, int height, int arcStart, |
| int arcAngle) |
| { |
| fill(new Arc2D.Double(x, y, width, height, arcStart, arcAngle, |
| Arc2D.OPEN)); |
| } |
| |
| public void drawPolyline(int[] xPoints, int[] yPoints, int npoints) |
| { |
| // FIXME: Implement this. |
| throw new UnsupportedOperationException("Not yet implemented"); |
| } |
| |
| /** |
| * Draws the outline of a polygon. |
| */ |
| public void drawPolygon(int[] xPoints, int[] yPoints, int npoints) |
| { |
| draw(new Polygon(xPoints, yPoints, npoints)); |
| } |
| |
| /** |
| * Fills the outline of a polygon. |
| */ |
| public void fillPolygon(int[] xPoints, int[] yPoints, int npoints) |
| { |
| fill(new Polygon(xPoints, yPoints, npoints)); |
| } |
| |
| /** |
| * 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, y, 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(); |
| t.translate(x, y); |
| double scaleX = (double) width / (double) image.getWidth(observer); |
| double scaleY = (double) height / (double) image.getHeight(observer); |
| t.scale(scaleX, scaleY); |
| return drawImage(image, t, 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. The shape has already been clipped against the |
| * current clip. |
| * |
| * @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 on for text as long as we have no |
| // good hinting implemented. |
| antialias = (v == RenderingHints.VALUE_TEXT_ANTIALIAS_ON); |
| //|| v == RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); |
| } |
| else |
| { |
| Object v = renderingHints.get(RenderingHints.KEY_ANTIALIASING); |
| antialias = (v == RenderingHints.VALUE_ANTIALIAS_ON); |
| } |
| |
| Rectangle2D userBounds = s.getBounds2D(); |
| Rectangle2D deviceBounds = new Rectangle2D.Double(); |
| ArrayList segs = getSegments(s, transform, deviceBounds, false); |
| Rectangle2D clipBounds = new Rectangle2D.Double(); |
| ArrayList clipSegs = getSegments(clip, transform, clipBounds, true); |
| segs.addAll(clipSegs); |
| Rectangle2D inclClipBounds = new Rectangle2D.Double(); |
| Rectangle2D.union(clipBounds, deviceBounds, inclClipBounds); |
| if (segs.size() > 0) |
| { |
| if (antialias) |
| fillShapeAntialias(segs, deviceBounds, userBounds, inclClipBounds); |
| else |
| fillShapeImpl(segs, deviceBounds, userBounds, inclClipBounds); |
| } |
| } |
| |
| /** |
| * 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 Rectangle getDeviceBounds() |
| { |
| return destinationRaster.getBounds(); |
| } |
| |
| /** |
| * 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) |
| { |
| draw(new Line2D.Float(x0, y0, x1, y1)); |
| } |
| |
| /** |
| * Draws a string 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 text the string to be drawn |
| * @param x the start of the baseline, X coordinate |
| * @param y the start of the baseline, Y coordinate |
| */ |
| protected void rawDrawString(String text, int x, int y) |
| { |
| FontRenderContext ctx = getFontRenderContext(); |
| GlyphVector gv = font.createGlyphVector(ctx, text.toCharArray()); |
| drawGlyphVector(gv, x, y); |
| } |
| |
| /** |
| * 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) |
| { |
| fill(new Rectangle(x, y, w, h)); |
| } |
| |
| /** |
| * 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."); |
| } |
| |
| /** |
| * Fills the specified polygon. This should be overridden by backends |
| * that support accelerated (native) polygon filling, which is the |
| * case for most toolkit window and offscreen image implementations. |
| * |
| * The polygon is already clipped when this method is called. |
| */ |
| private void fillShapeImpl(ArrayList segs, Rectangle2D deviceBounds2D, |
| Rectangle2D userBounds, |
| Rectangle2D inclClipBounds) |
| { |
| // This is an implementation of a polygon scanline conversion algorithm |
| // described here: |
| // http://www.cs.berkeley.edu/~ug/slide/pipeline/assignments/scan/ |
| |
| // Create table of all edges. |
| // The edge buckets, sorted and indexed by their Y values. |
| |
| double minX = deviceBounds2D.getMinX(); |
| double minY = deviceBounds2D.getMinY(); |
| double maxX = deviceBounds2D.getMaxX(); |
| double maxY = deviceBounds2D.getMaxY(); |
| double icMinY = inclClipBounds.getMinY(); |
| double icMaxY = inclClipBounds.getMaxY(); |
| Rectangle deviceBounds = new Rectangle((int) minX, (int) minY, |
| (int) Math.ceil(maxX) - (int) minX, |
| (int) Math.ceil(maxY) - (int) minY); |
| PaintContext pCtx = paint.createContext(getColorModel(), deviceBounds, |
| userBounds, transform, renderingHints); |
| |
| ArrayList[] edgeTable = new ArrayList[(int) Math.ceil(icMaxY) |
| - (int) Math.ceil(icMinY) + 1]; |
| |
| for (Iterator i = segs.iterator(); i.hasNext();) |
| { |
| PolyEdge edge = (PolyEdge) i.next(); |
| int yindex = (int) ((int) Math.ceil(edge.y0) - (int) Math.ceil(icMinY)); |
| if (edgeTable[yindex] == null) // Create bucket when needed. |
| edgeTable[yindex] = new ArrayList(); |
| edgeTable[yindex].add(edge); // Add edge to the bucket of its line. |
| } |
| |
| // TODO: The following could be useful for a future optimization. |
| // // Sort all the edges in the edge table within their buckets. |
| // for (int y = 0; y < edgeTable.length; y++) |
| // { |
| // if (edgeTable[y] != null) |
| // Collections.sort(edgeTable[y]); |
| // } |
| |
| // The activeEdges list contains all the edges of the current scanline |
| // ordered by their intersection points with this scanline. |
| ArrayList activeEdges = new ArrayList(); |
| PolyEdgeComparator comparator = new PolyEdgeComparator(); |
| |
| // Scan all relevant lines. |
| int minYInt = (int) Math.ceil(icMinY); |
| |
| Rectangle devClip = getDeviceBounds(); |
| int scanlineMax = (int) Math.min(maxY, devClip.getMaxY()); |
| for (int y = minYInt; y < scanlineMax; y++) |
| { |
| ArrayList bucket = edgeTable[y - minYInt]; |
| // Update all the x intersections in the current activeEdges table |
| // and remove entries that are no longer in the scanline. |
| for (Iterator i = activeEdges.iterator(); i.hasNext();) |
| { |
| PolyEdge edge = (PolyEdge) i.next(); |
| if (y > edge.y1) |
| i.remove(); |
| else |
| { |
| edge.xIntersection += edge.slope; |
| //edge.xIntersection = edge.x0 + edge.slope * (y - edge.y0); |
| //System.err.println("edge.xIntersection: " + edge.xIntersection); |
| } |
| } |
| |
| if (bucket != null) |
| activeEdges.addAll(bucket); |
| |
| // Sort current edges. We are using a bubble sort, because the order |
| // of the intersections will not change in most situations. They |
| // will only change, when edges intersect each other. |
| int size = activeEdges.size(); |
| if (size > 1) |
| { |
| for (int i = 1; i < size; i++) |
| { |
| PolyEdge e1 = (PolyEdge) activeEdges.get(i - 1); |
| PolyEdge e2 = (PolyEdge) activeEdges.get(i); |
| if (comparator.compare(e1, e2) > 0) |
| { |
| // Swap e2 with its left neighbor until it 'fits'. |
| int j = i; |
| do |
| { |
| activeEdges.set(j, e1); |
| activeEdges.set(j - 1, e2); |
| j--; |
| if (j >= 1) |
| e1 = (PolyEdge) activeEdges.get(j - 1); |
| } while (j >= 1 && comparator.compare(e1, e2) > 0); |
| } |
| } |
| } |
| |
| // Now draw all pixels inside the polygon. |
| // This is the last edge that intersected the scanline. |
| PolyEdge previous = null; // Gets initialized below. |
| boolean insideShape = false; |
| boolean insideClip = false; |
| //System.err.println("scanline: " + y); |
| for (Iterator i = activeEdges.iterator(); i.hasNext();) |
| { |
| PolyEdge edge = (PolyEdge) i.next(); |
| if (edge.y1 <= y) |
| continue; |
| |
| // Draw scanline when we are inside the shape AND inside the |
| // clip. |
| if (insideClip && insideShape) |
| { |
| int x0 = (int) previous.xIntersection; |
| int x1 = (int) edge.xIntersection; |
| if (x0 < x1) |
| fillScanline(pCtx, x0, x1, y); |
| } |
| // Update state. |
| previous = edge; |
| if (edge.isClip) |
| insideClip = ! insideClip; |
| else |
| insideShape = ! insideShape; |
| } |
| } |
| pCtx.dispose(); |
| } |
| |
| /** |
| * Paints a scanline between x0 and x1. |
| * |
| * @param x0 the left offset |
| * @param x1 the right offset |
| * @param y the scanline |
| */ |
| protected void fillScanline(PaintContext pCtx, int x0, int x1, int y) |
| { |
| Raster paintRaster = pCtx.getRaster(x0, y, x1 - x0, 1); |
| ColorModel paintColorModel = pCtx.getColorModel(); |
| CompositeContext cCtx = composite.createContext(paintColorModel, |
| getColorModel(), |
| renderingHints); |
| WritableRaster targetChild = destinationRaster.createWritableTranslatedChild(-x0,- y); |
| cCtx.compose(paintRaster, targetChild, targetChild); |
| updateRaster(destinationRaster, x0, y, x1 - x0, 1); |
| cCtx.dispose(); |
| } |
| |
| /** |
| * Fills arbitrary shapes in an anti-aliased fashion. |
| * |
| * @param segs the line segments which define the shape which is to be filled |
| */ |
| private void fillShapeAntialias(ArrayList segs, Rectangle2D deviceBounds2D, |
| Rectangle2D userBounds, |
| Rectangle2D inclClipBounds) |
| { |
| // This is an implementation of a polygon scanline conversion algorithm |
| // described here: |
| // http://www.cs.berkeley.edu/~ug/slide/pipeline/assignments/scan/ |
| // The antialiasing is implemented using a sampling technique, we do |
| // not scan whole lines but fractions of the line. |
| |
| double minX = deviceBounds2D.getMinX(); |
| double minY = deviceBounds2D.getMinY(); |
| double maxX = deviceBounds2D.getMaxX(); |
| double maxY = deviceBounds2D.getMaxY(); |
| double icMinY = inclClipBounds.getMinY(); |
| double icMaxY = inclClipBounds.getMaxY(); |
| double icMinX = inclClipBounds.getMinX(); |
| double icMaxX = inclClipBounds.getMaxX(); |
| Rectangle deviceBounds = new Rectangle((int) minX, (int) minY, |
| (int) Math.ceil(maxX) - (int) minX, |
| (int) Math.ceil(maxY) - (int) minY); |
| PaintContext pCtx = paint.createContext(ColorModel.getRGBdefault(), |
| deviceBounds, |
| userBounds, transform, |
| renderingHints); |
| |
| // This array will contain the oversampled transparency values for |
| // each pixel in the scanline. |
| int numScanlines = (int) Math.ceil(icMaxY) - (int) icMinY; |
| int numScanlinePixels = (int) Math.ceil(icMaxX) - (int) icMinX + 1; |
| if (alpha == null || alpha.length < (numScanlinePixels + 1)) |
| alpha = new int[numScanlinePixels + 1]; |
| |
| int firstLine = (int) icMinY; |
| //System.err.println("minY: " + minY); |
| int firstSubline = (int) (Math.ceil((icMinY - Math.floor(icMinY)) * AA_SAMPLING)); |
| double firstLineDouble = firstLine + firstSubline / (double) AA_SAMPLING; |
| //System.err.println("firstSubline: " + firstSubline); |
| |
| // Create table of all edges. |
| // The edge buckets, sorted and indexed by their Y values. |
| //System.err.println("numScanlines: " + numScanlines); |
| if (edgeTable == null |
| || edgeTable.length < numScanlines * AA_SAMPLING + AA_SAMPLING) |
| edgeTable = new ArrayList[numScanlines * AA_SAMPLING + AA_SAMPLING]; |
| |
| //System.err.println("firstLineDouble: " + firstLineDouble); |
| |
| for (Iterator i = segs.iterator(); i.hasNext();) |
| { |
| PolyEdge edge = (PolyEdge) i.next(); |
| int yindex = (int) (Math.ceil((edge.y0 - firstLineDouble) * AA_SAMPLING)); |
| //System.err.println("yindex: " + yindex + " for y0: " + edge.y0); |
| // Initialize edge's slope and initial xIntersection. |
| edge.slope = ((edge.x1 - edge.x0) / (edge.y1 - edge.y0)) / AA_SAMPLING; |
| if (edge.y0 == edge.y1) // Horizontal edge. |
| edge.xIntersection = Math.min(edge.x0, edge.x1); |
| else |
| { |
| double alignedFirst = Math.ceil(edge.y0 * AA_SAMPLING) / AA_SAMPLING; |
| edge.xIntersection = edge.x0 + (edge.slope * AA_SAMPLING) * (alignedFirst - edge.y0); |
| } |
| //System.err.println(edge); |
| // FIXME: Sanity check should not be needed when clipping works. |
| if (yindex >= 0 && yindex < edgeTable.length) |
| { |
| if (edgeTable[yindex] == null) // Create bucket when needed. |
| edgeTable[yindex] = new ArrayList(); |
| edgeTable[yindex].add(edge); // Add edge to the bucket of its line. |
| } |
| } |
| |
| // The activeEdges list contains all the edges of the current scanline |
| // ordered by their intersection points with this scanline. |
| ArrayList activeEdges = new ArrayList(); |
| PolyEdgeComparator comparator = new PolyEdgeComparator(); |
| |
| // Scan all lines. |
| int yindex = 0; |
| //System.err.println("firstLine: " + firstLine + ", maxY: " + maxY + ", firstSubline: " + firstSubline); |
| for (int y = firstLine; y <= icMaxY; y++) |
| { |
| int leftX = (int) icMaxX; |
| int rightX = (int) icMinX; |
| boolean emptyScanline = true; |
| for (int subY = firstSubline; subY < AA_SAMPLING; subY++) |
| { |
| //System.err.println("scanline: " + y + ", subScanline: " + subY); |
| ArrayList bucket = edgeTable[yindex]; |
| // Update all the x intersections in the current activeEdges table |
| // and remove entries that are no longer in the scanline. |
| for (Iterator i = activeEdges.iterator(); i.hasNext();) |
| { |
| PolyEdge edge = (PolyEdge) i.next(); |
| // TODO: Do the following using integer arithmetics. |
| if ((y + ((double) subY / (double) AA_SAMPLING)) > edge.y1) |
| i.remove(); |
| else |
| { |
| edge.xIntersection += edge.slope; |
| //System.err.println("edge: " + edge); |
| //edge.xIntersection = edge.x0 + edge.slope * (y - edge.y0); |
| //System.err.println("edge.xIntersection: " + edge.xIntersection); |
| } |
| } |
| |
| if (bucket != null) |
| { |
| activeEdges.addAll(bucket); |
| edgeTable[yindex].clear(); |
| } |
| |
| // Sort current edges. We are using a bubble sort, because the order |
| // of the intersections will not change in most situations. They |
| // will only change, when edges intersect each other. |
| int size = activeEdges.size(); |
| if (size > 1) |
| { |
| for (int i = 1; i < size; i++) |
| { |
| PolyEdge e1 = (PolyEdge) activeEdges.get(i - 1); |
| PolyEdge e2 = (PolyEdge) activeEdges.get(i); |
| if (comparator.compare(e1, e2) > 0) |
| { |
| // Swap e2 with its left neighbor until it 'fits'. |
| int j = i; |
| do |
| { |
| activeEdges.set(j, e1); |
| activeEdges.set(j - 1, e2); |
| j--; |
| if (j >= 1) |
| e1 = (PolyEdge) activeEdges.get(j - 1); |
| } while (j >= 1 && comparator.compare(e1, e2) > 0); |
| } |
| } |
| } |
| |
| // Now draw all pixels inside the polygon. |
| // This is the last edge that intersected the scanline. |
| PolyEdge previous = null; // Gets initialized below. |
| boolean insideClip = false; |
| boolean insideShape = false; |
| //System.err.println("scanline: " + y + ", subscanline: " + subY); |
| for (Iterator i = activeEdges.iterator(); i.hasNext();) |
| { |
| PolyEdge edge = (PolyEdge) i.next(); |
| if (edge.y1 <= (y + (subY / (double) AA_SAMPLING))) |
| continue; |
| |
| if (insideClip && insideShape) |
| { |
| // TODO: Use integer arithmetics here. |
| if (edge.y1 > (y + (subY / (double) AA_SAMPLING))) |
| { |
| //System.err.println(edge); |
| // TODO: Eliminate the aligments. |
| int x0 = (int) Math.min(Math.max(previous.xIntersection, minX), maxX); |
| int x1 = (int) Math.min(Math.max(edge.xIntersection, minX), maxX); |
| //System.err.println("minX: " + minX + ", x0: " + x0 + ", x1: " + x1 + ", maxX: " + maxX); |
| // TODO: Pull out cast. |
| int left = x0 - (int) minX; |
| int right = x1 - (int) minX + 1; |
| alpha[left]++; |
| alpha[right]--; |
| leftX = Math.min(x0, leftX); |
| rightX = Math.max(x1+2, rightX); |
| emptyScanline = false; |
| } |
| } |
| previous = edge; |
| if (edge.isClip) |
| insideClip = ! insideClip; |
| else |
| insideShape = ! insideShape; |
| } |
| yindex++; |
| } |
| firstSubline = 0; |
| // Render full scanline. |
| //System.err.println("scanline: " + y); |
| if (! emptyScanline) |
| fillScanlineAA(alpha, leftX, (int) y, rightX - leftX, pCtx, |
| (int) minX); |
| } |
| |
| pCtx.dispose(); |
| } |
| |
| /** |
| * Fills a horizontal line between x0 and x1 for anti aliased rendering. |
| * the alpha array contains the deltas of the alpha values from one pixel |
| * to the next. |
| * |
| * @param alpha the alpha values in the scanline |
| * @param x0 the beginning of the scanline |
| * @param y the y coordinate of the line |
| */ |
| private void fillScanlineAA(int[] alpha, int x0, int yy, int numPixels, |
| PaintContext pCtx, int offs) |
| { |
| CompositeContext cCtx = composite.createContext(pCtx.getColorModel(), |
| getColorModel(), |
| renderingHints); |
| Raster paintRaster = pCtx.getRaster(x0, yy, numPixels, 1); |
| //System.err.println("paintColorModel: " + pCtx.getColorModel()); |
| WritableRaster aaRaster = paintRaster.createCompatibleWritableRaster(); |
| int numBands = paintRaster.getNumBands(); |
| ColorModel cm = pCtx.getColorModel(); |
| double lastAlpha = 0.; |
| int lastAlphaInt = 0; |
| |
| Object pixel = null; |
| int[] comps = null; |
| int x1 = x0 + numPixels; |
| for (int x = x0; x < x1; x++) |
| { |
| int i = x - offs; |
| if (alpha[i] != 0) |
| { |
| lastAlphaInt += alpha[i]; |
| lastAlpha = (double) lastAlphaInt / (double) AA_SAMPLING; |
| alpha[i] = 0; |
| } |
| pixel = paintRaster.getDataElements(x - x0, 0, pixel); |
| comps = cm.getComponents(pixel, comps, 0); |
| if (cm.hasAlpha() && ! cm.isAlphaPremultiplied()) |
| comps[comps.length - 1] *= lastAlpha; |
| else |
| { |
| int max; |
| if (cm.hasAlpha()) |
| max = comps.length - 2; |
| else |
| max = comps.length - 1; |
| for (int j = 0; j < max; j++) |
| comps[j] *= lastAlpha; |
| } |
| pixel = cm.getDataElements(comps, 0, pixel); |
| aaRaster.setDataElements(x - x0, 0, pixel); |
| } |
| |
| WritableRaster targetChild = |
| destinationRaster.createWritableTranslatedChild(-x0, -yy); |
| cCtx.compose(aaRaster, targetChild, targetChild); |
| updateRaster(destinationRaster, x0, yy, numPixels, 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(new Font("SansSerif", Font.PLAIN, 12)); |
| isOptimized = true; |
| |
| // FIXME: Should not be necessary. A clip of null should mean |
| // 'clip against device bounds. |
| destinationRaster = getDestinationRaster(); |
| clip = getDeviceBounds(); |
| } |
| |
| /** |
| * 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 = (int) rect.x; |
| int y2 = (int) rect.y; |
| int w2 = (int) rect.width; |
| int h2 = (int) 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); |
| } |
| |
| /** |
| * Converts the specified shape into a list of segments. |
| * |
| * @param s the shape to convert |
| * @param t the transformation to apply before converting |
| * @param deviceBounds an output parameter; holds the bounding rectangle of |
| * s in device space after return |
| * @param isClip true when the shape is a clip, false for normal shapes; |
| * this influences the settings in the created PolyEdge instances. |
| * |
| * @return a list of PolyEdge that form the shape in device space |
| */ |
| private ArrayList getSegments(Shape s, AffineTransform t, |
| Rectangle2D deviceBounds, boolean isClip) |
| { |
| // Flatten the path. TODO: Determine the best flattening factor |
| // wrt to speed and quality. |
| PathIterator path = s.getPathIterator(getTransform(), 1.0); |
| |
| // Build up polygons and let the native backend render this using |
| // rawFillShape() which would provide a default implementation for |
| // drawPixel using a PolyScan algorithm. |
| double[] seg = new double[6]; |
| |
| // TODO: Use ArrayList<PolyEdge> here when availble. |
| ArrayList segs = new ArrayList(); |
| double segX = 0.; // The start point of the current edge. |
| double segY = 0.; |
| double polyX = 0.; // The start point of the current polygon. |
| double polyY = 0.; |
| |
| double minX = Integer.MAX_VALUE; |
| double maxX = Integer.MIN_VALUE; |
| double minY = Integer.MAX_VALUE; |
| double maxY = Integer.MIN_VALUE; |
| |
| //System.err.println("fill polygon"); |
| while (! path.isDone()) |
| { |
| int segType = path.currentSegment(seg); |
| minX = Math.min(minX, seg[0]); |
| maxX = Math.max(maxX, seg[0]); |
| minY = Math.min(minY, seg[1]); |
| maxY = Math.max(maxY, seg[1]); |
| |
| //System.err.println("segment: " + segType + ", " + seg[0] + ", " + seg[1]); |
| if (segType == PathIterator.SEG_MOVETO) |
| { |
| segX = seg[0]; |
| segY = seg[1]; |
| polyX = seg[0]; |
| polyY = seg[1]; |
| } |
| else if (segType == PathIterator.SEG_CLOSE) |
| { |
| // Close the polyline. |
| PolyEdge edge = new PolyEdge(segX, segY, |
| polyX, polyY, isClip); |
| segs.add(edge); |
| } |
| else if (segType == PathIterator.SEG_LINETO) |
| { |
| PolyEdge edge = new PolyEdge(segX, segY, |
| seg[0], seg[1], isClip); |
| segs.add(edge); |
| segX = seg[0]; |
| segY = seg[1]; |
| } |
| path.next(); |
| } |
| deviceBounds.setRect(minX, minY, maxX - minX, maxY - minY); |
| return segs; |
| } |
| } |