
libjava/ 2008-06-28 Matthias Klose <doko@ubuntu.com> Import GNU Classpath (classpath-0_97_2-release). * Regenerate class and header files. * Regenerate auto* files. * gcj/javaprims.h: Define jobjectRefType. * jni.cc (_Jv_JNI_GetObjectRefType): New (stub only). (_Jv_JNIFunctions): Initialize GetObjectRefType. * gnu/classpath/jdwp/VMVirtualMachine.java, java/security/VMSecureRandom.java: Merge from classpath. * HACKING: Fix typo. * ChangeLog-2007: New file. * configure.ac: Set JAVAC, pass --disable-regen-headers to classpath. libjava/classpath/ 2008-06-28 Matthias Klose <doko@ubuntu.com> * m4/ac_prog_javac.m4: Disable check for JAVAC, when not configured with --enable-java-maintainer-mode. * aclocal.m4, configure: Regenerate. * native/jni/gstreamer-peer/Makefile.am: Do not link with libclasspathnative. * native/jni/gstreamer-peer/Makefile.in: Regenerate. * tools/Makefile.am, lib/Makefile.am: Use JAVAC for setting JCOMPILER, drop flags not understood by gcj. From-SVN: r137223
1989 lines
60 KiB
Java
1989 lines
60 KiB
Java
/* 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.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.ImageObserver;
|
|
import java.awt.image.Raster;
|
|
import java.awt.image.RenderedImage;
|
|
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;
|
|
|
|
/**
|
|
* 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
|
|
{
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* 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 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 STANDARD_HINTS;
|
|
static {
|
|
HashMap hints = new HashMap();
|
|
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
|
|
{
|
|
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)
|
|
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()
|
|
{
|
|
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
|
|
{
|
|
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();
|
|
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. 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 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)
|
|
{
|
|
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 = paintContext;
|
|
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 targetChild = destinationRaster.createWritableTranslatedChild(-x0,- y);
|
|
cCtx.compose(paintRaster, targetChild, targetChild);
|
|
updateRaster(destinationRaster, 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);
|
|
}
|
|
}
|
|
|
|
}
|