| /* CairoGraphics2D.java -- |
| 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.peer.gtk; |
| |
| import gnu.java.awt.ClasspathToolkit; |
| |
| import java.awt.AWTPermission; |
| import java.awt.AlphaComposite; |
| import java.awt.BasicStroke; |
| import java.awt.Color; |
| import java.awt.Composite; |
| import java.awt.Font; |
| import java.awt.FontMetrics; |
| import java.awt.GradientPaint; |
| import java.awt.Graphics; |
| import java.awt.Graphics2D; |
| import java.awt.GraphicsConfiguration; |
| import java.awt.Image; |
| import java.awt.Paint; |
| import java.awt.Polygon; |
| import java.awt.Rectangle; |
| import java.awt.RenderingHints; |
| import java.awt.Shape; |
| import java.awt.Stroke; |
| import java.awt.TexturePaint; |
| import java.awt.Toolkit; |
| import java.awt.font.FontRenderContext; |
| import java.awt.font.GlyphVector; |
| import java.awt.font.TextLayout; |
| 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.NoninvertibleTransformException; |
| import java.awt.geom.PathIterator; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.geom.RoundRectangle2D; |
| import java.awt.image.AffineTransformOp; |
| import java.awt.image.BufferedImage; |
| import java.awt.image.BufferedImageOp; |
| import java.awt.image.ColorModel; |
| import java.awt.image.DataBuffer; |
| import java.awt.image.DataBufferInt; |
| import java.awt.image.DirectColorModel; |
| import java.awt.image.ImageObserver; |
| import java.awt.image.ImageProducer; |
| import java.awt.image.ImagingOpException; |
| import java.awt.image.MultiPixelPackedSampleModel; |
| 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.RenderContext; |
| import java.awt.image.renderable.RenderableImage; |
| import java.text.AttributedCharacterIterator; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * This is an abstract implementation of Graphics2D on Cairo. |
| * |
| * It should be subclassed for different Cairo contexts. |
| * |
| * Note for subclassers: Apart from the constructor (see comments below), |
| * The following abstract methods must be implemented: |
| * |
| * Graphics create() |
| * GraphicsConfiguration getDeviceConfiguration() |
| * copyArea(int x, int y, int width, int height, int dx, int dy) |
| * |
| * Also, dispose() must be overloaded to free any native datastructures |
| * used by subclass and in addition call super.dispose() to free the |
| * native cairographics2d structure and cairo_t. |
| * |
| * @author Sven de Marothy |
| */ |
| public abstract class CairoGraphics2D extends Graphics2D |
| { |
| static |
| { |
| System.loadLibrary("gtkpeer"); |
| } |
| |
| /** |
| * Important: This is a pointer to the native cairographics2d structure |
| * |
| * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE. |
| */ |
| long nativePointer; |
| |
| // Drawing state variables |
| /** |
| * The current paint |
| */ |
| Paint paint; |
| |
| /** |
| * The current stroke |
| */ |
| Stroke stroke; |
| |
| /* |
| * Current foreground and background color. |
| */ |
| Color fg, bg; |
| |
| /** |
| * Current clip shape. |
| */ |
| Shape clip; |
| |
| /** |
| * Current transform. |
| */ |
| AffineTransform transform; |
| |
| /** |
| * Current font. |
| */ |
| Font font; |
| |
| /** |
| * The current compositing context, if any. |
| */ |
| Composite comp; |
| |
| /** |
| * Rendering hint map. |
| */ |
| private RenderingHints hints; |
| |
| /** |
| * Some operations (drawing rather than filling) require that their |
| * coords be shifted to land on 0.5-pixel boundaries, in order to land on |
| * "middle of pixel" coordinates and light up complete pixels. |
| */ |
| private boolean shiftDrawCalls = false; |
| |
| /** |
| * Keep track if the first clip to be set, which is restored on setClip(null); |
| */ |
| private boolean firstClip = true; |
| private Shape originalClip; |
| |
| /** |
| * Stroke used for 3DRects |
| */ |
| private static BasicStroke draw3DRectStroke = new BasicStroke(); |
| |
| static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF); |
| static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF, |
| 0xFF000000); |
| |
| /** |
| * Constructor does nothing. |
| */ |
| public CairoGraphics2D() |
| { |
| } |
| |
| /** |
| * Sets up the default values and allocates the native cairographics2d structure |
| * @param cairo_t_pointer, a native pointer to a cairo_t of the context. |
| */ |
| public void setup(long cairo_t_pointer) |
| { |
| nativePointer = init(cairo_t_pointer); |
| setRenderingHints(new RenderingHints(getDefaultHints())); |
| font = new Font("SansSerif", Font.PLAIN, 12); |
| setColor(Color.black); |
| setBackground(Color.white); |
| setPaint(Color.black); |
| setStroke(new BasicStroke()); |
| setTransform(new AffineTransform()); |
| } |
| |
| /** |
| * Same as above, but copies the state of another CairoGraphics2D. |
| */ |
| public void copy(CairoGraphics2D g, long cairo_t_pointer) |
| { |
| nativePointer = init(cairo_t_pointer); |
| paint = g.paint; |
| stroke = g.stroke; |
| setRenderingHints(g.hints); |
| |
| Color foreground; |
| |
| if (g.fg.getAlpha() != -1) |
| foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(), |
| g.fg.getAlpha()); |
| else |
| foreground = new Color(g.fg.getRGB()); |
| |
| if (g.bg != null) |
| { |
| if (g.bg.getAlpha() != -1) |
| bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(), |
| g.bg.getAlpha()); |
| else |
| bg = new Color(g.bg.getRGB()); |
| } |
| |
| clip = g.getClip(); |
| |
| if (g.transform == null) |
| transform = null; |
| else |
| transform = new AffineTransform(g.transform); |
| |
| font = g.font; |
| |
| setColor(foreground); |
| setBackground(bg); |
| setPaint(paint); |
| setStroke(stroke); |
| setTransformImpl(transform); |
| setClip(clip); |
| } |
| |
| /** |
| * Generic destructor - call the native dispose() method. |
| */ |
| public void finalize() |
| { |
| dispose(); |
| } |
| |
| /** |
| * Disposes the native cairographics2d structure, including the |
| * cairo_t and any gradient stuff, if allocated. |
| * Subclasses should of course overload and call this if |
| * they have additional native structures. |
| */ |
| public void dispose() |
| { |
| disposeNative(nativePointer); |
| nativePointer = 0; |
| } |
| |
| /** |
| * Allocate the cairographics2d structure and set the cairo_t pointer in it. |
| * @param pointer - a cairo_t pointer, casted to a long. |
| */ |
| private native long init(long pointer); |
| |
| /** |
| * These are declared abstract as there may be context-specific issues. |
| */ |
| public abstract Graphics create(); |
| |
| public abstract GraphicsConfiguration getDeviceConfiguration(); |
| |
| protected abstract void copyAreaImpl(int x, int y, |
| int width, int height, int dx, int dy); |
| |
| |
| protected abstract Rectangle2D getRealBounds(); |
| |
| ////// Native Methods //////////////////////////////////////////////////// |
| |
| /** |
| * Dispose of allocate native resouces. |
| */ |
| public native void disposeNative(long pointer); |
| |
| /** |
| * Draw pixels as an RGBA int matrix |
| * @param w, h - width and height |
| * @param stride - stride of the array width |
| * @param i2u - affine transform array |
| */ |
| private native void drawPixels(long pointer, int[] pixels, int w, int h, |
| int stride, double[] i2u, double alpha); |
| |
| private native void setGradient(long pointer, double x1, double y1, |
| double x2, double y2, |
| int r1, int g1, int b1, int a1, int r2, |
| int g2, int b2, int a2, boolean cyclic); |
| |
| private native void setTexturePixels(long pointer, int[] pixels, int w, |
| int h, int stride); |
| |
| /** |
| * Set the current transform matrix |
| */ |
| private native void cairoSetMatrix(long pointer, double[] m); |
| |
| /** |
| * Scaling method |
| */ |
| private native void cairoScale(long pointer, double x, double y); |
| |
| /** |
| * Set the compositing operator |
| */ |
| private native void cairoSetOperator(long pointer, int cairoOperator); |
| |
| /** |
| * Sets the current color in RGBA as a 0.0-1.0 double |
| */ |
| private native void cairoSetRGBAColor(long pointer, double red, double green, |
| double blue, double alpha); |
| |
| /** |
| * Sets the current winding rule in Cairo |
| */ |
| private native void cairoSetFillRule(long pointer, int cairoFillRule); |
| |
| /** |
| * Set the line style, cap, join and miter limit. |
| * Cap and join parameters are in the BasicStroke enumerations. |
| */ |
| private native void cairoSetLine(long pointer, double width, int cap, |
| int join, double miterLimit); |
| |
| /** |
| * Set the dash style |
| */ |
| private native void cairoSetDash(long pointer, double[] dashes, int ndash, |
| double offset); |
| |
| /* |
| * Draws a Glyph Vector |
| */ |
| native void cairoDrawGlyphVector(long pointer, GdkFontPeer font, |
| float x, float y, int n, |
| int[] codes, float[] positions); |
| |
| |
| private native void cairoRelCurveTo(long pointer, double dx1, double dy1, |
| double dx2, double dy2, double dx3, |
| double dy3); |
| |
| /** |
| * Appends a rectangle to the current path |
| */ |
| private native void cairoRectangle(long pointer, double x, double y, |
| double width, double height); |
| |
| /** |
| * Appends an arc to the current path |
| */ |
| private native void cairoArc(long pointer, double x, double y, |
| double radius, double angle1, double angle2); |
| |
| /** |
| * Save / restore a cairo path |
| */ |
| private native void cairoSave(long pointer); |
| private native void cairoRestore(long pointer); |
| |
| /** |
| * New current path |
| */ |
| private native void cairoNewPath(long pointer); |
| |
| /** |
| * Close current path |
| */ |
| private native void cairoClosePath(long pointer); |
| |
| /** moveTo */ |
| private native void cairoMoveTo(long pointer, double x, double y); |
| |
| /** relative moveTo */ |
| private native void cairoRelMoveTo(long pointer, double dx, double dy); |
| |
| /** lineTo */ |
| private native void cairoLineTo(long pointer, double x, double y); |
| |
| /** relative lineTo */ |
| private native void cairoRelLineTo(long pointer, double dx, double dy); |
| |
| /** Cubic curve-to */ |
| private native void cairoCurveTo(long pointer, double x1, double y1, |
| double x2, double y2, |
| double x3, double y3); |
| |
| /** |
| * Stroke current path |
| */ |
| private native void cairoStroke(long pointer); |
| |
| /** |
| * Fill current path |
| */ |
| private native void cairoFill(long pointer, double alpha); |
| |
| /** |
| * Clip current path |
| */ |
| private native void cairoClip(long pointer); |
| |
| /** |
| * Save clip |
| */ |
| private native void cairoPreserveClip(long pointer); |
| |
| /** |
| * Save clip |
| */ |
| private native void cairoResetClip(long pointer); |
| |
| /** |
| * Set interpolation types |
| */ |
| private native void cairoSurfaceSetFilter(long pointer, int filter); |
| |
| /** |
| * Draws a line from (x1,y1) to (x2,y2). |
| * |
| * @param pointer the native pointer |
| * |
| * @param x1 the x coordinate of the starting point |
| * @param y1 the y coordinate of the starting point |
| * @param x2 the x coordinate of the end point |
| * @param y2 the y coordinate of the end point |
| */ |
| private native void cairoDrawLine(long pointer, double x1, double y1, |
| double x2, double y2); |
| |
| /** |
| * Draws a rectangle at starting point (x,y) and with the specified width |
| * and height. |
| * |
| * @param pointer the native pointer |
| * @param x the x coordinate of the upper left corner |
| * @param y the y coordinate of the upper left corner |
| * @param w the width of the rectangle |
| * @param h the height of the rectangle |
| */ |
| private native void cairoDrawRect(long pointer, double x, double y, double w, |
| double h); |
| |
| /** |
| * Fills a rectangle at starting point (x,y) and with the specified width |
| * and height. |
| * |
| * @param pointer the native pointer |
| * @param x the x coordinate of the upper left corner |
| * @param y the y coordinate of the upper left corner |
| * @param w the width of the rectangle |
| * @param h the height of the rectangle |
| */ |
| private native void cairoFillRect(long pointer, double x, double y, double w, |
| double h); |
| |
| |
| ///////////////////////// TRANSFORMS /////////////////////////////////// |
| /** |
| * Set the current transform |
| */ |
| public void setTransform(AffineTransform tx) |
| { |
| // Transform clip into target space using the old transform. |
| updateClip(transform); |
| |
| // Update the native transform. |
| setTransformImpl(tx); |
| |
| // 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(); |
| } |
| |
| if (clip != null) |
| setClip(clip); |
| } |
| |
| private void setTransformImpl(AffineTransform tx) |
| { |
| transform = tx; |
| if (transform != null) |
| { |
| double[] m = new double[6]; |
| transform.getMatrix(m); |
| cairoSetMatrix(nativePointer, m); |
| } |
| } |
| |
| public void transform(AffineTransform tx) |
| { |
| if (transform == null) |
| transform = new AffineTransform(tx); |
| else |
| transform.concatenate(tx); |
| |
| if (clip != null) |
| { |
| try |
| { |
| AffineTransform clipTransform = tx.createInverse(); |
| updateClip(clipTransform); |
| } |
| catch (NoninvertibleTransformException ex) |
| { |
| // TODO: How can we deal properly with this? |
| ex.printStackTrace(); |
| } |
| } |
| |
| setTransformImpl(transform); |
| } |
| |
| public void rotate(double theta) |
| { |
| transform(AffineTransform.getRotateInstance(theta)); |
| } |
| |
| public void rotate(double theta, double x, double y) |
| { |
| transform(AffineTransform.getRotateInstance(theta, x, y)); |
| } |
| |
| public void scale(double sx, double sy) |
| { |
| transform(AffineTransform.getScaleInstance(sx, sy)); |
| } |
| |
| /** |
| * Translate the system of the co-ordinates. As translation is a frequent |
| * operation, it is done in an optimised way, unlike scaling and rotating. |
| */ |
| public void translate(double tx, double ty) |
| { |
| if (transform != null) |
| transform.translate(tx, ty); |
| else |
| transform = AffineTransform.getTranslateInstance(tx, ty); |
| |
| if (clip != null) |
| { |
| // FIXME: this should actuall try to transform the shape |
| // rather than degrade to bounds. |
| if (clip instanceof Rectangle2D) |
| { |
| Rectangle2D r = (Rectangle2D) clip; |
| r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(), |
| r.getHeight()); |
| } |
| else |
| { |
| AffineTransform clipTransform = |
| AffineTransform.getTranslateInstance(-tx, -ty); |
| updateClip(clipTransform); |
| } |
| } |
| |
| setTransformImpl(transform); |
| } |
| |
| public void translate(int x, int y) |
| { |
| translate((double) x, (double) y); |
| } |
| |
| public void shear(double shearX, double shearY) |
| { |
| transform(AffineTransform.getShearInstance(shearX, shearY)); |
| } |
| |
| ///////////////////////// DRAWING STATE /////////////////////////////////// |
| |
| public void clip(Shape s) |
| { |
| // Do not touch clip when s == null. |
| if (s == null) |
| { |
| // The spec says this should clear the clip. The reference |
| // implementation throws a NullPointerException instead. I think, |
| // in this case we should conform to the specs, as it shouldn't |
| // affect compatibility. |
| setClip(null); |
| return; |
| } |
| |
| // If the current clip is still null, initialize it. |
| if (clip == null) |
| { |
| clip = getRealBounds(); |
| } |
| |
| // This is so common, let's optimize this. |
| if (clip instanceof Rectangle2D && s instanceof Rectangle2D) |
| { |
| Rectangle2D clipRect = (Rectangle2D) clip; |
| Rectangle2D r = (Rectangle2D) s; |
| Rectangle2D.intersect(clipRect, r, clipRect); |
| 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; |
| // Call setClip so that the native side gets notified. |
| setClip(clip); |
| } |
| } |
| |
| public Paint getPaint() |
| { |
| return paint; |
| } |
| |
| public AffineTransform getTransform() |
| { |
| return (AffineTransform) transform.clone(); |
| } |
| |
| public void setPaint(Paint p) |
| { |
| if (paint == null) |
| return; |
| |
| paint = p; |
| if (paint instanceof Color) |
| { |
| setColor((Color) paint); |
| } |
| else if (paint instanceof TexturePaint) |
| { |
| TexturePaint tp = (TexturePaint) paint; |
| BufferedImage img = tp.getImage(); |
| |
| // map the image to the anchor rectangle |
| int width = (int) tp.getAnchorRect().getWidth(); |
| int height = (int) tp.getAnchorRect().getHeight(); |
| |
| double scaleX = width / (double) img.getWidth(); |
| double scaleY = height / (double) img.getHeight(); |
| |
| AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0); |
| AffineTransformOp op = new AffineTransformOp(at, getRenderingHints()); |
| BufferedImage texture = op.filter(img, null); |
| int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width); |
| setTexturePixels(nativePointer, pixels, width, height, width); |
| } |
| else if (paint instanceof GradientPaint) |
| { |
| GradientPaint gp = (GradientPaint) paint; |
| Point2D p1 = gp.getPoint1(); |
| Point2D p2 = gp.getPoint2(); |
| Color c1 = gp.getColor1(); |
| Color c2 = gp.getColor2(); |
| setGradient(nativePointer, p1.getX(), p1.getY(), p2.getX(), p2.getY(), |
| c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(), |
| c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(), |
| gp.isCyclic()); |
| } |
| else |
| throw new java.lang.UnsupportedOperationException(); |
| } |
| |
| public Stroke getStroke() |
| { |
| return stroke; |
| } |
| |
| public void setStroke(Stroke st) |
| { |
| stroke = st; |
| if (stroke instanceof BasicStroke) |
| { |
| BasicStroke bs = (BasicStroke) stroke; |
| cairoSetLine(nativePointer, bs.getLineWidth(), bs.getEndCap(), |
| bs.getLineJoin(), bs.getMiterLimit()); |
| |
| float[] dashes = bs.getDashArray(); |
| if (dashes != null) |
| { |
| double[] double_dashes = new double[dashes.length]; |
| for (int i = 0; i < dashes.length; i++) |
| double_dashes[i] = dashes[i]; |
| cairoSetDash(nativePointer, double_dashes, double_dashes.length, |
| (double) bs.getDashPhase()); |
| } |
| else |
| cairoSetDash(nativePointer, new double[0], 0, 0.0); |
| } |
| } |
| |
| public void setPaintMode() |
| { |
| setComposite(AlphaComposite.SrcOver); |
| } |
| |
| public void setXORMode(Color c) |
| { |
| // FIXME: implement |
| } |
| |
| public void setColor(Color c) |
| { |
| if (c == null) |
| c = Color.BLACK; |
| |
| fg = c; |
| paint = c; |
| updateColor(); |
| } |
| |
| /** |
| * Set the current fg value as the cairo color. |
| */ |
| void updateColor() |
| { |
| if (fg == null) |
| fg = Color.BLACK; |
| cairoSetRGBAColor(nativePointer, fg.getRed() / 255.0, |
| fg.getGreen() / 255.0,fg.getBlue() / 255.0, |
| fg.getAlpha() / 255.0); |
| } |
| |
| public Color getColor() |
| { |
| return fg; |
| } |
| |
| public void clipRect(int x, int y, int width, int height) |
| { |
| if (clip == null) |
| setClip(new Rectangle(x, y, width, height)); |
| else if (clip instanceof Rectangle) |
| { |
| computeIntersection(x, y, width, height, (Rectangle) clip); |
| setClip(clip); |
| } |
| else |
| clip(new Rectangle(x, y, width, height)); |
| } |
| |
| public Shape getClip() |
| { |
| if (clip == null) |
| return null; |
| else if (clip instanceof Rectangle2D) |
| return clip.getBounds2D(); //getClipInDevSpace(); |
| else |
| { |
| GeneralPath p = new GeneralPath(); |
| PathIterator pi = clip.getPathIterator(null); |
| p.append(pi, false); |
| return p; |
| } |
| } |
| |
| public Rectangle getClipBounds() |
| { |
| if (clip == null) |
| return null; |
| else |
| return clip.getBounds(); |
| } |
| |
| protected Rectangle2D getClipInDevSpace() |
| { |
| Rectangle2D uclip = clip.getBounds2D(); |
| if (transform == null) |
| return uclip; |
| else |
| { |
| Point2D pos = transform.transform(new Point2D.Double(uclip.getX(), |
| uclip.getY()), |
| (Point2D) null); |
| Point2D extent = transform.deltaTransform(new Point2D.Double(uclip |
| .getWidth(), |
| uclip |
| .getHeight()), |
| (Point2D) null); |
| return new Rectangle2D.Double(pos.getX(), pos.getY(), extent.getX(), |
| extent.getY()); |
| } |
| } |
| |
| public void setClip(int x, int y, int width, int height) |
| { |
| if( width < 0 || height < 0 ) |
| return; |
| |
| setClip(new Rectangle2D.Double(x, y, width, height)); |
| } |
| |
| public void setClip(Shape s) |
| { |
| // The first time the clip is set, save it as the original clip |
| // to reset to on s == null. We can rely on this being non-null |
| // because the constructor in subclasses is expected to set the |
| // initial clip properly. |
| if( firstClip ) |
| { |
| originalClip = s; |
| firstClip = false; |
| } |
| |
| clip = s; |
| cairoResetClip(nativePointer); |
| |
| if (clip != null) |
| { |
| cairoNewPath(nativePointer); |
| if (clip instanceof Rectangle2D) |
| { |
| Rectangle2D r = (Rectangle2D) clip; |
| cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(), |
| r.getHeight()); |
| } |
| else |
| walkPath(clip.getPathIterator(null), false); |
| |
| cairoClip(nativePointer); |
| } |
| } |
| |
| public void setBackground(Color c) |
| { |
| if (c == null) |
| c = Color.WHITE; |
| bg = c; |
| } |
| |
| public Color getBackground() |
| { |
| return bg; |
| } |
| |
| /** |
| * Return the current composite. |
| */ |
| public Composite getComposite() |
| { |
| if (comp == null) |
| return AlphaComposite.SrcOver; |
| else |
| return comp; |
| } |
| |
| /** |
| * Sets the current composite context. |
| */ |
| public void setComposite(Composite comp) |
| { |
| this.comp = comp; |
| |
| if (comp instanceof AlphaComposite) |
| { |
| AlphaComposite a = (AlphaComposite) comp; |
| cairoSetOperator(nativePointer, a.getRule()); |
| } |
| else |
| { |
| // 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")); |
| |
| // FIXME: implement general Composite support |
| throw new java.lang.UnsupportedOperationException(); |
| } |
| } |
| |
| ///////////////////////// DRAWING PRIMITIVES /////////////////////////////////// |
| |
| public void draw(Shape s) |
| { |
| if ((stroke != null && ! (stroke instanceof BasicStroke)) |
| || (comp instanceof AlphaComposite && ((AlphaComposite) comp).getAlpha() != 1.0)) |
| { |
| // Cairo doesn't support stroking with alpha, so we create the stroked |
| // shape and fill with alpha instead |
| fill(stroke.createStrokedShape(s)); |
| return; |
| } |
| |
| createPath(s); |
| cairoStroke(nativePointer); |
| } |
| |
| public void fill(Shape s) |
| { |
| createPath(s); |
| |
| double alpha = 1.0; |
| if (comp instanceof AlphaComposite) |
| alpha = ((AlphaComposite) comp).getAlpha(); |
| cairoFill(nativePointer, alpha); |
| } |
| |
| private void createPath(Shape s) |
| { |
| cairoNewPath(nativePointer); |
| |
| // Optimize rectangles, since there is a direct Cairo function |
| if (s instanceof Rectangle2D) |
| { |
| Rectangle2D r = (Rectangle2D) s; |
| cairoRectangle(nativePointer, shifted(r.getX(), shiftDrawCalls), |
| shifted(r.getY(), shiftDrawCalls), r.getWidth(), |
| r.getHeight()); |
| } |
| |
| // We can optimize ellipses too; however we don't bother optimizing arcs: |
| // the iterator is fast enough (an ellipse requires 5 steps using the |
| // iterator, while most arcs are only 2-3) |
| else if (s instanceof Ellipse2D) |
| { |
| Ellipse2D e = (Ellipse2D) s; |
| |
| double radius = Math.min(e.getHeight(), e.getWidth()) / 2; |
| |
| // Cairo only draws circular shapes, but we can use a stretch to make |
| // them into ellipses |
| double xscale = 1, yscale = 1; |
| if (e.getHeight() != e.getWidth()) |
| { |
| cairoSave(nativePointer); |
| |
| if (e.getHeight() < e.getWidth()) |
| xscale = e.getWidth() / (radius * 2); |
| else |
| yscale = e.getHeight() / (radius * 2); |
| |
| if (xscale != 1 || yscale != 1) |
| cairoScale(nativePointer, xscale, yscale); |
| } |
| |
| cairoArc(nativePointer, |
| shifted(e.getCenterX() / xscale, shiftDrawCalls), |
| shifted(e.getCenterY() / yscale, shiftDrawCalls), radius, 0, |
| Math.PI * 2); |
| |
| if (xscale != 1 || yscale != 1) |
| cairoRestore(nativePointer); |
| } |
| |
| // All other shapes are broken down and drawn in steps using the |
| // PathIterator |
| else |
| walkPath(s.getPathIterator(null), shiftDrawCalls); |
| } |
| |
| /** |
| * Note that the rest of the drawing methods go via fill() or draw() for the drawing, |
| * although subclasses may with to overload these methods where context-specific |
| * optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int) |
| */ |
| |
| public void clearRect(int x, int y, int width, int height) |
| { |
| if (bg != null) |
| cairoSetRGBAColor(nativePointer, bg.getRed() / 255.0, |
| bg.getGreen() / 255.0, bg.getBlue() / 255.0, 1.0); |
| fillRect(x, y, width, height); |
| updateColor(); |
| } |
| |
| public void draw3DRect(int x, int y, int width, int height, boolean raised) |
| { |
| Stroke tmp = stroke; |
| setStroke(draw3DRectStroke); |
| super.draw3DRect(x, y, width, height, raised); |
| setStroke(tmp); |
| } |
| |
| public void drawArc(int x, int y, int width, int height, int startAngle, |
| int arcAngle) |
| { |
| draw(new Arc2D.Double((double) x, (double) y, (double) width, |
| (double) height, (double) startAngle, |
| (double) arcAngle, Arc2D.OPEN)); |
| } |
| |
| public void drawLine(int x1, int y1, int x2, int y2) |
| { |
| // The coordinates being pairwise identical means one wants |
| // to draw a single pixel. This is emulated by drawing |
| // a one pixel sized rectangle. |
| if (x1 == x2 && y1 == y2) |
| cairoFillRect(nativePointer, x1, y1, 1, 1); |
| else |
| cairoDrawLine(nativePointer, x1 + 0.5, y1 + 0.5, x2 + 0.5, y2 + 0.5); |
| } |
| |
| public void drawRect(int x, int y, int width, int height) |
| { |
| cairoDrawRect(nativePointer, shifted(x, shiftDrawCalls), |
| shifted(y, shiftDrawCalls), width, height); |
| } |
| |
| public void fillArc(int x, int y, int width, int height, int startAngle, |
| int arcAngle) |
| { |
| fill(new Arc2D.Double((double) x, (double) y, (double) width, |
| (double) height, (double) startAngle, |
| (double) arcAngle, Arc2D.OPEN)); |
| } |
| |
| public void fillRect(int x, int y, int width, int height) |
| { |
| cairoFillRect(nativePointer, x, y, width, height); |
| } |
| |
| public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) |
| { |
| fill(new Polygon(xPoints, yPoints, nPoints)); |
| } |
| |
| public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) |
| { |
| draw(new Polygon(xPoints, yPoints, nPoints)); |
| } |
| |
| public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) |
| { |
| draw(new Polygon(xPoints, yPoints, nPoints)); |
| } |
| |
| public void drawOval(int x, int y, int width, int height) |
| { |
| drawArc(x, y, width, height, 0, 360); |
| } |
| |
| 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)); |
| } |
| |
| public void fillOval(int x, int y, int width, int height) |
| { |
| fillArc(x, y, width, height, 0, 360); |
| } |
| |
| 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)); |
| } |
| |
| /** |
| * CopyArea - performs clipping to the native surface as a convenience |
| * (requires getRealBounds). Then calls copyAreaImpl. |
| */ |
| public void copyArea(int ox, int oy, int owidth, int oheight, |
| int odx, int ody) |
| { |
| Point2D pos = transform.transform(new Point2D.Double(ox, oy), |
| (Point2D) null); |
| Point2D dim = transform.transform(new Point2D.Double(ox + owidth, |
| oy + oheight), |
| (Point2D) null); |
| Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody), |
| (Point2D) null); |
| int x = (int)pos.getX(); |
| int y = (int)pos.getY(); |
| int width = (int)(dim.getX() - pos.getX()); |
| int height = (int)(dim.getY() - pos.getY()); |
| int dx = (int)(p2.getX() - pos.getX()); |
| int dy = (int)(p2.getY() - pos.getY()); |
| |
| Rectangle2D r = getRealBounds(); |
| |
| if( width < 0 || height < 0 ) |
| return; |
| // Return if outside the surface |
| if( x + dx > r.getWidth() || y + dy > r.getHeight() ) |
| return; |
| |
| if( x + dx + width < r.getX() || y + dy + height < r.getY() ) |
| return; |
| |
| // Clip edges if necessary |
| if( x + dx < r.getX() ) // left |
| { |
| width = x + dx + width; |
| x = (int)r.getX() - dx; |
| } |
| |
| if( y + dy < r.getY() ) // top |
| { |
| height = y + dy + height; |
| y = (int)r.getY() - dy; |
| } |
| |
| if( x + dx + width >= r.getWidth() ) // right |
| width = (int)r.getWidth() - dx - x; |
| |
| if( y + dy + height >= r.getHeight() ) // bottom |
| height = (int)r.getHeight() - dy - y; |
| |
| copyAreaImpl(x, y, width, height, dx, dy); |
| } |
| |
| ///////////////////////// RENDERING HINTS /////////////////////////////////// |
| |
| /** |
| * FIXME- support better |
| */ |
| public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) |
| { |
| hints.put(hintKey, hintValue); |
| |
| if (hintKey.equals(RenderingHints.KEY_INTERPOLATION) |
| || hintKey.equals(RenderingHints.KEY_ALPHA_INTERPOLATION)) |
| { |
| if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)) |
| cairoSurfaceSetFilter(nativePointer, 0); |
| |
| else if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_BILINEAR)) |
| cairoSurfaceSetFilter(nativePointer, 1); |
| |
| else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED)) |
| cairoSurfaceSetFilter(nativePointer, 2); |
| |
| else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)) |
| cairoSurfaceSetFilter(nativePointer, 3); |
| |
| else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT)) |
| cairoSurfaceSetFilter(nativePointer, 4); |
| } |
| |
| shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE) |
| || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT); |
| } |
| |
| public Object getRenderingHint(RenderingHints.Key hintKey) |
| { |
| return hints.get(hintKey); |
| } |
| |
| public void setRenderingHints(Map hints) |
| { |
| this.hints = new RenderingHints(getDefaultHints()); |
| this.hints.add(new RenderingHints(hints)); |
| |
| if (hints.containsKey(RenderingHints.KEY_INTERPOLATION)) |
| { |
| if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)) |
| cairoSurfaceSetFilter(nativePointer, 0); |
| |
| else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR)) |
| cairoSurfaceSetFilter(nativePointer, 1); |
| } |
| |
| if (hints.containsKey(RenderingHints.KEY_ALPHA_INTERPOLATION)) |
| { |
| if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED)) |
| cairoSurfaceSetFilter(nativePointer, 2); |
| |
| else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)) |
| cairoSurfaceSetFilter(nativePointer, 3); |
| |
| else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT)) |
| cairoSurfaceSetFilter(nativePointer, 4); |
| } |
| |
| shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE) |
| || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT); |
| } |
| |
| public void addRenderingHints(Map hints) |
| { |
| this.hints.add(new RenderingHints(hints)); |
| } |
| |
| public RenderingHints getRenderingHints() |
| { |
| return hints; |
| } |
| |
| ///////////////////////// IMAGE. METHODS /////////////////////////////////// |
| |
| protected boolean drawImage(Image img, AffineTransform xform, |
| Color bgcolor, ImageObserver obs) |
| { |
| if (img == null) |
| return false; |
| |
| if (xform == null) |
| xform = new AffineTransform(); |
| |
| // In this case, xform is an AffineTransform that transforms bounding |
| // box of the specified image from image space to user space. However |
| // when we pass this transform to cairo, cairo will use this transform |
| // to map "user coordinates" to "pixel" coordinates, which is the |
| // other way around. Therefore to get the "user -> pixel" transform |
| // that cairo wants from "image -> user" transform that we currently |
| // have, we will need to invert the transformation matrix. |
| AffineTransform invertedXform; |
| |
| try |
| { |
| invertedXform = xform.createInverse(); |
| } |
| catch (NoninvertibleTransformException e) |
| { |
| throw new ImagingOpException("Unable to invert transform " |
| + xform.toString()); |
| } |
| |
| // Unrecognized image - convert to a BufferedImage |
| // Note - this can get us in trouble when the gdk lock is re-acquired. |
| // for example by VolatileImage. See ComponentGraphics for how we work |
| // around this. |
| |
| if( !(img instanceof BufferedImage) ) |
| { |
| ImageProducer source = img.getSource(); |
| if (source == null) |
| return false; |
| img = Toolkit.getDefaultToolkit().createImage(source); |
| } |
| |
| BufferedImage b = (BufferedImage) img; |
| DataBuffer db; |
| double[] i2u = new double[6]; |
| int width = b.getWidth(); |
| int height = b.getHeight(); |
| |
| // If this BufferedImage has a BufferedImageGraphics object, |
| // use the cached CairoSurface that BIG is drawing onto |
| |
| if( BufferedImageGraphics.bufferedImages.get( b ) != null ) |
| db = (DataBuffer)BufferedImageGraphics.bufferedImages.get( b ); |
| else |
| db = b.getRaster().getDataBuffer(); |
| |
| invertedXform.getMatrix(i2u); |
| |
| double alpha = 1.0; |
| if (comp instanceof AlphaComposite) |
| alpha = ((AlphaComposite) comp).getAlpha(); |
| |
| if(db instanceof CairoSurface) |
| { |
| ((CairoSurface)db).drawSurface(nativePointer, i2u, alpha); |
| updateColor(); |
| return true; |
| } |
| |
| if( bgcolor != null ) |
| { |
| // Fill a rectangle with the background color |
| // to composite the image onto. |
| Paint oldPaint = paint; |
| AffineTransform oldTransform = transform; |
| setPaint( bgcolor ); |
| setTransform( invertedXform ); |
| fillRect(0, 0, width, height); |
| setTransform( oldTransform ); |
| setPaint( oldPaint ); |
| } |
| |
| int[] pixels = b.getRGB(0, 0, width, height, null, 0, width); |
| |
| drawPixels(nativePointer, pixels, width, height, width, i2u, alpha); |
| |
| // Cairo seems to lose the current color which must be restored. |
| updateColor(); |
| return true; |
| } |
| |
| public void drawRenderedImage(RenderedImage image, AffineTransform xform) |
| { |
| drawRaster(image.getColorModel(), image.getData(), xform, null); |
| } |
| |
| public void drawRenderableImage(RenderableImage image, AffineTransform xform) |
| { |
| drawRenderedImage(image.createRendering(new RenderContext(xform)), xform); |
| } |
| |
| public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) |
| { |
| return drawImage(img, xform, null, obs); |
| } |
| |
| public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y) |
| { |
| Image filtered = image; |
| if (op != null) |
| filtered = op.filter(image, null); |
| drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null); |
| } |
| |
| public boolean drawImage(Image img, int x, int y, ImageObserver observer) |
| { |
| return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, |
| observer); |
| } |
| |
| public boolean drawImage(Image img, int x, int y, Color bgcolor, |
| ImageObserver observer) |
| { |
| return drawImage(img, x, y, img.getWidth(observer), |
| img.getHeight(observer), bgcolor, observer); |
| } |
| |
| public boolean drawImage(Image img, int x, int y, int width, int height, |
| Color bgcolor, ImageObserver observer) |
| { |
| double scaleX = width / (double) img.getWidth(observer); |
| double scaleY = height / (double) img.getHeight(observer); |
| if( scaleX == 0 || scaleY == 0 ) |
| return true; |
| |
| return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y), |
| bgcolor, observer); |
| } |
| |
| public boolean drawImage(Image img, int x, int y, int width, int height, |
| ImageObserver observer) |
| { |
| return drawImage(img, x, y, width, height, null, observer); |
| } |
| |
| public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, |
| int sx1, int sy1, int sx2, int sy2, Color bgcolor, |
| ImageObserver observer) |
| { |
| if (img == null) |
| return false; |
| |
| int sourceWidth = sx2 - sx1; |
| int sourceHeight = sy2 - sy1; |
| |
| int destWidth = dx2 - dx1; |
| int destHeight = dy2 - dy1; |
| |
| if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 || |
| sourceHeight == 0) |
| return true; |
| |
| double scaleX = destWidth / (double) sourceWidth; |
| double scaleY = destHeight / (double) sourceHeight; |
| |
| // FIXME: Avoid using an AT if possible here - it's at least twice as slow. |
| |
| Shape oldClip = getClip(); |
| int cx, cy, cw, ch; |
| if( dx1 < dx2 ) |
| { cx = dx1; cw = dx2 - dx1; } |
| else |
| { cx = dx2; cw = dx1 - dx2; } |
| if( dy1 < dy2 ) |
| { cy = dy1; ch = dy2 - dy1; } |
| else |
| { cy = dy2; ch = dy1 - dy2; } |
| |
| clipRect( cx, cy, cw, ch ); |
| |
| AffineTransform tx = new AffineTransform(); |
| tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY ); |
| tx.scale( scaleX, scaleY ); |
| |
| boolean retval = drawImage(img, tx, bgcolor, observer); |
| setClip( oldClip ); |
| return retval; |
| } |
| |
| public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, |
| int sx1, int sy1, int sx2, int sy2, |
| ImageObserver observer) |
| { |
| return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer); |
| } |
| |
| ///////////////////////// TEXT METHODS //////////////////////////////////// |
| |
| public void drawString(String str, float x, float y) |
| { |
| if (str == null || str.length() == 0) |
| return; |
| (new TextLayout( str, getFont(), getFontRenderContext() )). |
| draw(this, x, y); |
| } |
| |
| public void drawString(String str, int x, int y) |
| { |
| drawString (str, (float) x, (float) y); |
| } |
| |
| public void drawString(AttributedCharacterIterator ci, int x, int y) |
| { |
| drawString (ci, (float) x, (float) y); |
| } |
| |
| public void drawGlyphVector(GlyphVector gv, float x, float y) |
| { |
| double alpha = 1.0; |
| |
| if( gv.getNumGlyphs() <= 0 ) |
| return; |
| |
| if (comp instanceof AlphaComposite) |
| alpha = ((AlphaComposite) comp).getAlpha(); |
| if (gv instanceof FreetypeGlyphVector && alpha == 1.0) |
| { |
| int n = gv.getNumGlyphs (); |
| int[] codes = gv.getGlyphCodes (0, n, null); |
| float[] positions = gv.getGlyphPositions (0, n, null); |
| |
| setFont (gv.getFont ()); |
| synchronized( this.font ) |
| { |
| cairoDrawGlyphVector(nativePointer, (GdkFontPeer)getFont().getPeer(), |
| x, y, n, codes, positions); |
| } |
| } |
| else |
| { |
| translate(x, y); |
| fill(gv.getOutline()); |
| translate(-x, -y); |
| } |
| } |
| |
| public void drawString(AttributedCharacterIterator ci, float x, float y) |
| { |
| GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci); |
| drawGlyphVector(gv, x, y); |
| } |
| |
| /** |
| * Should perhaps be contexct dependent, but this is left for now as an |
| * overloadable default implementation. |
| */ |
| public FontRenderContext getFontRenderContext() |
| { |
| return new FontRenderContext(transform, true, true); |
| } |
| |
| // Until such time as pango is happy to talk directly to cairo, we |
| // actually need to redirect some calls from the GtkFontPeer and |
| // GtkFontMetrics into the drawing kit and ask cairo ourselves. |
| |
| public FontMetrics getFontMetrics() |
| { |
| return getFontMetrics(getFont()); |
| } |
| |
| public FontMetrics getFontMetrics(Font f) |
| { |
| // the reason we go via the toolkit here is to try to get |
| // a cached object. the toolkit keeps such a cache. |
| return Toolkit.getDefaultToolkit().getFontMetrics(f); |
| } |
| |
| public void setFont(Font f) |
| { |
| // Sun's JDK does not throw NPEs, instead it leaves the current setting |
| // unchanged. So do we. |
| if (f == null) |
| return; |
| |
| if (f.getPeer() instanceof GdkFontPeer) |
| font = f; |
| else |
| font = |
| ((ClasspathToolkit)(Toolkit.getDefaultToolkit())) |
| .getFont(f.getName(), f.getAttributes()); |
| } |
| |
| public Font getFont() |
| { |
| if (font == null) |
| return new Font("SansSerif", Font.PLAIN, 12); |
| return font; |
| } |
| |
| /////////////////////// MISC. PUBLIC METHODS ///////////////////////////////// |
| |
| public boolean hit(Rectangle rect, Shape s, boolean onStroke) |
| { |
| if( onStroke ) |
| { |
| Shape stroked = stroke.createStrokedShape( s ); |
| return stroked.intersects( (double)rect.x, (double)rect.y, |
| (double)rect.width, (double)rect.height ); |
| } |
| return s.intersects( (double)rect.x, (double)rect.y, |
| (double)rect.width, (double)rect.height ); |
| } |
| |
| public String toString() |
| { |
| return (getClass().getName() |
| + "[font=" + getFont().toString() |
| + ",color=" + fg.toString() |
| + "]"); |
| } |
| |
| ///////////////////////// PRIVATE METHODS /////////////////////////////////// |
| |
| /** |
| * All the drawImage() methods eventually get delegated here if the image |
| * is not a Cairo surface. |
| * |
| * @param bgcolor - if non-null draws the background color before |
| * drawing the image. |
| */ |
| private boolean drawRaster(ColorModel cm, Raster r, |
| AffineTransform imageToUser, Color bgcolor) |
| { |
| if (r == null) |
| return false; |
| |
| SampleModel sm = r.getSampleModel(); |
| DataBuffer db = r.getDataBuffer(); |
| |
| if (db == null || sm == null) |
| return false; |
| |
| if (cm == null) |
| cm = ColorModel.getRGBdefault(); |
| |
| double[] i2u = new double[6]; |
| if (imageToUser != null) |
| imageToUser.getMatrix(i2u); |
| else |
| { |
| i2u[0] = 1; |
| i2u[1] = 0; |
| i2u[2] = 0; |
| i2u[3] = 1; |
| i2u[4] = 0; |
| i2u[5] = 0; |
| } |
| |
| int[] pixels = findSimpleIntegerArray(cm, r); |
| |
| if (pixels == null) |
| { |
| // FIXME: I don't think this code will work correctly with a non-RGB |
| // MultiPixelPackedSampleModel. Although this entire method should |
| // probably be rewritten to better utilize Cairo's different supported |
| // data formats. |
| if (sm instanceof MultiPixelPackedSampleModel) |
| { |
| pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels); |
| for (int i = 0; i < pixels.length; i++) |
| pixels[i] = cm.getRGB(pixels[i]); |
| } |
| else |
| { |
| pixels = new int[r.getWidth() * r.getHeight()]; |
| for (int i = 0; i < pixels.length; i++) |
| pixels[i] = cm.getRGB(db.getElem(i)); |
| } |
| } |
| |
| // Change all transparent pixels in the image to the specified bgcolor, |
| // or (if there's no alpha) fill in an alpha channel so that it paints |
| // correctly. |
| if (cm.hasAlpha()) |
| { |
| if (bgcolor != null && cm.hasAlpha()) |
| for (int i = 0; i < pixels.length; i++) |
| { |
| if (cm.getAlpha(pixels[i]) == 0) |
| pixels[i] = bgcolor.getRGB(); |
| } |
| } |
| else |
| for (int i = 0; i < pixels.length; i++) |
| pixels[i] |= 0xFF000000; |
| |
| double alpha = 1.0; |
| if (comp instanceof AlphaComposite) |
| alpha = ((AlphaComposite) comp).getAlpha(); |
| drawPixels(nativePointer, pixels, r.getWidth(), r.getHeight(), |
| r.getWidth(), i2u, alpha); |
| |
| // Cairo seems to lose the current color which must be restored. |
| updateColor(); |
| |
| return true; |
| } |
| |
| /** |
| * Shifts coordinates by 0.5. |
| */ |
| private double shifted(double coord, boolean doShift) |
| { |
| if (doShift) |
| return Math.floor(coord) + 0.5; |
| else |
| return coord; |
| } |
| |
| /** |
| * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule. |
| */ |
| private void walkPath(PathIterator p, boolean doShift) |
| { |
| double x = 0; |
| double y = 0; |
| double[] coords = new double[6]; |
| |
| cairoSetFillRule(nativePointer, p.getWindingRule()); |
| for (; ! p.isDone(); p.next()) |
| { |
| int seg = p.currentSegment(coords); |
| switch (seg) |
| { |
| case PathIterator.SEG_MOVETO: |
| x = shifted(coords[0], doShift); |
| y = shifted(coords[1], doShift); |
| cairoMoveTo(nativePointer, x, y); |
| break; |
| case PathIterator.SEG_LINETO: |
| x = shifted(coords[0], doShift); |
| y = shifted(coords[1], doShift); |
| cairoLineTo(nativePointer, x, y); |
| break; |
| case PathIterator.SEG_QUADTO: |
| // splitting a quadratic bezier into a cubic: |
| // see: http://pfaedit.sourceforge.net/bezier.html |
| double x1 = x + (2.0 / 3.0) * (shifted(coords[0], doShift) - x); |
| double y1 = y + (2.0 / 3.0) * (shifted(coords[1], doShift) - y); |
| |
| double x2 = x1 + (1.0 / 3.0) * (shifted(coords[2], doShift) - x); |
| double y2 = y1 + (1.0 / 3.0) * (shifted(coords[3], doShift) - y); |
| |
| x = shifted(coords[2], doShift); |
| y = shifted(coords[3], doShift); |
| cairoCurveTo(nativePointer, x1, y1, x2, y2, x, y); |
| break; |
| case PathIterator.SEG_CUBICTO: |
| x = shifted(coords[4], doShift); |
| y = shifted(coords[5], doShift); |
| cairoCurveTo(nativePointer, shifted(coords[0], doShift), |
| shifted(coords[1], doShift), |
| shifted(coords[2], doShift), |
| shifted(coords[3], doShift), x, y); |
| break; |
| case PathIterator.SEG_CLOSE: |
| cairoClosePath(nativePointer); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Used by setRenderingHints() |
| */ |
| private Map getDefaultHints() |
| { |
| HashMap defaultHints = new HashMap(); |
| |
| defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, |
| RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); |
| |
| defaultHints.put(RenderingHints.KEY_STROKE_CONTROL, |
| RenderingHints.VALUE_STROKE_DEFAULT); |
| |
| defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS, |
| RenderingHints.VALUE_FRACTIONALMETRICS_OFF); |
| |
| defaultHints.put(RenderingHints.KEY_ANTIALIASING, |
| RenderingHints.VALUE_ANTIALIAS_OFF); |
| |
| defaultHints.put(RenderingHints.KEY_RENDERING, |
| RenderingHints.VALUE_RENDER_DEFAULT); |
| |
| return defaultHints; |
| } |
| |
| /** |
| * Used by drawRaster and GdkPixbufDecoder |
| */ |
| public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster) |
| { |
| if (cm == null || raster == null) |
| return null; |
| |
| if (! cm.getColorSpace().isCS_sRGB()) |
| return null; |
| |
| if (! (cm instanceof DirectColorModel)) |
| return null; |
| |
| DirectColorModel dcm = (DirectColorModel) cm; |
| |
| if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00 |
| || dcm.getBlueMask() != 0x000000FF) |
| return null; |
| |
| if (! (raster instanceof WritableRaster)) |
| return null; |
| |
| if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT) |
| return null; |
| |
| if (! (raster.getDataBuffer() instanceof DataBufferInt)) |
| return null; |
| |
| DataBufferInt db = (DataBufferInt) raster.getDataBuffer(); |
| |
| if (db.getNumBanks() != 1) |
| return null; |
| |
| // Finally, we have determined that this is a single bank, [A]RGB-int |
| // buffer in sRGB space. It's worth checking all this, because it means |
| // that cairo can paint directly into the data buffer, which is very |
| // fast compared to all the normal copying and converting. |
| |
| return db.getData(); |
| } |
| |
| /** |
| * 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 == null) |
| return; |
| |
| if (! (clip instanceof GeneralPath)) |
| clip = new GeneralPath(clip); |
| |
| GeneralPath p = (GeneralPath) clip; |
| p.transform(t); |
| } |
| |
| 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; |
| } |
| } |