Merged with mainline at revision 128810.
[official-gcc.git] / libjava / classpath / gnu / java / awt / peer / gtk / CairoGraphics2D.java
blobdb8acd1cda04c3f4691779db950d181b37b25e8f
1 /* CairoGraphics2D.java --
2 Copyright (C) 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu.java.awt.peer.gtk;
41 import gnu.java.awt.ClasspathToolkit;
43 import java.awt.AWTPermission;
44 import java.awt.AlphaComposite;
45 import java.awt.BasicStroke;
46 import java.awt.Color;
47 import java.awt.Composite;
48 import java.awt.CompositeContext;
49 import java.awt.Font;
50 import java.awt.FontMetrics;
51 import java.awt.GradientPaint;
52 import java.awt.Graphics;
53 import java.awt.Graphics2D;
54 import java.awt.GraphicsConfiguration;
55 import java.awt.Image;
56 import java.awt.Paint;
57 import java.awt.PaintContext;
58 import java.awt.Point;
59 import java.awt.Polygon;
60 import java.awt.Rectangle;
61 import java.awt.RenderingHints;
62 import java.awt.Shape;
63 import java.awt.Stroke;
64 import java.awt.TexturePaint;
65 import java.awt.Toolkit;
66 import java.awt.font.FontRenderContext;
67 import java.awt.font.GlyphVector;
68 import java.awt.font.TextLayout;
69 import java.awt.geom.AffineTransform;
70 import java.awt.geom.Arc2D;
71 import java.awt.geom.Area;
72 import java.awt.geom.Ellipse2D;
73 import java.awt.geom.GeneralPath;
74 import java.awt.geom.Line2D;
75 import java.awt.geom.NoninvertibleTransformException;
76 import java.awt.geom.PathIterator;
77 import java.awt.geom.Point2D;
78 import java.awt.geom.Rectangle2D;
79 import java.awt.geom.RoundRectangle2D;
80 import java.awt.image.AffineTransformOp;
81 import java.awt.image.BufferedImage;
82 import java.awt.image.BufferedImageOp;
83 import java.awt.image.ColorModel;
84 import java.awt.image.DataBuffer;
85 import java.awt.image.DataBufferInt;
86 import java.awt.image.DirectColorModel;
87 import java.awt.image.ImageObserver;
88 import java.awt.image.ImageProducer;
89 import java.awt.image.ImagingOpException;
90 import java.awt.image.MultiPixelPackedSampleModel;
91 import java.awt.image.Raster;
92 import java.awt.image.RenderedImage;
93 import java.awt.image.SampleModel;
94 import java.awt.image.WritableRaster;
95 import java.awt.image.renderable.RenderContext;
96 import java.awt.image.renderable.RenderableImage;
97 import java.text.AttributedCharacterIterator;
98 import java.util.HashMap;
99 import java.util.Map;
102 * This is an abstract implementation of Graphics2D on Cairo.
104 * It should be subclassed for different Cairo contexts.
106 * Note for subclassers: Apart from the constructor (see comments below),
107 * The following abstract methods must be implemented:
109 * Graphics create()
110 * GraphicsConfiguration getDeviceConfiguration()
111 * copyArea(int x, int y, int width, int height, int dx, int dy)
113 * Also, dispose() must be overloaded to free any native datastructures
114 * used by subclass and in addition call super.dispose() to free the
115 * native cairographics2d structure and cairo_t.
117 * @author Sven de Marothy
119 public abstract class CairoGraphics2D extends Graphics2D
121 static
123 System.loadLibrary("gtkpeer");
127 * Important: This is a pointer to the native cairographics2d structure
129 * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
131 long nativePointer;
133 // Drawing state variables
135 * The current paint
137 Paint paint;
138 boolean customPaint;
141 * The current stroke
143 Stroke stroke;
146 * Current foreground and background color.
148 Color fg, bg;
151 * Current clip shape.
153 Shape clip;
156 * Current transform.
158 AffineTransform transform;
161 * Current font.
163 Font font;
166 * The current compositing context, if any.
168 Composite comp;
169 CompositeContext compCtx;
172 * Rendering hint map.
174 private RenderingHints hints;
177 * Status of the anti-alias flag in cairo.
179 private boolean antialias = false;
180 private boolean ignoreAA = false;
183 * Some operations (drawing rather than filling) require that their
184 * coords be shifted to land on 0.5-pixel boundaries, in order to land on
185 * "middle of pixel" coordinates and light up complete pixels.
187 protected boolean shiftDrawCalls = false;
190 * Keep track if the first clip to be set, which is restored on setClip(null);
192 private boolean firstClip = true;
193 private Shape originalClip;
196 * Stroke used for 3DRects
198 private static BasicStroke draw3DRectStroke = new BasicStroke();
200 static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
201 static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF,
202 0xFF000000);
205 * Native constants for interpolation methods.
206 * Note, this corresponds to an enum in native/jni/gtk-peer/cairographics2d.h
208 public static final int INTERPOLATION_NEAREST = 0,
209 INTERPOLATION_BILINEAR = 1,
210 INTERPOLATION_BICUBIC = 5,
211 ALPHA_INTERPOLATION_SPEED = 2,
212 ALPHA_INTERPOLATION_QUALITY = 3,
213 ALPHA_INTERPOLATION_DEFAULT = 4;
214 // TODO: Does ALPHA_INTERPOLATION really correspond to CAIRO_FILTER_FAST/BEST/GOOD?
217 * Constructor does nothing.
219 public CairoGraphics2D()
224 * Sets up the default values and allocates the native cairographics2d structure
225 * @param cairo_t_pointer, a native pointer to a cairo_t of the context.
227 public void setup(long cairo_t_pointer)
229 nativePointer = init(cairo_t_pointer);
230 setRenderingHints(new RenderingHints(getDefaultHints()));
231 setFont(new Font("SansSerif", Font.PLAIN, 12));
232 setColor(Color.black);
233 setBackground(Color.white);
234 setPaint(Color.black);
235 setStroke(new BasicStroke());
236 setTransform(new AffineTransform());
237 cairoSetAntialias(nativePointer, antialias);
241 * Same as above, but copies the state of another CairoGraphics2D.
243 public void copy(CairoGraphics2D g, long cairo_t_pointer)
245 nativePointer = init(cairo_t_pointer);
246 paint = g.paint;
247 stroke = g.stroke;
248 setRenderingHints(g.hints);
250 Color foreground;
252 if (g.fg.getAlpha() != -1)
253 foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
254 g.fg.getAlpha());
255 else
256 foreground = new Color(g.fg.getRGB());
258 if (g.bg != null)
260 if (g.bg.getAlpha() != -1)
261 bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
262 g.bg.getAlpha());
263 else
264 bg = new Color(g.bg.getRGB());
267 firstClip = g.firstClip;
268 originalClip = g.originalClip;
269 clip = g.getClip();
271 if (g.transform == null)
272 transform = null;
273 else
274 transform = new AffineTransform(g.transform);
276 setFont(g.font);
277 setColor(foreground);
278 setBackground(bg);
279 setPaint(paint);
280 setStroke(stroke);
281 setTransformImpl(transform);
282 setClip(clip);
283 setComposite(comp);
285 antialias = !g.antialias;
286 setAntialias(g.antialias);
290 * Generic destructor - call the native dispose() method.
292 public void finalize()
294 dispose();
298 * Disposes the native cairographics2d structure, including the
299 * cairo_t and any gradient stuff, if allocated.
300 * Subclasses should of course overload and call this if
301 * they have additional native structures.
303 public void dispose()
305 disposeNative(nativePointer);
306 nativePointer = 0;
307 if (compCtx != null)
308 compCtx.dispose();
312 * Allocate the cairographics2d structure and set the cairo_t pointer in it.
313 * @param pointer - a cairo_t pointer, casted to a long.
315 protected native long init(long pointer);
318 * These are declared abstract as there may be context-specific issues.
320 public abstract Graphics create();
322 public abstract GraphicsConfiguration getDeviceConfiguration();
324 protected abstract void copyAreaImpl(int x, int y, int width, int height,
325 int dx, int dy);
329 * Find the bounds of this graphics context, in device space.
331 * @return the bounds in device-space
333 protected abstract Rectangle2D getRealBounds();
335 ////// Native Methods ////////////////////////////////////////////////////
338 * Dispose of allocate native resouces.
340 public native void disposeNative(long pointer);
343 * Draw pixels as an RGBA int matrix
344 * @param w, h - width and height
345 * @param stride - stride of the array width
346 * @param i2u - affine transform array
348 protected native void drawPixels(long pointer, int[] pixels, int w, int h,
349 int stride, double[] i2u, double alpha,
350 int interpolation);
352 protected native void setGradient(long pointer, double x1, double y1,
353 double x2, double y2,
354 int r1, int g1, int b1, int a1, int r2,
355 int g2, int b2, int a2, boolean cyclic);
357 protected native void setPaintPixels(long pointer, int[] pixels, int w,
358 int h, int stride, boolean repeat,
359 int x, int y);
362 * Set the current transform matrix
364 protected native void cairoSetMatrix(long pointer, double[] m);
367 * Scaling method
369 protected native void cairoScale(long pointer, double x, double y);
372 * Set the compositing operator
374 protected native void cairoSetOperator(long pointer, int cairoOperator);
377 * Sets the current color in RGBA as a 0.0-1.0 double
379 protected native void cairoSetRGBAColor(long pointer, double red, double green,
380 double blue, double alpha);
383 * Sets the current winding rule in Cairo
385 protected native void cairoSetFillRule(long pointer, int cairoFillRule);
388 * Set the line style, cap, join and miter limit.
389 * Cap and join parameters are in the BasicStroke enumerations.
391 protected native void cairoSetLine(long pointer, double width, int cap,
392 int join, double miterLimit);
395 * Set the dash style
397 protected native void cairoSetDash(long pointer, double[] dashes, int ndash,
398 double offset);
401 * Draws a Glyph Vector
403 protected native void cairoDrawGlyphVector(long pointer, GdkFontPeer font,
404 float x, float y, int n,
405 int[] codes, float[] positions, long[] fontset);
408 * Set the font in cairo.
410 protected native void cairoSetFont(long pointer, GdkFontPeer font);
413 * Appends a rectangle to the current path
415 protected native void cairoRectangle(long pointer, double x, double y,
416 double width, double height);
419 * Appends an arc to the current path
421 protected native void cairoArc(long pointer, double x, double y,
422 double radius, double angle1, double angle2);
425 * Save / restore a cairo path
427 protected native void cairoSave(long pointer);
428 protected native void cairoRestore(long pointer);
431 * New current path
433 protected native void cairoNewPath(long pointer);
435 /**
436 * Close current path
438 protected native void cairoClosePath(long pointer);
440 /** moveTo */
441 protected native void cairoMoveTo(long pointer, double x, double y);
443 /** lineTo */
444 protected native void cairoLineTo(long pointer, double x, double y);
446 /** Cubic curve-to */
447 protected native void cairoCurveTo(long pointer, double x1, double y1,
448 double x2, double y2,
449 double x3, double y3);
452 * Stroke current path
454 protected native void cairoStroke(long pointer);
457 * Fill current path
459 protected native void cairoFill(long pointer, double alpha);
461 /**
462 * Clip current path
464 protected native void cairoClip(long pointer);
466 /**
467 * Clear clip
469 protected native void cairoResetClip(long pointer);
472 * Set antialias.
474 protected native void cairoSetAntialias(long pointer, boolean aa);
477 ///////////////////////// TRANSFORMS ///////////////////////////////////
479 * Set the current transform
481 public void setTransform(AffineTransform tx)
483 // Transform clip into target space using the old transform.
484 updateClip(transform);
486 // Update the native transform.
487 setTransformImpl(tx);
489 // Transform the clip back into user space using the inverse new transform.
492 updateClip(transform.createInverse());
494 catch (NoninvertibleTransformException ex)
496 // TODO: How can we deal properly with this?
497 ex.printStackTrace();
500 if (clip != null)
501 setClip(clip);
504 private void setTransformImpl(AffineTransform tx)
506 transform = tx;
507 if (transform != null)
509 double[] m = new double[6];
510 transform.getMatrix(m);
511 cairoSetMatrix(nativePointer, m);
515 public void transform(AffineTransform tx)
517 if (transform == null)
518 transform = new AffineTransform(tx);
519 else
520 transform.concatenate(tx);
522 if (clip != null)
526 AffineTransform clipTransform = tx.createInverse();
527 updateClip(clipTransform);
529 catch (NoninvertibleTransformException ex)
531 // TODO: How can we deal properly with this?
532 ex.printStackTrace();
536 setTransformImpl(transform);
539 public void rotate(double theta)
541 transform(AffineTransform.getRotateInstance(theta));
544 public void rotate(double theta, double x, double y)
546 transform(AffineTransform.getRotateInstance(theta, x, y));
549 public void scale(double sx, double sy)
551 transform(AffineTransform.getScaleInstance(sx, sy));
555 * Translate the system of the co-ordinates. As translation is a frequent
556 * operation, it is done in an optimised way, unlike scaling and rotating.
558 public void translate(double tx, double ty)
560 if (transform != null)
561 transform.translate(tx, ty);
562 else
563 transform = AffineTransform.getTranslateInstance(tx, ty);
565 if (clip != null)
567 // FIXME: this should actuall try to transform the shape
568 // rather than degrade to bounds.
569 if (clip instanceof Rectangle2D)
571 Rectangle2D r = (Rectangle2D) clip;
572 r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(),
573 r.getHeight());
575 else
577 AffineTransform clipTransform =
578 AffineTransform.getTranslateInstance(-tx, -ty);
579 updateClip(clipTransform);
583 setTransformImpl(transform);
586 public void translate(int x, int y)
588 translate((double) x, (double) y);
591 public void shear(double shearX, double shearY)
593 transform(AffineTransform.getShearInstance(shearX, shearY));
596 ///////////////////////// DRAWING STATE ///////////////////////////////////
598 public void clip(Shape s)
600 // Do not touch clip when s == null.
601 if (s == null)
603 // The spec says this should clear the clip. The reference
604 // implementation throws a NullPointerException instead. I think,
605 // in this case we should conform to the specs, as it shouldn't
606 // affect compatibility.
607 setClip(null);
608 return;
611 // If the current clip is still null, initialize it.
612 if (clip == null)
614 clip = getRealBounds();
617 // This is so common, let's optimize this.
618 if (clip instanceof Rectangle2D && s instanceof Rectangle2D)
620 Rectangle2D clipRect = (Rectangle2D) clip;
621 Rectangle2D r = (Rectangle2D) s;
622 Rectangle2D.intersect(clipRect, r, clipRect);
623 setClip(clipRect);
625 else
627 Area current;
628 if (clip instanceof Area)
629 current = (Area) clip;
630 else
631 current = new Area(clip);
633 Area intersect;
634 if (s instanceof Area)
635 intersect = (Area) s;
636 else
637 intersect = new Area(s);
639 current.intersect(intersect);
640 clip = current;
641 // Call setClip so that the native side gets notified.
642 setClip(clip);
646 public Paint getPaint()
648 return paint;
651 public AffineTransform getTransform()
653 return (AffineTransform) transform.clone();
656 public void setPaint(Paint p)
658 if (p == null)
659 return;
661 paint = p;
662 if (paint instanceof Color)
664 setColor((Color) paint);
665 customPaint = false;
668 else if (paint instanceof TexturePaint)
670 TexturePaint tp = (TexturePaint) paint;
671 BufferedImage img = tp.getImage();
673 // map the image to the anchor rectangle
674 int width = (int) tp.getAnchorRect().getWidth();
675 int height = (int) tp.getAnchorRect().getHeight();
677 double scaleX = width / (double) img.getWidth();
678 double scaleY = height / (double) img.getHeight();
680 AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
681 AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
682 BufferedImage texture = op.filter(img, null);
683 int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
684 setPaintPixels(nativePointer, pixels, width, height, width, true, 0, 0);
685 customPaint = false;
688 else if (paint instanceof GradientPaint)
690 GradientPaint gp = (GradientPaint) paint;
691 Point2D p1 = gp.getPoint1();
692 Point2D p2 = gp.getPoint2();
693 Color c1 = gp.getColor1();
694 Color c2 = gp.getColor2();
695 setGradient(nativePointer, p1.getX(), p1.getY(), p2.getX(), p2.getY(),
696 c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(),
697 c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(),
698 gp.isCyclic());
699 customPaint = false;
701 else
703 customPaint = true;
708 * Sets a custom paint
710 * @param bounds the bounding box, in user space
712 protected void setCustomPaint(Rectangle bounds)
714 if (paint instanceof Color || paint instanceof TexturePaint
715 || paint instanceof GradientPaint)
716 return;
718 int userX = bounds.x;
719 int userY = bounds.y;
720 int userWidth = bounds.width;
721 int userHeight = bounds.height;
723 // Find bounds in device space
724 Rectangle2D bounds2D = getTransformedBounds(bounds, transform);
725 int deviceX = (int)bounds2D.getX();
726 int deviceY = (int)bounds2D.getY();
727 int deviceWidth = (int)Math.ceil(bounds2D.getWidth());
728 int deviceHeight = (int)Math.ceil(bounds2D.getHeight());
730 // Get raster of the paint background
731 PaintContext pc = paint.createContext(CairoSurface.cairoColorModel,
732 new Rectangle(deviceX, deviceY,
733 deviceWidth,
734 deviceHeight),
735 bounds,
736 transform, hints);
738 Raster raster = pc.getRaster(deviceX, deviceY, deviceWidth,
739 deviceHeight);
741 // Clear the transform matrix in Cairo, since the raster returned by the
742 // PaintContext is already in device-space
743 AffineTransform oldTx = new AffineTransform(transform);
744 setTransformImpl(new AffineTransform());
746 // Set pixels in cairo, aligning the top-left of the background image
747 // to the top-left corner in device space
748 if (pc.getColorModel().equals(CairoSurface.cairoColorModel)
749 && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
751 // Use a fast copy if the paint context can uses a Cairo-compatible
752 // color model
753 setPaintPixels(nativePointer,
754 (int[])raster.getDataElements(0, 0, deviceWidth,
755 deviceHeight, null),
756 deviceWidth, deviceHeight, deviceWidth, false,
757 deviceX, deviceY);
760 else if (pc.getColorModel().equals(CairoSurface.cairoCM_opaque)
761 && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
763 // We can also optimize if the context uses a similar color model
764 // but without an alpha channel; we just add the alpha
765 int[] pixels = (int[])raster.getDataElements(0, 0, deviceWidth,
766 deviceHeight, null);
768 for (int i = 0; i < pixels.length; i++)
769 pixels[i] = 0xff000000 | (pixels[i] & 0x00ffffff);
771 setPaintPixels(nativePointer, pixels, deviceWidth, deviceHeight,
772 deviceWidth, false, deviceX, deviceY);
775 else
777 // Fall back on wrapping the raster in a BufferedImage, and
778 // use BufferedImage.getRGB() to do color-model conversion
779 WritableRaster wr = Raster.createWritableRaster(raster.getSampleModel(),
780 new Point(raster.getMinX(),
781 raster.getMinY()));
782 wr.setRect(raster);
784 BufferedImage img2 = new BufferedImage(pc.getColorModel(), wr,
785 pc.getColorModel().isAlphaPremultiplied(),
786 null);
788 setPaintPixels(nativePointer,
789 img2.getRGB(0, 0, deviceWidth, deviceHeight, null, 0,
790 deviceWidth),
791 deviceWidth, deviceHeight, deviceWidth, false,
792 deviceX, deviceY);
795 // Restore transform
796 setTransformImpl(oldTx);
799 public Stroke getStroke()
801 return stroke;
804 public void setStroke(Stroke st)
806 stroke = st;
807 if (stroke instanceof BasicStroke)
809 BasicStroke bs = (BasicStroke) stroke;
810 cairoSetLine(nativePointer, bs.getLineWidth(), bs.getEndCap(),
811 bs.getLineJoin(), bs.getMiterLimit());
813 float[] dashes = bs.getDashArray();
814 if (dashes != null)
816 double[] double_dashes = new double[dashes.length];
817 for (int i = 0; i < dashes.length; i++)
818 double_dashes[i] = dashes[i];
820 cairoSetDash(nativePointer, double_dashes, double_dashes.length,
821 (double) bs.getDashPhase());
823 else
824 cairoSetDash(nativePointer, new double[0], 0, 0.0);
829 * Utility method to find the bounds of a shape, including the stroke width.
831 * @param s the shape
832 * @return the bounds of the shape, including stroke width
834 protected Rectangle findStrokedBounds(Shape s)
836 Rectangle r = s.getBounds();
838 if (stroke instanceof BasicStroke)
840 int strokeWidth = (int)Math.ceil(((BasicStroke)stroke).getLineWidth());
841 r.x -= strokeWidth / 2;
842 r.y -= strokeWidth / 2;
843 r.height += strokeWidth;
844 r.width += strokeWidth;
846 else
848 Shape s2 = stroke.createStrokedShape(s);
849 r = s2.getBounds();
852 return r;
855 public void setPaintMode()
857 setComposite(AlphaComposite.SrcOver);
860 public void setXORMode(Color c)
862 // FIXME: implement
865 public void setColor(Color c)
867 if (c == null)
868 c = Color.BLACK;
870 fg = c;
871 paint = c;
872 updateColor();
876 * Set the current fg value as the cairo color.
878 void updateColor()
880 if (fg == null)
881 fg = Color.BLACK;
883 cairoSetRGBAColor(nativePointer, fg.getRed() / 255.0,
884 fg.getGreen() / 255.0,fg.getBlue() / 255.0,
885 fg.getAlpha() / 255.0);
888 public Color getColor()
890 return fg;
893 public void clipRect(int x, int y, int width, int height)
895 if (clip == null)
896 setClip(new Rectangle(x, y, width, height));
897 else if (clip instanceof Rectangle)
899 computeIntersection(x, y, width, height, (Rectangle) clip);
900 setClip(clip);
902 else
903 clip(new Rectangle(x, y, width, height));
906 public Shape getClip()
908 if (clip == null)
909 return null;
910 else if (clip instanceof Rectangle2D)
911 return clip.getBounds2D(); //getClipInDevSpace();
912 else
914 GeneralPath p = new GeneralPath();
915 PathIterator pi = clip.getPathIterator(null);
916 p.append(pi, false);
917 return p;
921 public Rectangle getClipBounds()
923 if (clip == null)
924 return null;
925 else
926 return clip.getBounds();
929 protected Rectangle2D getClipInDevSpace()
931 Rectangle2D uclip = clip.getBounds2D();
932 if (transform == null)
933 return uclip;
934 else
935 return getTransformedBounds(clip.getBounds2D(), transform);
938 public void setClip(int x, int y, int width, int height)
940 if( width < 0 || height < 0 )
941 return;
943 setClip(new Rectangle2D.Double(x, y, width, height));
946 public void setClip(Shape s)
948 // The first time the clip is set, save it as the original clip
949 // to reset to on s == null. We can rely on this being non-null
950 // because the constructor in subclasses is expected to set the
951 // initial clip properly.
952 if( firstClip )
954 originalClip = s;
955 firstClip = false;
958 clip = s;
959 cairoResetClip(nativePointer);
961 if (clip != null)
963 cairoNewPath(nativePointer);
964 if (clip instanceof Rectangle2D)
966 Rectangle2D r = (Rectangle2D) clip;
967 cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(),
968 r.getHeight());
970 else
971 walkPath(clip.getPathIterator(null), false);
973 cairoClip(nativePointer);
977 public void setBackground(Color c)
979 if (c == null)
980 c = Color.WHITE;
981 bg = c;
984 public Color getBackground()
986 return bg;
990 * Return the current composite.
992 public Composite getComposite()
994 if (comp == null)
995 return AlphaComposite.SrcOver;
996 else
997 return comp;
1001 * Sets the current composite context.
1003 public void setComposite(Composite comp)
1005 if (this.comp == comp)
1006 return;
1008 this.comp = comp;
1009 if (compCtx != null)
1010 compCtx.dispose();
1011 compCtx = null;
1013 if (comp instanceof AlphaComposite)
1015 AlphaComposite a = (AlphaComposite) comp;
1016 cairoSetOperator(nativePointer, a.getRule());
1019 else
1021 cairoSetOperator(nativePointer, AlphaComposite.SRC_OVER);
1023 if (comp != null)
1025 // FIXME: this check is only required "if this Graphics2D
1026 // context is drawing to a Component on the display screen".
1027 SecurityManager sm = System.getSecurityManager();
1028 if (sm != null)
1029 sm.checkPermission(new AWTPermission("readDisplayPixels"));
1031 compCtx = comp.createContext(getBufferCM(), getNativeCM(), hints);
1037 * Returns the Colour Model describing the native, raw image data for this
1038 * specific peer.
1040 * @return ColorModel the ColorModel of native data in this peer
1042 protected abstract ColorModel getNativeCM();
1045 * Returns the Color Model describing the buffer that this peer uses
1046 * for custom composites.
1048 * @return ColorModel the ColorModel of the composite buffer in this peer.
1050 protected ColorModel getBufferCM()
1052 // This may be overridden by some subclasses
1053 return getNativeCM();
1056 ///////////////////////// DRAWING PRIMITIVES ///////////////////////////////////
1058 public void draw(Shape s)
1060 if ((stroke != null && ! (stroke instanceof BasicStroke))
1061 || (comp instanceof AlphaComposite && ((AlphaComposite) comp).getAlpha() != 1.0))
1063 // Cairo doesn't support stroking with alpha, so we create the stroked
1064 // shape and fill with alpha instead
1065 fill(stroke.createStrokedShape(s));
1066 return;
1069 if (customPaint)
1071 Rectangle r = findStrokedBounds(s);
1072 setCustomPaint(r);
1075 setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING)
1076 .equals(RenderingHints.VALUE_ANTIALIAS_OFF));
1077 createPath(s, true);
1078 cairoStroke(nativePointer);
1081 public void fill(Shape s)
1083 createPath(s, false);
1085 if (customPaint)
1086 setCustomPaint(s.getBounds());
1088 setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING)
1089 .equals(RenderingHints.VALUE_ANTIALIAS_OFF));
1090 double alpha = 1.0;
1091 if (comp instanceof AlphaComposite)
1092 alpha = ((AlphaComposite) comp).getAlpha();
1093 cairoFill(nativePointer, alpha);
1096 private void createPath(Shape s, boolean isDraw)
1098 cairoNewPath(nativePointer);
1100 // Optimize rectangles, since there is a direct Cairo function
1101 if (s instanceof Rectangle2D)
1103 Rectangle2D r = (Rectangle2D) s;
1105 // Pixels need to be shifted in draw operations to ensure that they
1106 // light up entire pixels, but we also need to make sure the rectangle
1107 // does not get distorted by this shifting operation
1108 double x = shiftX(r.getX(),shiftDrawCalls && isDraw);
1109 double y = shiftY(r.getY(), shiftDrawCalls && isDraw);
1110 double w = Math.round(r.getWidth());
1111 double h = Math.round(r.getHeight());
1112 cairoRectangle(nativePointer, x, y, w, h);
1115 // Lines are easy too
1116 else if (s instanceof Line2D)
1118 Line2D l = (Line2D) s;
1119 cairoMoveTo(nativePointer, shiftX(l.getX1(), shiftDrawCalls && isDraw),
1120 shiftY(l.getY1(), shiftDrawCalls && isDraw));
1121 cairoLineTo(nativePointer, shiftX(l.getX2(), shiftDrawCalls && isDraw),
1122 shiftY(l.getY2(), shiftDrawCalls && isDraw));
1125 // We can optimize ellipses too; however we don't bother optimizing arcs:
1126 // the iterator is fast enough (an ellipse requires 5 steps using the
1127 // iterator, while most arcs are only 2-3)
1128 else if (s instanceof Ellipse2D)
1130 Ellipse2D e = (Ellipse2D) s;
1132 double radius = Math.min(e.getHeight(), e.getWidth()) / 2;
1134 // Cairo only draws circular shapes, but we can use a stretch to make
1135 // them into ellipses
1136 double xscale = 1, yscale = 1;
1137 if (e.getHeight() != e.getWidth())
1139 cairoSave(nativePointer);
1141 if (e.getHeight() < e.getWidth())
1142 xscale = e.getWidth() / (radius * 2);
1143 else
1144 yscale = e.getHeight() / (radius * 2);
1146 if (xscale != 1 || yscale != 1)
1147 cairoScale(nativePointer, xscale, yscale);
1150 cairoArc(nativePointer,
1151 shiftX(e.getCenterX() / xscale, shiftDrawCalls && isDraw),
1152 shiftY(e.getCenterY() / yscale, shiftDrawCalls && isDraw),
1153 radius, 0, Math.PI * 2);
1155 if (xscale != 1 || yscale != 1)
1156 cairoRestore(nativePointer);
1159 // All other shapes are broken down and drawn in steps using the
1160 // PathIterator
1161 else
1162 walkPath(s.getPathIterator(null), shiftDrawCalls && isDraw);
1166 * Note that the rest of the drawing methods go via fill() or draw() for the drawing,
1167 * although subclasses may with to overload these methods where context-specific
1168 * optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int)
1171 public void clearRect(int x, int y, int width, int height)
1173 if (bg != null)
1174 cairoSetRGBAColor(nativePointer, bg.getRed() / 255.0,
1175 bg.getGreen() / 255.0, bg.getBlue() / 255.0,
1176 bg.getAlpha() / 255.0);
1178 Composite oldcomp = comp;
1179 setComposite(AlphaComposite.Src);
1180 fillRect(x, y, width, height);
1182 setComposite(oldcomp);
1183 updateColor();
1186 public void draw3DRect(int x, int y, int width, int height, boolean raised)
1188 Stroke tmp = stroke;
1189 setStroke(draw3DRectStroke);
1190 super.draw3DRect(x, y, width, height, raised);
1191 setStroke(tmp);
1194 public void drawArc(int x, int y, int width, int height, int startAngle,
1195 int arcAngle)
1197 draw(new Arc2D.Double((double) x, (double) y, (double) width,
1198 (double) height, (double) startAngle,
1199 (double) arcAngle, Arc2D.OPEN));
1202 public void drawLine(int x1, int y1, int x2, int y2)
1204 // The coordinates being pairwise identical means one wants
1205 // to draw a single pixel. This is emulated by drawing
1206 // a one pixel sized rectangle.
1207 if (x1 == x2 && y1 == y2)
1208 fill(new Rectangle(x1, y1, 1, 1));
1209 else
1210 draw(new Line2D.Double(x1, y1, x2, y2));
1213 public void drawRect(int x, int y, int width, int height)
1215 draw(new Rectangle(x, y, width, height));
1218 public void fillArc(int x, int y, int width, int height, int startAngle,
1219 int arcAngle)
1221 fill(new Arc2D.Double((double) x, (double) y, (double) width,
1222 (double) height, (double) startAngle,
1223 (double) arcAngle, Arc2D.PIE));
1226 public void fillRect(int x, int y, int width, int height)
1228 fill (new Rectangle(x, y, width, height));
1231 public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1233 fill(new Polygon(xPoints, yPoints, nPoints));
1236 public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1238 draw(new Polygon(xPoints, yPoints, nPoints));
1241 public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1243 draw(new Polygon(xPoints, yPoints, nPoints));
1246 public void drawOval(int x, int y, int width, int height)
1248 drawArc(x, y, width, height, 0, 360);
1251 public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
1252 int arcHeight)
1254 draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
1257 public void fillOval(int x, int y, int width, int height)
1259 fillArc(x, y, width, height, 0, 360);
1262 public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
1263 int arcHeight)
1265 fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
1269 * CopyArea - performs clipping to the native surface as a convenience
1270 * (requires getRealBounds). Then calls copyAreaImpl.
1272 public void copyArea(int ox, int oy, int owidth, int oheight,
1273 int odx, int ody)
1275 // FIXME: does this handle a rotation transform properly?
1276 // (the width/height might not be correct)
1277 Point2D pos = transform.transform(new Point2D.Double(ox, oy),
1278 (Point2D) null);
1279 Point2D dim = transform.transform(new Point2D.Double(ox + owidth,
1280 oy + oheight),
1281 (Point2D) null);
1282 Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody),
1283 (Point2D) null);
1284 int x = (int)pos.getX();
1285 int y = (int)pos.getY();
1286 int width = (int)(dim.getX() - pos.getX());
1287 int height = (int)(dim.getY() - pos.getY());
1288 int dx = (int)(p2.getX() - pos.getX());
1289 int dy = (int)(p2.getY() - pos.getY());
1291 Rectangle2D r = getRealBounds();
1293 if( width <= 0 || height <= 0 )
1294 return;
1295 // Return if outside the surface
1296 if( x + dx > r.getWidth() || y + dy > r.getHeight() )
1297 return;
1299 if( x + dx + width < r.getX() || y + dy + height < r.getY() )
1300 return;
1302 // Clip edges if necessary
1303 if( x + dx < r.getX() ) // left
1305 width = x + dx + width;
1306 x = (int)r.getX() - dx;
1309 if( y + dy < r.getY() ) // top
1311 height = y + dy + height;
1312 y = (int)r.getY() - dy;
1315 if( x + dx + width >= r.getWidth() ) // right
1316 width = (int)r.getWidth() - dx - x;
1318 if( y + dy + height >= r.getHeight() ) // bottom
1319 height = (int)r.getHeight() - dy - y;
1321 copyAreaImpl(x, y, width, height, dx, dy);
1324 ///////////////////////// RENDERING HINTS ///////////////////////////////////
1326 public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
1328 hints.put(hintKey, hintValue);
1330 shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1331 || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1334 public Object getRenderingHint(RenderingHints.Key hintKey)
1336 return hints.get(hintKey);
1339 public void setRenderingHints(Map<?,?> hints)
1341 this.hints = new RenderingHints(getDefaultHints());
1342 this.hints.putAll(hints);
1344 shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1345 || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1347 if (compCtx != null)
1349 compCtx.dispose();
1350 compCtx = comp.createContext(getNativeCM(), getNativeCM(), this.hints);
1354 public void addRenderingHints(Map hints)
1356 this.hints.putAll(hints);
1359 public RenderingHints getRenderingHints()
1361 return hints;
1364 private int getInterpolation()
1366 if (this.hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1367 return INTERPOLATION_NEAREST;
1369 else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1370 return INTERPOLATION_BILINEAR;
1372 else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC))
1373 return INTERPOLATION_BICUBIC;
1375 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1376 return ALPHA_INTERPOLATION_SPEED;
1378 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1379 return ALPHA_INTERPOLATION_QUALITY;
1381 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1382 return ALPHA_INTERPOLATION_DEFAULT;
1384 // Do bilinear interpolation as default
1385 return INTERPOLATION_BILINEAR;
1389 * Set antialias if needed. If the ignoreAA flag is set, this method will
1390 * return without doing anything.
1392 * @param needAA RenderingHints.VALUE_ANTIALIAS_ON or RenderingHints.VALUE_ANTIALIAS_OFF
1394 private void setAntialias(boolean needAA)
1396 if (ignoreAA)
1397 return;
1399 if (needAA != antialias)
1401 antialias = !antialias;
1402 cairoSetAntialias(nativePointer, antialias);
1406 ///////////////////////// IMAGE. METHODS ///////////////////////////////////
1408 protected boolean drawImage(Image img, AffineTransform xform,
1409 Color bgcolor, ImageObserver obs)
1411 if (img == null)
1412 return false;
1414 if (xform == null)
1415 xform = new AffineTransform();
1417 // In this case, xform is an AffineTransform that transforms bounding
1418 // box of the specified image from image space to user space. However
1419 // when we pass this transform to cairo, cairo will use this transform
1420 // to map "user coordinates" to "pixel" coordinates, which is the
1421 // other way around. Therefore to get the "user -> pixel" transform
1422 // that cairo wants from "image -> user" transform that we currently
1423 // have, we will need to invert the transformation matrix.
1424 AffineTransform invertedXform;
1428 invertedXform = xform.createInverse();
1430 catch (NoninvertibleTransformException e)
1432 throw new ImagingOpException("Unable to invert transform "
1433 + xform.toString());
1436 // Unrecognized image - convert to a BufferedImage
1437 // Note - this can get us in trouble when the gdk lock is re-acquired.
1438 // for example by VolatileImage. See ComponentGraphics for how we work
1439 // around this.
1440 img = AsyncImage.realImage(img, obs);
1441 if( !(img instanceof BufferedImage) )
1443 ImageProducer source = img.getSource();
1444 if (source == null)
1445 return false;
1446 img = Toolkit.getDefaultToolkit().createImage(source);
1449 BufferedImage b = (BufferedImage) img;
1450 Raster raster;
1451 double[] i2u = new double[6];
1452 int width = b.getWidth();
1453 int height = b.getHeight();
1455 // If this BufferedImage has a BufferedImageGraphics object,
1456 // use the cached CairoSurface that BIG is drawing onto
1458 if( BufferedImageGraphics.bufferedImages.get( b ) != null )
1459 raster = BufferedImageGraphics.bufferedImages.get( b );
1460 else
1461 raster = b.getRaster();
1463 invertedXform.getMatrix(i2u);
1465 double alpha = 1.0;
1466 if (comp instanceof AlphaComposite)
1467 alpha = ((AlphaComposite) comp).getAlpha();
1469 if(raster instanceof CairoSurface
1470 && ((CairoSurface)raster).sharedBuffer == true)
1472 drawCairoSurface((CairoSurface)raster, xform, alpha, getInterpolation());
1473 updateColor();
1474 return true;
1477 if( bgcolor != null )
1479 Color oldColor = bg;
1480 setBackground(bgcolor);
1482 Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
1483 bounds = getTransformedBounds(bounds, xform);
1485 clearRect((int)bounds.getX(), (int)bounds.getY(),
1486 (int)bounds.getWidth(), (int)bounds.getHeight());
1488 setBackground(oldColor);
1491 int[] pixels = b.getRGB(0, 0, width, height, null, 0, width);
1492 // FIXME: The above method returns data in the standard ARGB colorspace,
1493 // meaning data should NOT be alpha pre-multiplied; however Cairo expects
1494 // data to be premultiplied.
1496 cairoSave(nativePointer);
1497 Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
1498 bounds = getTransformedBounds(bounds, xform);
1499 cairoRectangle(nativePointer, bounds.getX(), bounds.getY(),
1500 bounds.getWidth(), bounds.getHeight());
1501 cairoClip(nativePointer);
1503 drawPixels(nativePointer, pixels, width, height, width, i2u, alpha,
1504 getInterpolation());
1506 cairoRestore(nativePointer);
1508 // Cairo seems to lose the current color which must be restored.
1509 updateColor();
1510 return true;
1513 public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1515 drawRaster(image.getColorModel(), image.getData(), xform, null);
1518 public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1520 drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1523 public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1525 return drawImage(img, xform, null, obs);
1528 public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
1530 Image filtered = image;
1531 if (op != null)
1532 filtered = op.filter(image, null);
1533 drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null);
1536 public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1538 return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null,
1539 observer);
1542 public boolean drawImage(Image img, int x, int y, Color bgcolor,
1543 ImageObserver observer)
1545 return drawImage(img, x, y, img.getWidth(observer),
1546 img.getHeight(observer), bgcolor, observer);
1549 public boolean drawImage(Image img, int x, int y, int width, int height,
1550 Color bgcolor, ImageObserver observer)
1552 double scaleX = width / (double) img.getWidth(observer);
1553 double scaleY = height / (double) img.getHeight(observer);
1554 if( scaleX == 0 || scaleY == 0 )
1555 return true;
1557 return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y),
1558 bgcolor, observer);
1561 public boolean drawImage(Image img, int x, int y, int width, int height,
1562 ImageObserver observer)
1564 return drawImage(img, x, y, width, height, null, observer);
1567 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1568 int sx1, int sy1, int sx2, int sy2, Color bgcolor,
1569 ImageObserver observer)
1571 if (img == null)
1572 return false;
1574 int sourceWidth = sx2 - sx1;
1575 int sourceHeight = sy2 - sy1;
1577 int destWidth = dx2 - dx1;
1578 int destHeight = dy2 - dy1;
1580 if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 ||
1581 sourceHeight == 0)
1582 return true;
1584 double scaleX = destWidth / (double) sourceWidth;
1585 double scaleY = destHeight / (double) sourceHeight;
1587 // FIXME: Avoid using an AT if possible here - it's at least twice as slow.
1589 Shape oldClip = getClip();
1590 int cx, cy, cw, ch;
1591 if( dx1 < dx2 )
1592 { cx = dx1; cw = dx2 - dx1; }
1593 else
1594 { cx = dx2; cw = dx1 - dx2; }
1595 if( dy1 < dy2 )
1596 { cy = dy1; ch = dy2 - dy1; }
1597 else
1598 { cy = dy2; ch = dy1 - dy2; }
1600 clipRect( cx, cy, cw, ch );
1602 AffineTransform tx = new AffineTransform();
1603 tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY );
1604 tx.scale( scaleX, scaleY );
1606 boolean retval = drawImage(img, tx, bgcolor, observer);
1607 setClip( oldClip );
1608 return retval;
1611 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1612 int sx1, int sy1, int sx2, int sy2,
1613 ImageObserver observer)
1615 return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
1619 * Optimized method for drawing a CairoSurface onto this graphics context.
1621 * @param surface The surface to draw.
1622 * @param tx The transformation matrix (cannot be null).
1623 * @param alpha The alpha value to paint with ( 0 <= alpha <= 1).
1624 * @param interpolation The interpolation type.
1626 protected void drawCairoSurface(CairoSurface surface, AffineTransform tx,
1627 double alpha, int interpolation)
1629 // Find offset required if this surface is a sub-raster, and append offset
1630 // to transformation.
1631 if (surface.getSampleModelTranslateX() != 0
1632 || surface.getSampleModelTranslateY() != 0)
1634 Point2D origin = new Point2D.Double(0, 0);
1635 Point2D offset = new Point2D.Double(surface.getSampleModelTranslateX(),
1636 surface.getSampleModelTranslateY());
1638 tx.transform(origin, origin);
1639 tx.transform(offset, offset);
1641 tx.translate(offset.getX() - origin.getX(),
1642 offset.getY() - origin.getY());
1645 // Find dimensions of this surface relative to the root parent surface
1646 Rectangle bounds = new Rectangle(-surface.getSampleModelTranslateX(),
1647 -surface.getSampleModelTranslateY(),
1648 surface.width, surface.height);
1650 // Clip to the translated image
1651 // We use direct cairo methods to avoid the overhead of maintaining a
1652 // java copy of the clip, since we will be reverting it immediately
1653 // after drawing
1654 Shape newBounds = tx.createTransformedShape(bounds);
1655 cairoSave(nativePointer);
1656 walkPath(newBounds.getPathIterator(null), false);
1657 cairoClip(nativePointer);
1659 // Draw the surface
1662 double[] i2u = new double[6];
1663 tx.createInverse().getMatrix(i2u);
1664 surface.nativeDrawSurface(surface.surfacePointer, nativePointer, i2u,
1665 alpha, interpolation);
1667 catch (NoninvertibleTransformException ex)
1669 // This should never happen(?), so we don't need to do anything here.
1673 // Restore clip
1674 cairoRestore(nativePointer);
1678 ///////////////////////// TEXT METHODS ////////////////////////////////////
1680 public void drawString(String str, float x, float y)
1682 if (str == null || str.length() == 0)
1683 return;
1684 GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
1685 TextLayout tl = (TextLayout) fontPeer.textLayoutCache.get(str);
1686 if (tl == null)
1688 tl = new TextLayout( str, getFont(), getFontRenderContext() );
1689 fontPeer.textLayoutCache.put(str, tl);
1692 // Set antialias to text_antialiasing, and set the ignoreAA flag so that
1693 // the setting doesn't get overridden in a draw() or fill() call.
1694 setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING)
1695 .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
1696 ignoreAA = true;
1698 tl.draw(this, x, y);
1699 ignoreAA = false;
1702 public void drawString(String str, int x, int y)
1704 drawString (str, (float) x, (float) y);
1707 public void drawString(AttributedCharacterIterator ci, int x, int y)
1709 drawString (ci, (float) x, (float) y);
1712 public void drawGlyphVector(GlyphVector gv, float x, float y)
1714 double alpha = 1.0;
1716 if( gv.getNumGlyphs() <= 0 )
1717 return;
1719 if (customPaint)
1720 setCustomPaint(gv.getOutline().getBounds());
1722 if (comp instanceof AlphaComposite)
1723 alpha = ((AlphaComposite) comp).getAlpha();
1725 setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING)
1726 .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
1727 ignoreAA = true;
1729 if (gv instanceof FreetypeGlyphVector && alpha == 1.0
1730 && !((FreetypeGlyphVector)gv).hasTransforms())
1732 int n = gv.getNumGlyphs ();
1733 int[] codes = gv.getGlyphCodes (0, n, null);
1734 long[] fontset = ((FreetypeGlyphVector)gv).getGlyphFonts (0, n, null);
1735 float[] positions = gv.getGlyphPositions (0, n, null);
1737 setFont (gv.getFont ());
1738 GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
1739 synchronized (fontPeer)
1741 cairoDrawGlyphVector(nativePointer, fontPeer,
1742 x, y, n, codes, positions, fontset);
1745 else
1747 translate(x, y);
1748 fill(gv.getOutline());
1749 translate(-x, -y);
1752 ignoreAA = false;
1755 public void drawString(AttributedCharacterIterator ci, float x, float y)
1757 GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci);
1758 drawGlyphVector(gv, x, y);
1762 * Should perhaps be contexct dependent, but this is left for now as an
1763 * overloadable default implementation.
1765 public FontRenderContext getFontRenderContext()
1767 return new FontRenderContext(transform, true, true);
1770 // Until such time as pango is happy to talk directly to cairo, we
1771 // actually need to redirect some calls from the GtkFontPeer and
1772 // GtkFontMetrics into the drawing kit and ask cairo ourselves.
1774 public FontMetrics getFontMetrics()
1776 return getFontMetrics(getFont());
1779 public FontMetrics getFontMetrics(Font f)
1781 return ((GdkFontPeer) f.getPeer()).getFontMetrics(f);
1784 public void setFont(Font f)
1786 // Sun's JDK does not throw NPEs, instead it leaves the current setting
1787 // unchanged. So do we.
1788 if (f == null)
1789 return;
1791 if (f.getPeer() instanceof GdkFontPeer)
1792 font = f;
1793 else
1794 font =
1795 ((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
1796 .getFont(f.getName(), f.getAttributes());
1798 GdkFontPeer fontpeer = (GdkFontPeer) getFont().getPeer();
1799 synchronized (fontpeer)
1801 cairoSetFont(nativePointer, fontpeer);
1805 public Font getFont()
1807 if (font == null)
1808 return new Font("SansSerif", Font.PLAIN, 12);
1809 return font;
1812 /////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
1814 public boolean hit(Rectangle rect, Shape s, boolean onStroke)
1816 if( onStroke )
1818 Shape stroked = stroke.createStrokedShape( s );
1819 return stroked.intersects( (double)rect.x, (double)rect.y,
1820 (double)rect.width, (double)rect.height );
1822 return s.intersects( (double)rect.x, (double)rect.y,
1823 (double)rect.width, (double)rect.height );
1826 public String toString()
1828 return (getClass().getName()
1829 + "[font=" + getFont().toString()
1830 + ",color=" + fg.toString()
1831 + "]");
1834 ///////////////////////// PRIVATE METHODS ///////////////////////////////////
1837 * All the drawImage() methods eventually get delegated here if the image
1838 * is not a Cairo surface.
1840 * @param bgcolor - if non-null draws the background color before
1841 * drawing the image.
1843 private boolean drawRaster(ColorModel cm, Raster r,
1844 AffineTransform imageToUser, Color bgcolor)
1846 if (r == null)
1847 return false;
1849 SampleModel sm = r.getSampleModel();
1850 DataBuffer db = r.getDataBuffer();
1852 if (db == null || sm == null)
1853 return false;
1855 if (cm == null)
1856 cm = ColorModel.getRGBdefault();
1858 double[] i2u = new double[6];
1859 if (imageToUser != null)
1860 imageToUser.getMatrix(i2u);
1861 else
1863 i2u[0] = 1;
1864 i2u[1] = 0;
1865 i2u[2] = 0;
1866 i2u[3] = 1;
1867 i2u[4] = 0;
1868 i2u[5] = 0;
1871 int[] pixels = findSimpleIntegerArray(cm, r);
1873 if (pixels == null)
1875 // FIXME: I don't think this code will work correctly with a non-RGB
1876 // MultiPixelPackedSampleModel. Although this entire method should
1877 // probably be rewritten to better utilize Cairo's different supported
1878 // data formats.
1879 if (sm instanceof MultiPixelPackedSampleModel)
1881 pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
1882 for (int i = 0; i < pixels.length; i++)
1883 pixels[i] = cm.getRGB(pixels[i]);
1885 else
1887 pixels = new int[r.getWidth() * r.getHeight()];
1888 for (int i = 0; i < pixels.length; i++)
1889 pixels[i] = cm.getRGB(db.getElem(i));
1893 // Change all transparent pixels in the image to the specified bgcolor,
1894 // or (if there's no alpha) fill in an alpha channel so that it paints
1895 // correctly.
1896 if (cm.hasAlpha())
1898 if (bgcolor != null && cm.hasAlpha())
1899 for (int i = 0; i < pixels.length; i++)
1901 if (cm.getAlpha(pixels[i]) == 0)
1902 pixels[i] = bgcolor.getRGB();
1905 else
1906 for (int i = 0; i < pixels.length; i++)
1907 pixels[i] |= 0xFF000000;
1909 double alpha = 1.0;
1910 if (comp instanceof AlphaComposite)
1911 alpha = ((AlphaComposite) comp).getAlpha();
1913 drawPixels(nativePointer, pixels, r.getWidth(), r.getHeight(),
1914 r.getWidth(), i2u, alpha, getInterpolation());
1916 // Cairo seems to lose the current color which must be restored.
1917 updateColor();
1919 return true;
1923 * Shifts an x-coordinate by 0.5 in device space.
1925 private double shiftX(double coord, boolean doShift)
1927 if (doShift)
1929 double shift = 0.5;
1930 if (!transform.isIdentity())
1931 shift /= transform.getScaleX();
1932 return (coord + shift);
1934 else
1935 return coord;
1939 * Shifts a y-coordinate by 0.5 in device space.
1941 private double shiftY(double coord, boolean doShift)
1943 if (doShift)
1945 double shift = 0.5;
1946 if (!transform.isIdentity())
1947 shift /= transform.getScaleY();
1948 return (coord + shift);
1950 else
1951 return coord;
1955 * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
1957 private void walkPath(PathIterator p, boolean doShift)
1959 double x = 0;
1960 double y = 0;
1961 double[] coords = new double[6];
1963 cairoSetFillRule(nativePointer, p.getWindingRule());
1964 for (; ! p.isDone(); p.next())
1966 int seg = p.currentSegment(coords);
1967 switch (seg)
1969 case PathIterator.SEG_MOVETO:
1970 x = shiftX(coords[0], doShift);
1971 y = shiftY(coords[1], doShift);
1972 cairoMoveTo(nativePointer, x, y);
1973 break;
1974 case PathIterator.SEG_LINETO:
1975 x = shiftX(coords[0], doShift);
1976 y = shiftY(coords[1], doShift);
1977 cairoLineTo(nativePointer, x, y);
1978 break;
1979 case PathIterator.SEG_QUADTO:
1980 // splitting a quadratic bezier into a cubic:
1981 // see: http://pfaedit.sourceforge.net/bezier.html
1982 double x1 = x + (2.0 / 3.0) * (shiftX(coords[0], doShift) - x);
1983 double y1 = y + (2.0 / 3.0) * (shiftY(coords[1], doShift) - y);
1985 double x2 = x1 + (1.0 / 3.0) * (shiftX(coords[2], doShift) - x);
1986 double y2 = y1 + (1.0 / 3.0) * (shiftY(coords[3], doShift) - y);
1988 x = shiftX(coords[2], doShift);
1989 y = shiftY(coords[3], doShift);
1990 cairoCurveTo(nativePointer, x1, y1, x2, y2, x, y);
1991 break;
1992 case PathIterator.SEG_CUBICTO:
1993 x = shiftX(coords[4], doShift);
1994 y = shiftY(coords[5], doShift);
1995 cairoCurveTo(nativePointer, shiftX(coords[0], doShift),
1996 shiftY(coords[1], doShift),
1997 shiftX(coords[2], doShift),
1998 shiftY(coords[3], doShift), x, y);
1999 break;
2000 case PathIterator.SEG_CLOSE:
2001 cairoClosePath(nativePointer);
2002 break;
2008 * Used by setRenderingHints()
2010 private Map<RenderingHints.Key, Object> getDefaultHints()
2012 HashMap<RenderingHints.Key, Object> defaultHints =
2013 new HashMap<RenderingHints.Key, Object>();
2015 defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
2016 RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
2018 defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
2019 RenderingHints.VALUE_STROKE_DEFAULT);
2021 defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
2022 RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
2024 defaultHints.put(RenderingHints.KEY_ANTIALIASING,
2025 RenderingHints.VALUE_ANTIALIAS_OFF);
2027 defaultHints.put(RenderingHints.KEY_RENDERING,
2028 RenderingHints.VALUE_RENDER_DEFAULT);
2030 return defaultHints;
2034 * Used by drawRaster and GdkPixbufDecoder
2036 public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
2038 if (cm == null || raster == null)
2039 return null;
2041 if (! cm.getColorSpace().isCS_sRGB())
2042 return null;
2044 if (! (cm instanceof DirectColorModel))
2045 return null;
2047 DirectColorModel dcm = (DirectColorModel) cm;
2049 if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
2050 || dcm.getBlueMask() != 0x000000FF)
2051 return null;
2053 if (! (raster instanceof WritableRaster))
2054 return null;
2056 if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
2057 return null;
2059 if (! (raster.getDataBuffer() instanceof DataBufferInt))
2060 return null;
2062 DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
2064 if (db.getNumBanks() != 1)
2065 return null;
2067 // Finally, we have determined that this is a single bank, [A]RGB-int
2068 // buffer in sRGB space. It's worth checking all this, because it means
2069 // that cairo can paint directly into the data buffer, which is very
2070 // fast compared to all the normal copying and converting.
2072 return db.getData();
2076 * Helper method to transform the clip. This is called by the various
2077 * transformation-manipulation methods to update the clip (which is in
2078 * userspace) accordingly.
2080 * The transform usually is the inverse transform that was applied to the
2081 * graphics object.
2083 * @param t the transform to apply to the clip
2085 private void updateClip(AffineTransform t)
2087 if (clip == null)
2088 return;
2090 // If the clip is a rectangle, and the transformation preserves the shape
2091 // (translate/stretch only), then keep the clip as a rectangle
2092 double[] matrix = new double[4];
2093 t.getMatrix(matrix);
2094 if (clip instanceof Rectangle2D && matrix[1] == 0 && matrix[2] == 0)
2096 Rectangle2D rect = (Rectangle2D)clip;
2097 double[] origin = new double[] {rect.getX(), rect.getY()};
2098 double[] dimensions = new double[] {rect.getWidth(), rect.getHeight()};
2099 t.transform(origin, 0, origin, 0, 1);
2100 t.deltaTransform(dimensions, 0, dimensions, 0, 1);
2101 rect.setRect(origin[0], origin[1], dimensions[0], dimensions[1]);
2103 else
2105 if (! (clip instanceof GeneralPath))
2106 clip = new GeneralPath(clip);
2108 GeneralPath p = (GeneralPath) clip;
2109 p.transform(t);
2113 private static Rectangle computeIntersection(int x, int y, int w, int h,
2114 Rectangle rect)
2116 int x2 = (int) rect.x;
2117 int y2 = (int) rect.y;
2118 int w2 = (int) rect.width;
2119 int h2 = (int) rect.height;
2121 int dx = (x > x2) ? x : x2;
2122 int dy = (y > y2) ? y : y2;
2123 int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx);
2124 int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy);
2126 if (dw >= 0 && dh >= 0)
2127 rect.setBounds(dx, dy, dw, dh);
2128 else
2129 rect.setBounds(0, 0, 0, 0);
2131 return rect;
2134 static Rectangle2D getTransformedBounds(Rectangle2D bounds, AffineTransform tx)
2136 double x1 = bounds.getX();
2137 double x2 = bounds.getX() + bounds.getWidth();
2138 double x3 = x1;
2139 double x4 = x2;
2140 double y1 = bounds.getY();
2141 double y2 = y1;
2142 double y3 = bounds.getY() + bounds.getHeight();
2143 double y4 = y3;
2145 double[] points = new double[] {x1, y1, x2, y2, x3, y3, x4, y4};
2146 tx.transform(points, 0, points, 0, 4);
2148 double minX = points[0];
2149 double maxX = minX;
2150 double minY = points[1];
2151 double maxY = minY;
2152 for (int i = 0; i < 8; i++)
2154 if (points[i] < minX)
2155 minX = points[i];
2156 if (points[i] > maxX)
2157 maxX = points[i];
2158 i++;
2160 if (points[i] < minY)
2161 minY = points[i];
2162 if (points[i] > maxY)
2163 maxY = points[i];
2166 return new Rectangle2D.Double(minX, minY, (maxX - minX), (maxY - minY));