Merge with trank @ 137446
[official-gcc.git] / libjava / classpath / gnu / java / awt / peer / gtk / CairoGraphics2D.java
blobfcd3f6acd92bb3d159351bcff81a150ec69bd1f2
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.classpath.Configuration;
43 import gnu.java.awt.ClasspathToolkit;
45 import java.awt.AWTPermission;
46 import java.awt.AlphaComposite;
47 import java.awt.BasicStroke;
48 import java.awt.Color;
49 import java.awt.Composite;
50 import java.awt.CompositeContext;
51 import java.awt.Font;
52 import java.awt.FontMetrics;
53 import java.awt.GradientPaint;
54 import java.awt.Graphics;
55 import java.awt.Graphics2D;
56 import java.awt.GraphicsConfiguration;
57 import java.awt.Image;
58 import java.awt.Paint;
59 import java.awt.PaintContext;
60 import java.awt.Point;
61 import java.awt.Polygon;
62 import java.awt.Rectangle;
63 import java.awt.RenderingHints;
64 import java.awt.Shape;
65 import java.awt.Stroke;
66 import java.awt.TexturePaint;
67 import java.awt.Toolkit;
68 import java.awt.font.FontRenderContext;
69 import java.awt.font.GlyphVector;
70 import java.awt.font.TextLayout;
71 import java.awt.geom.AffineTransform;
72 import java.awt.geom.Arc2D;
73 import java.awt.geom.Area;
74 import java.awt.geom.Ellipse2D;
75 import java.awt.geom.GeneralPath;
76 import java.awt.geom.Line2D;
77 import java.awt.geom.NoninvertibleTransformException;
78 import java.awt.geom.PathIterator;
79 import java.awt.geom.Point2D;
80 import java.awt.geom.Rectangle2D;
81 import java.awt.geom.RoundRectangle2D;
82 import java.awt.image.AffineTransformOp;
83 import java.awt.image.BufferedImage;
84 import java.awt.image.BufferedImageOp;
85 import java.awt.image.ColorModel;
86 import java.awt.image.DataBuffer;
87 import java.awt.image.DataBufferInt;
88 import java.awt.image.DirectColorModel;
89 import java.awt.image.ImageObserver;
90 import java.awt.image.ImageProducer;
91 import java.awt.image.ImagingOpException;
92 import java.awt.image.MultiPixelPackedSampleModel;
93 import java.awt.image.Raster;
94 import java.awt.image.RenderedImage;
95 import java.awt.image.SampleModel;
96 import java.awt.image.WritableRaster;
97 import java.awt.image.renderable.RenderContext;
98 import java.awt.image.renderable.RenderableImage;
99 import java.text.AttributedCharacterIterator;
100 import java.util.HashMap;
101 import java.util.Map;
104 * This is an abstract implementation of Graphics2D on Cairo.
106 * It should be subclassed for different Cairo contexts.
108 * Note for subclassers: Apart from the constructor (see comments below),
109 * The following abstract methods must be implemented:
111 * Graphics create()
112 * GraphicsConfiguration getDeviceConfiguration()
113 * copyArea(int x, int y, int width, int height, int dx, int dy)
115 * Also, dispose() must be overloaded to free any native datastructures
116 * used by subclass and in addition call super.dispose() to free the
117 * native cairographics2d structure and cairo_t.
119 * @author Sven de Marothy
121 public abstract class CairoGraphics2D extends Graphics2D
123 static
125 if (Configuration.INIT_LOAD_LIBRARY)
127 System.loadLibrary("gtkpeer");
132 * Important: This is a pointer to the native cairographics2d structure
134 * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
136 long nativePointer;
138 // Drawing state variables
140 * The current paint
142 Paint paint;
143 boolean customPaint;
146 * The current stroke
148 Stroke stroke;
151 * Current foreground and background color.
153 Color fg, bg;
156 * Current clip shape.
158 Shape clip;
161 * Current transform.
163 AffineTransform transform;
166 * Current font.
168 Font font;
171 * The current compositing context, if any.
173 Composite comp;
174 CompositeContext compCtx;
177 * Rendering hint map.
179 private RenderingHints hints;
182 * Status of the anti-alias flag in cairo.
184 private boolean antialias = false;
185 private boolean ignoreAA = false;
188 * Some operations (drawing rather than filling) require that their
189 * coords be shifted to land on 0.5-pixel boundaries, in order to land on
190 * "middle of pixel" coordinates and light up complete pixels.
192 protected boolean shiftDrawCalls = false;
195 * Keep track if the first clip to be set, which is restored on setClip(null);
197 private boolean firstClip = true;
198 private Shape originalClip;
201 * Stroke used for 3DRects
203 private static BasicStroke draw3DRectStroke = new BasicStroke();
205 static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
206 static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF,
207 0xFF000000);
210 * Native constants for interpolation methods.
211 * Note, this corresponds to an enum in native/jni/gtk-peer/cairographics2d.h
213 public static final int INTERPOLATION_NEAREST = 0,
214 INTERPOLATION_BILINEAR = 1,
215 INTERPOLATION_BICUBIC = 5,
216 ALPHA_INTERPOLATION_SPEED = 2,
217 ALPHA_INTERPOLATION_QUALITY = 3,
218 ALPHA_INTERPOLATION_DEFAULT = 4;
219 // TODO: Does ALPHA_INTERPOLATION really correspond to CAIRO_FILTER_FAST/BEST/GOOD?
222 * Constructor does nothing.
224 public CairoGraphics2D()
229 * Sets up the default values and allocates the native cairographics2d structure
230 * @param cairo_t_pointer a native pointer to a cairo_t of the context.
232 public void setup(long cairo_t_pointer)
234 nativePointer = init(cairo_t_pointer);
235 setRenderingHints(new RenderingHints(getDefaultHints()));
236 setFont(new Font("SansSerif", Font.PLAIN, 12));
237 setColor(Color.black);
238 setBackground(Color.white);
239 setPaint(Color.black);
240 setStroke(new BasicStroke());
241 setTransform(new AffineTransform());
242 cairoSetAntialias(nativePointer, antialias);
246 * Same as above, but copies the state of another CairoGraphics2D.
248 public void copy(CairoGraphics2D g, long cairo_t_pointer)
250 nativePointer = init(cairo_t_pointer);
251 paint = g.paint;
252 stroke = g.stroke;
253 setRenderingHints(g.hints);
255 Color foreground;
257 if (g.fg.getAlpha() != -1)
258 foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
259 g.fg.getAlpha());
260 else
261 foreground = new Color(g.fg.getRGB());
263 if (g.bg != null)
265 if (g.bg.getAlpha() != -1)
266 bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
267 g.bg.getAlpha());
268 else
269 bg = new Color(g.bg.getRGB());
272 firstClip = g.firstClip;
273 originalClip = g.originalClip;
274 clip = g.getClip();
276 if (g.transform == null)
277 transform = null;
278 else
279 transform = new AffineTransform(g.transform);
281 setFont(g.font);
282 setColor(foreground);
283 setBackground(bg);
284 setPaint(paint);
285 setStroke(stroke);
286 setTransformImpl(transform);
287 setClip(clip);
288 setComposite(comp);
290 antialias = !g.antialias;
291 setAntialias(g.antialias);
295 * Generic destructor - call the native dispose() method.
297 public void finalize()
299 dispose();
303 * Disposes the native cairographics2d structure, including the
304 * cairo_t and any gradient stuff, if allocated.
305 * Subclasses should of course overload and call this if
306 * they have additional native structures.
308 public void dispose()
310 disposeNative(nativePointer);
311 nativePointer = 0;
312 if (compCtx != null)
313 compCtx.dispose();
317 * Allocate the cairographics2d structure and set the cairo_t pointer in it.
318 * @param pointer - a cairo_t pointer, casted to a long.
320 protected native long init(long pointer);
323 * These are declared abstract as there may be context-specific issues.
325 public abstract Graphics create();
327 public abstract GraphicsConfiguration getDeviceConfiguration();
329 protected abstract void copyAreaImpl(int x, int y, int width, int height,
330 int dx, int dy);
334 * Find the bounds of this graphics context, in device space.
336 * @return the bounds in device-space
338 protected abstract Rectangle2D getRealBounds();
340 ////// Native Methods ////////////////////////////////////////////////////
343 * Dispose of allocate native resouces.
345 public native void disposeNative(long pointer);
348 * Draw pixels as an RGBA int matrix
349 * @param w - width
350 * @param h - height
351 * @param stride - stride of the array width
352 * @param i2u - affine transform array
354 protected native void drawPixels(long pointer, int[] pixels, int w, int h,
355 int stride, double[] i2u, double alpha,
356 int interpolation);
358 protected native void setGradient(long pointer, double x1, double y1,
359 double x2, double y2,
360 int r1, int g1, int b1, int a1, int r2,
361 int g2, int b2, int a2, boolean cyclic);
363 protected native void setPaintPixels(long pointer, int[] pixels, int w,
364 int h, int stride, boolean repeat,
365 int x, int y);
368 * Set the current transform matrix
370 protected native void cairoSetMatrix(long pointer, double[] m);
373 * Scaling method
375 protected native void cairoScale(long pointer, double x, double y);
378 * Set the compositing operator
380 protected native void cairoSetOperator(long pointer, int cairoOperator);
383 * Sets the current color in RGBA as a 0.0-1.0 double
385 protected native void cairoSetRGBAColor(long pointer, double red, double green,
386 double blue, double alpha);
389 * Sets the current winding rule in Cairo
391 protected native void cairoSetFillRule(long pointer, int cairoFillRule);
394 * Set the line style, cap, join and miter limit.
395 * Cap and join parameters are in the BasicStroke enumerations.
397 protected native void cairoSetLine(long pointer, double width, int cap,
398 int join, double miterLimit);
401 * Set the dash style
403 protected native void cairoSetDash(long pointer, double[] dashes, int ndash,
404 double offset);
407 * Draws a Glyph Vector
409 protected native void cairoDrawGlyphVector(long pointer, GdkFontPeer font,
410 float x, float y, int n,
411 int[] codes, float[] positions, long[] fontset);
414 * Set the font in cairo.
416 protected native void cairoSetFont(long pointer, GdkFontPeer font);
419 * Appends a rectangle to the current path
421 protected native void cairoRectangle(long pointer, double x, double y,
422 double width, double height);
425 * Appends an arc to the current path
427 protected native void cairoArc(long pointer, double x, double y,
428 double radius, double angle1, double angle2);
431 * Save / restore a cairo path
433 protected native void cairoSave(long pointer);
434 protected native void cairoRestore(long pointer);
437 * New current path
439 protected native void cairoNewPath(long pointer);
441 /**
442 * Close current path
444 protected native void cairoClosePath(long pointer);
446 /** moveTo */
447 protected native void cairoMoveTo(long pointer, double x, double y);
449 /** lineTo */
450 protected native void cairoLineTo(long pointer, double x, double y);
452 /** Cubic curve-to */
453 protected native void cairoCurveTo(long pointer, double x1, double y1,
454 double x2, double y2,
455 double x3, double y3);
458 * Stroke current path
460 protected native void cairoStroke(long pointer);
463 * Fill current path
465 protected native void cairoFill(long pointer, double alpha);
467 /**
468 * Clip current path
470 protected native void cairoClip(long pointer);
472 /**
473 * Clear clip
475 protected native void cairoResetClip(long pointer);
478 * Set antialias.
480 protected native void cairoSetAntialias(long pointer, boolean aa);
483 ///////////////////////// TRANSFORMS ///////////////////////////////////
485 * Set the current transform
487 public void setTransform(AffineTransform tx)
489 // Transform clip into target space using the old transform.
490 updateClip(transform);
492 // Update the native transform.
493 setTransformImpl(tx);
495 // Transform the clip back into user space using the inverse new transform.
498 updateClip(transform.createInverse());
500 catch (NoninvertibleTransformException ex)
502 // TODO: How can we deal properly with this?
503 ex.printStackTrace();
506 if (clip != null)
507 setClip(clip);
510 private void setTransformImpl(AffineTransform tx)
512 transform = tx;
513 if (transform != null)
515 double[] m = new double[6];
516 transform.getMatrix(m);
517 cairoSetMatrix(nativePointer, m);
521 public void transform(AffineTransform tx)
523 if (transform == null)
524 transform = new AffineTransform(tx);
525 else
526 transform.concatenate(tx);
528 if (clip != null)
532 AffineTransform clipTransform = tx.createInverse();
533 updateClip(clipTransform);
535 catch (NoninvertibleTransformException ex)
537 // TODO: How can we deal properly with this?
538 ex.printStackTrace();
542 setTransformImpl(transform);
545 public void rotate(double theta)
547 transform(AffineTransform.getRotateInstance(theta));
550 public void rotate(double theta, double x, double y)
552 transform(AffineTransform.getRotateInstance(theta, x, y));
555 public void scale(double sx, double sy)
557 transform(AffineTransform.getScaleInstance(sx, sy));
561 * Translate the system of the co-ordinates. As translation is a frequent
562 * operation, it is done in an optimised way, unlike scaling and rotating.
564 public void translate(double tx, double ty)
566 if (transform != null)
567 transform.translate(tx, ty);
568 else
569 transform = AffineTransform.getTranslateInstance(tx, ty);
571 if (clip != null)
573 // FIXME: this should actuall try to transform the shape
574 // rather than degrade to bounds.
575 if (clip instanceof Rectangle2D)
577 Rectangle2D r = (Rectangle2D) clip;
578 r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(),
579 r.getHeight());
581 else
583 AffineTransform clipTransform =
584 AffineTransform.getTranslateInstance(-tx, -ty);
585 updateClip(clipTransform);
589 setTransformImpl(transform);
592 public void translate(int x, int y)
594 translate((double) x, (double) y);
597 public void shear(double shearX, double shearY)
599 transform(AffineTransform.getShearInstance(shearX, shearY));
602 ///////////////////////// DRAWING STATE ///////////////////////////////////
604 public void clip(Shape s)
606 // Do not touch clip when s == null.
607 if (s == null)
609 // The spec says this should clear the clip. The reference
610 // implementation throws a NullPointerException instead. I think,
611 // in this case we should conform to the specs, as it shouldn't
612 // affect compatibility.
613 setClip(null);
614 return;
617 // If the current clip is still null, initialize it.
618 if (clip == null)
620 clip = getRealBounds();
623 // This is so common, let's optimize this.
624 if (clip instanceof Rectangle2D && s instanceof Rectangle2D)
626 Rectangle2D clipRect = (Rectangle2D) clip;
627 Rectangle2D r = (Rectangle2D) s;
628 Rectangle2D.intersect(clipRect, r, clipRect);
629 setClip(clipRect);
631 else
633 Area current;
634 if (clip instanceof Area)
635 current = (Area) clip;
636 else
637 current = new Area(clip);
639 Area intersect;
640 if (s instanceof Area)
641 intersect = (Area) s;
642 else
643 intersect = new Area(s);
645 current.intersect(intersect);
646 clip = current;
647 // Call setClip so that the native side gets notified.
648 setClip(clip);
652 public Paint getPaint()
654 return paint;
657 public AffineTransform getTransform()
659 return (AffineTransform) transform.clone();
662 public void setPaint(Paint p)
664 if (p == null)
665 return;
667 paint = p;
668 if (paint instanceof Color)
670 setColor((Color) paint);
671 customPaint = false;
674 else if (paint instanceof TexturePaint)
676 TexturePaint tp = (TexturePaint) paint;
677 BufferedImage img = tp.getImage();
679 // map the image to the anchor rectangle
680 int width = (int) tp.getAnchorRect().getWidth();
681 int height = (int) tp.getAnchorRect().getHeight();
683 double scaleX = width / (double) img.getWidth();
684 double scaleY = height / (double) img.getHeight();
686 AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
687 AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
688 BufferedImage texture = op.filter(img, null);
689 int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
690 setPaintPixels(nativePointer, pixels, width, height, width, true, 0, 0);
691 customPaint = false;
694 else if (paint instanceof GradientPaint)
696 GradientPaint gp = (GradientPaint) paint;
697 Point2D p1 = gp.getPoint1();
698 Point2D p2 = gp.getPoint2();
699 Color c1 = gp.getColor1();
700 Color c2 = gp.getColor2();
701 setGradient(nativePointer, p1.getX(), p1.getY(), p2.getX(), p2.getY(),
702 c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(),
703 c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(),
704 gp.isCyclic());
705 customPaint = false;
707 else
709 customPaint = true;
714 * Sets a custom paint
716 * @param bounds the bounding box, in user space
718 protected void setCustomPaint(Rectangle bounds)
720 if (paint instanceof Color || paint instanceof TexturePaint
721 || paint instanceof GradientPaint)
722 return;
724 int userX = bounds.x;
725 int userY = bounds.y;
726 int userWidth = bounds.width;
727 int userHeight = bounds.height;
729 // Find bounds in device space
730 Rectangle2D bounds2D = getTransformedBounds(bounds, transform);
731 int deviceX = (int)bounds2D.getX();
732 int deviceY = (int)bounds2D.getY();
733 int deviceWidth = (int)Math.ceil(bounds2D.getWidth());
734 int deviceHeight = (int)Math.ceil(bounds2D.getHeight());
736 // Get raster of the paint background
737 PaintContext pc = paint.createContext(CairoSurface.cairoColorModel,
738 new Rectangle(deviceX, deviceY,
739 deviceWidth,
740 deviceHeight),
741 bounds,
742 transform, hints);
744 Raster raster = pc.getRaster(deviceX, deviceY, deviceWidth,
745 deviceHeight);
747 // Clear the transform matrix in Cairo, since the raster returned by the
748 // PaintContext is already in device-space
749 AffineTransform oldTx = new AffineTransform(transform);
750 setTransformImpl(new AffineTransform());
752 // Set pixels in cairo, aligning the top-left of the background image
753 // to the top-left corner in device space
754 if (pc.getColorModel().equals(CairoSurface.cairoColorModel)
755 && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
757 // Use a fast copy if the paint context can uses a Cairo-compatible
758 // color model
759 setPaintPixels(nativePointer,
760 (int[])raster.getDataElements(0, 0, deviceWidth,
761 deviceHeight, null),
762 deviceWidth, deviceHeight, deviceWidth, false,
763 deviceX, deviceY);
766 else if (pc.getColorModel().equals(CairoSurface.cairoCM_opaque)
767 && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
769 // We can also optimize if the context uses a similar color model
770 // but without an alpha channel; we just add the alpha
771 int[] pixels = (int[])raster.getDataElements(0, 0, deviceWidth,
772 deviceHeight, null);
774 for (int i = 0; i < pixels.length; i++)
775 pixels[i] = 0xff000000 | (pixels[i] & 0x00ffffff);
777 setPaintPixels(nativePointer, pixels, deviceWidth, deviceHeight,
778 deviceWidth, false, deviceX, deviceY);
781 else
783 // Fall back on wrapping the raster in a BufferedImage, and
784 // use BufferedImage.getRGB() to do color-model conversion
785 WritableRaster wr = Raster.createWritableRaster(raster.getSampleModel(),
786 new Point(raster.getMinX(),
787 raster.getMinY()));
788 wr.setRect(raster);
790 BufferedImage img2 = new BufferedImage(pc.getColorModel(), wr,
791 pc.getColorModel().isAlphaPremultiplied(),
792 null);
794 setPaintPixels(nativePointer,
795 img2.getRGB(0, 0, deviceWidth, deviceHeight, null, 0,
796 deviceWidth),
797 deviceWidth, deviceHeight, deviceWidth, false,
798 deviceX, deviceY);
801 // Restore transform
802 setTransformImpl(oldTx);
805 public Stroke getStroke()
807 return stroke;
810 public void setStroke(Stroke st)
812 stroke = st;
813 if (stroke instanceof BasicStroke)
815 BasicStroke bs = (BasicStroke) stroke;
816 cairoSetLine(nativePointer, bs.getLineWidth(), bs.getEndCap(),
817 bs.getLineJoin(), bs.getMiterLimit());
819 float[] dashes = bs.getDashArray();
820 if (dashes != null)
822 double[] double_dashes = new double[dashes.length];
823 for (int i = 0; i < dashes.length; i++)
824 double_dashes[i] = dashes[i];
826 cairoSetDash(nativePointer, double_dashes, double_dashes.length,
827 (double) bs.getDashPhase());
829 else
830 cairoSetDash(nativePointer, new double[0], 0, 0.0);
835 * Utility method to find the bounds of a shape, including the stroke width.
837 * @param s the shape
838 * @return the bounds of the shape, including stroke width
840 protected Rectangle findStrokedBounds(Shape s)
842 Rectangle r = s.getBounds();
844 if (stroke instanceof BasicStroke)
846 int strokeWidth = (int)Math.ceil(((BasicStroke)stroke).getLineWidth());
847 r.x -= strokeWidth / 2;
848 r.y -= strokeWidth / 2;
849 r.height += strokeWidth;
850 r.width += strokeWidth;
852 else
854 Shape s2 = stroke.createStrokedShape(s);
855 r = s2.getBounds();
858 return r;
861 public void setPaintMode()
863 setComposite(AlphaComposite.SrcOver);
866 public void setXORMode(Color c)
868 // FIXME: implement
871 public void setColor(Color c)
873 if (c == null)
874 c = Color.BLACK;
876 fg = c;
877 paint = c;
878 updateColor();
882 * Set the current fg value as the cairo color.
884 void updateColor()
886 if (fg == null)
887 fg = Color.BLACK;
889 cairoSetRGBAColor(nativePointer, fg.getRed() / 255.0,
890 fg.getGreen() / 255.0,fg.getBlue() / 255.0,
891 fg.getAlpha() / 255.0);
894 public Color getColor()
896 return fg;
899 public void clipRect(int x, int y, int width, int height)
901 if (clip == null)
902 setClip(new Rectangle(x, y, width, height));
903 else if (clip instanceof Rectangle)
905 computeIntersection(x, y, width, height, (Rectangle) clip);
906 setClip(clip);
908 else
909 clip(new Rectangle(x, y, width, height));
912 public Shape getClip()
914 if (clip == null)
915 return null;
916 else if (clip instanceof Rectangle2D)
917 return clip.getBounds2D(); //getClipInDevSpace();
918 else
920 GeneralPath p = new GeneralPath();
921 PathIterator pi = clip.getPathIterator(null);
922 p.append(pi, false);
923 return p;
927 public Rectangle getClipBounds()
929 if (clip == null)
930 return null;
931 else
932 return clip.getBounds();
935 protected Rectangle2D getClipInDevSpace()
937 Rectangle2D uclip = clip.getBounds2D();
938 if (transform == null)
939 return uclip;
940 else
941 return getTransformedBounds(clip.getBounds2D(), transform);
944 public void setClip(int x, int y, int width, int height)
946 if( width < 0 || height < 0 )
947 return;
949 setClip(new Rectangle2D.Double(x, y, width, height));
952 public void setClip(Shape s)
954 // The first time the clip is set, save it as the original clip
955 // to reset to on s == null. We can rely on this being non-null
956 // because the constructor in subclasses is expected to set the
957 // initial clip properly.
958 if( firstClip )
960 originalClip = s;
961 firstClip = false;
964 clip = s;
965 cairoResetClip(nativePointer);
967 if (clip != null)
969 cairoNewPath(nativePointer);
970 if (clip instanceof Rectangle2D)
972 Rectangle2D r = (Rectangle2D) clip;
973 cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(),
974 r.getHeight());
976 else
977 walkPath(clip.getPathIterator(null), false);
979 cairoClip(nativePointer);
983 public void setBackground(Color c)
985 if (c == null)
986 c = Color.WHITE;
987 bg = c;
990 public Color getBackground()
992 return bg;
996 * Return the current composite.
998 public Composite getComposite()
1000 if (comp == null)
1001 return AlphaComposite.SrcOver;
1002 else
1003 return comp;
1007 * Sets the current composite context.
1009 public void setComposite(Composite comp)
1011 if (this.comp == comp)
1012 return;
1014 this.comp = comp;
1015 if (compCtx != null)
1016 compCtx.dispose();
1017 compCtx = null;
1019 if (comp instanceof AlphaComposite)
1021 AlphaComposite a = (AlphaComposite) comp;
1022 cairoSetOperator(nativePointer, a.getRule());
1025 else
1027 cairoSetOperator(nativePointer, AlphaComposite.SRC_OVER);
1029 if (comp != null)
1031 // FIXME: this check is only required "if this Graphics2D
1032 // context is drawing to a Component on the display screen".
1033 SecurityManager sm = System.getSecurityManager();
1034 if (sm != null)
1035 sm.checkPermission(new AWTPermission("readDisplayPixels"));
1037 compCtx = comp.createContext(getBufferCM(), getNativeCM(), hints);
1043 * Returns the Colour Model describing the native, raw image data for this
1044 * specific peer.
1046 * @return ColorModel the ColorModel of native data in this peer
1048 protected abstract ColorModel getNativeCM();
1051 * Returns the Color Model describing the buffer that this peer uses
1052 * for custom composites.
1054 * @return ColorModel the ColorModel of the composite buffer in this peer.
1056 protected ColorModel getBufferCM()
1058 // This may be overridden by some subclasses
1059 return getNativeCM();
1062 ///////////////////////// DRAWING PRIMITIVES ///////////////////////////////////
1064 public void draw(Shape s)
1066 if ((stroke != null && ! (stroke instanceof BasicStroke))
1067 || (comp instanceof AlphaComposite && ((AlphaComposite) comp).getAlpha() != 1.0))
1069 // Cairo doesn't support stroking with alpha, so we create the stroked
1070 // shape and fill with alpha instead
1071 fill(stroke.createStrokedShape(s));
1072 return;
1075 if (customPaint)
1077 Rectangle r = findStrokedBounds(s);
1078 setCustomPaint(r);
1081 setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING)
1082 .equals(RenderingHints.VALUE_ANTIALIAS_OFF));
1083 createPath(s, true);
1084 cairoStroke(nativePointer);
1087 public void fill(Shape s)
1089 createPath(s, false);
1091 if (customPaint)
1092 setCustomPaint(s.getBounds());
1094 setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING)
1095 .equals(RenderingHints.VALUE_ANTIALIAS_OFF));
1096 double alpha = 1.0;
1097 if (comp instanceof AlphaComposite)
1098 alpha = ((AlphaComposite) comp).getAlpha();
1099 cairoFill(nativePointer, alpha);
1102 private void createPath(Shape s, boolean isDraw)
1104 cairoNewPath(nativePointer);
1106 // Optimize rectangles, since there is a direct Cairo function
1107 if (s instanceof Rectangle2D)
1109 Rectangle2D r = (Rectangle2D) s;
1111 // Pixels need to be shifted in draw operations to ensure that they
1112 // light up entire pixels, but we also need to make sure the rectangle
1113 // does not get distorted by this shifting operation
1114 double x = shiftX(r.getX(),shiftDrawCalls && isDraw);
1115 double y = shiftY(r.getY(), shiftDrawCalls && isDraw);
1116 double w = Math.round(r.getWidth());
1117 double h = Math.round(r.getHeight());
1118 cairoRectangle(nativePointer, x, y, w, h);
1121 // Lines are easy too
1122 else if (s instanceof Line2D)
1124 Line2D l = (Line2D) s;
1125 cairoMoveTo(nativePointer, shiftX(l.getX1(), shiftDrawCalls && isDraw),
1126 shiftY(l.getY1(), shiftDrawCalls && isDraw));
1127 cairoLineTo(nativePointer, shiftX(l.getX2(), shiftDrawCalls && isDraw),
1128 shiftY(l.getY2(), shiftDrawCalls && isDraw));
1131 // We can optimize ellipses too; however we don't bother optimizing arcs:
1132 // the iterator is fast enough (an ellipse requires 5 steps using the
1133 // iterator, while most arcs are only 2-3)
1134 else if (s instanceof Ellipse2D)
1136 Ellipse2D e = (Ellipse2D) s;
1138 double radius = Math.min(e.getHeight(), e.getWidth()) / 2;
1140 // Cairo only draws circular shapes, but we can use a stretch to make
1141 // them into ellipses
1142 double xscale = 1, yscale = 1;
1143 if (e.getHeight() != e.getWidth())
1145 cairoSave(nativePointer);
1147 if (e.getHeight() < e.getWidth())
1148 xscale = e.getWidth() / (radius * 2);
1149 else
1150 yscale = e.getHeight() / (radius * 2);
1152 if (xscale != 1 || yscale != 1)
1153 cairoScale(nativePointer, xscale, yscale);
1156 cairoArc(nativePointer,
1157 shiftX(e.getCenterX() / xscale, shiftDrawCalls && isDraw),
1158 shiftY(e.getCenterY() / yscale, shiftDrawCalls && isDraw),
1159 radius, 0, Math.PI * 2);
1161 if (xscale != 1 || yscale != 1)
1162 cairoRestore(nativePointer);
1165 // All other shapes are broken down and drawn in steps using the
1166 // PathIterator
1167 else
1168 walkPath(s.getPathIterator(null), shiftDrawCalls && isDraw);
1172 * Note that the rest of the drawing methods go via fill() or draw() for the drawing,
1173 * although subclasses may with to overload these methods where context-specific
1174 * optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int)
1177 public void clearRect(int x, int y, int width, int height)
1179 if (bg != null)
1180 cairoSetRGBAColor(nativePointer, bg.getRed() / 255.0,
1181 bg.getGreen() / 255.0, bg.getBlue() / 255.0,
1182 bg.getAlpha() / 255.0);
1184 Composite oldcomp = comp;
1185 setComposite(AlphaComposite.Src);
1186 fillRect(x, y, width, height);
1188 setComposite(oldcomp);
1189 updateColor();
1192 public void draw3DRect(int x, int y, int width, int height, boolean raised)
1194 Stroke tmp = stroke;
1195 setStroke(draw3DRectStroke);
1196 super.draw3DRect(x, y, width, height, raised);
1197 setStroke(tmp);
1200 public void drawArc(int x, int y, int width, int height, int startAngle,
1201 int arcAngle)
1203 draw(new Arc2D.Double((double) x, (double) y, (double) width,
1204 (double) height, (double) startAngle,
1205 (double) arcAngle, Arc2D.OPEN));
1208 public void drawLine(int x1, int y1, int x2, int y2)
1210 // The coordinates being pairwise identical means one wants
1211 // to draw a single pixel. This is emulated by drawing
1212 // a one pixel sized rectangle.
1213 if (x1 == x2 && y1 == y2)
1214 fill(new Rectangle(x1, y1, 1, 1));
1215 else
1216 draw(new Line2D.Double(x1, y1, x2, y2));
1219 public void drawRect(int x, int y, int width, int height)
1221 draw(new Rectangle(x, y, width, height));
1224 public void fillArc(int x, int y, int width, int height, int startAngle,
1225 int arcAngle)
1227 fill(new Arc2D.Double((double) x, (double) y, (double) width,
1228 (double) height, (double) startAngle,
1229 (double) arcAngle, Arc2D.PIE));
1232 public void fillRect(int x, int y, int width, int height)
1234 fill (new Rectangle(x, y, width, height));
1237 public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1239 fill(new Polygon(xPoints, yPoints, nPoints));
1242 public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1244 draw(new Polygon(xPoints, yPoints, nPoints));
1247 public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1249 draw(new Polygon(xPoints, yPoints, nPoints));
1252 public void drawOval(int x, int y, int width, int height)
1254 drawArc(x, y, width, height, 0, 360);
1257 public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
1258 int arcHeight)
1260 draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
1263 public void fillOval(int x, int y, int width, int height)
1265 fillArc(x, y, width, height, 0, 360);
1268 public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
1269 int arcHeight)
1271 fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
1275 * CopyArea - performs clipping to the native surface as a convenience
1276 * (requires getRealBounds). Then calls copyAreaImpl.
1278 public void copyArea(int ox, int oy, int owidth, int oheight,
1279 int odx, int ody)
1281 // FIXME: does this handle a rotation transform properly?
1282 // (the width/height might not be correct)
1283 Point2D pos = transform.transform(new Point2D.Double(ox, oy),
1284 (Point2D) null);
1285 Point2D dim = transform.transform(new Point2D.Double(ox + owidth,
1286 oy + oheight),
1287 (Point2D) null);
1288 Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody),
1289 (Point2D) null);
1290 int x = (int)pos.getX();
1291 int y = (int)pos.getY();
1292 int width = (int)(dim.getX() - pos.getX());
1293 int height = (int)(dim.getY() - pos.getY());
1294 int dx = (int)(p2.getX() - pos.getX());
1295 int dy = (int)(p2.getY() - pos.getY());
1297 Rectangle2D r = getRealBounds();
1299 if( width <= 0 || height <= 0 )
1300 return;
1301 // Return if outside the surface
1302 if( x + dx > r.getWidth() || y + dy > r.getHeight() )
1303 return;
1305 if( x + dx + width < r.getX() || y + dy + height < r.getY() )
1306 return;
1308 // Clip edges if necessary
1309 if( x + dx < r.getX() ) // left
1311 width = x + dx + width;
1312 x = (int)r.getX() - dx;
1315 if( y + dy < r.getY() ) // top
1317 height = y + dy + height;
1318 y = (int)r.getY() - dy;
1321 if( x + dx + width >= r.getWidth() ) // right
1322 width = (int)r.getWidth() - dx - x;
1324 if( y + dy + height >= r.getHeight() ) // bottom
1325 height = (int)r.getHeight() - dy - y;
1327 copyAreaImpl(x, y, width, height, dx, dy);
1330 ///////////////////////// RENDERING HINTS ///////////////////////////////////
1332 public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
1334 hints.put(hintKey, hintValue);
1336 shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1337 || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1340 public Object getRenderingHint(RenderingHints.Key hintKey)
1342 return hints.get(hintKey);
1345 public void setRenderingHints(Map<?,?> hints)
1347 this.hints = new RenderingHints(getDefaultHints());
1348 this.hints.putAll(hints);
1350 shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1351 || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1353 if (compCtx != null)
1355 compCtx.dispose();
1356 compCtx = comp.createContext(getNativeCM(), getNativeCM(), this.hints);
1360 public void addRenderingHints(Map hints)
1362 this.hints.putAll(hints);
1365 public RenderingHints getRenderingHints()
1367 return hints;
1370 private int getInterpolation()
1372 if (this.hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1373 return INTERPOLATION_NEAREST;
1375 else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1376 return INTERPOLATION_BILINEAR;
1378 else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC))
1379 return INTERPOLATION_BICUBIC;
1381 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1382 return ALPHA_INTERPOLATION_SPEED;
1384 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1385 return ALPHA_INTERPOLATION_QUALITY;
1387 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1388 return ALPHA_INTERPOLATION_DEFAULT;
1390 // Do bilinear interpolation as default
1391 return INTERPOLATION_BILINEAR;
1395 * Set antialias if needed. If the ignoreAA flag is set, this method will
1396 * return without doing anything.
1398 * @param needAA RenderingHints.VALUE_ANTIALIAS_ON or RenderingHints.VALUE_ANTIALIAS_OFF
1400 private void setAntialias(boolean needAA)
1402 if (ignoreAA)
1403 return;
1405 if (needAA != antialias)
1407 antialias = !antialias;
1408 cairoSetAntialias(nativePointer, antialias);
1412 ///////////////////////// IMAGE. METHODS ///////////////////////////////////
1414 protected boolean drawImage(Image img, AffineTransform xform,
1415 Color bgcolor, ImageObserver obs)
1417 if (img == null)
1418 return false;
1420 if (xform == null)
1421 xform = new AffineTransform();
1423 // In this case, xform is an AffineTransform that transforms bounding
1424 // box of the specified image from image space to user space. However
1425 // when we pass this transform to cairo, cairo will use this transform
1426 // to map "user coordinates" to "pixel" coordinates, which is the
1427 // other way around. Therefore to get the "user -> pixel" transform
1428 // that cairo wants from "image -> user" transform that we currently
1429 // have, we will need to invert the transformation matrix.
1430 AffineTransform invertedXform;
1434 invertedXform = xform.createInverse();
1436 catch (NoninvertibleTransformException e)
1438 throw new ImagingOpException("Unable to invert transform "
1439 + xform.toString());
1442 // Unrecognized image - convert to a BufferedImage
1443 // Note - this can get us in trouble when the gdk lock is re-acquired.
1444 // for example by VolatileImage. See ComponentGraphics for how we work
1445 // around this.
1446 img = AsyncImage.realImage(img, obs);
1447 if( !(img instanceof BufferedImage) )
1449 ImageProducer source = img.getSource();
1450 if (source == null)
1451 return false;
1452 img = Toolkit.getDefaultToolkit().createImage(source);
1455 BufferedImage b = (BufferedImage) img;
1456 Raster raster;
1457 double[] i2u = new double[6];
1458 int width = b.getWidth();
1459 int height = b.getHeight();
1461 // If this BufferedImage has a BufferedImageGraphics object,
1462 // use the cached CairoSurface that BIG is drawing onto
1464 if( BufferedImageGraphics.bufferedImages.get( b ) != null )
1465 raster = BufferedImageGraphics.bufferedImages.get( b );
1466 else
1467 raster = b.getRaster();
1469 invertedXform.getMatrix(i2u);
1471 double alpha = 1.0;
1472 if (comp instanceof AlphaComposite)
1473 alpha = ((AlphaComposite) comp).getAlpha();
1475 if(raster instanceof CairoSurface
1476 && ((CairoSurface)raster).sharedBuffer == true)
1478 drawCairoSurface((CairoSurface)raster, xform, alpha, getInterpolation());
1479 updateColor();
1480 return true;
1483 if( bgcolor != null )
1485 Color oldColor = bg;
1486 setBackground(bgcolor);
1488 Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
1489 bounds = getTransformedBounds(bounds, xform);
1491 clearRect((int)bounds.getX(), (int)bounds.getY(),
1492 (int)bounds.getWidth(), (int)bounds.getHeight());
1494 setBackground(oldColor);
1497 int[] pixels = b.getRGB(0, 0, width, height, null, 0, width);
1498 // FIXME: The above method returns data in the standard ARGB colorspace,
1499 // meaning data should NOT be alpha pre-multiplied; however Cairo expects
1500 // data to be premultiplied.
1502 cairoSave(nativePointer);
1503 Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
1504 bounds = getTransformedBounds(bounds, xform);
1505 cairoRectangle(nativePointer, bounds.getX(), bounds.getY(),
1506 bounds.getWidth(), bounds.getHeight());
1507 cairoClip(nativePointer);
1509 drawPixels(nativePointer, pixels, width, height, width, i2u, alpha,
1510 getInterpolation());
1512 cairoRestore(nativePointer);
1514 // Cairo seems to lose the current color which must be restored.
1515 updateColor();
1516 return true;
1519 public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1521 drawRaster(image.getColorModel(), image.getData(), xform, null);
1524 public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1526 drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1529 public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1531 return drawImage(img, xform, null, obs);
1534 public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
1536 Image filtered = image;
1537 if (op != null)
1538 filtered = op.filter(image, null);
1539 drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null);
1542 public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1544 return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null,
1545 observer);
1548 public boolean drawImage(Image img, int x, int y, Color bgcolor,
1549 ImageObserver observer)
1551 return drawImage(img, x, y, img.getWidth(observer),
1552 img.getHeight(observer), bgcolor, observer);
1555 public boolean drawImage(Image img, int x, int y, int width, int height,
1556 Color bgcolor, ImageObserver observer)
1558 double scaleX = width / (double) img.getWidth(observer);
1559 double scaleY = height / (double) img.getHeight(observer);
1560 if( scaleX == 0 || scaleY == 0 )
1561 return true;
1563 return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y),
1564 bgcolor, observer);
1567 public boolean drawImage(Image img, int x, int y, int width, int height,
1568 ImageObserver observer)
1570 return drawImage(img, x, y, width, height, null, observer);
1573 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1574 int sx1, int sy1, int sx2, int sy2, Color bgcolor,
1575 ImageObserver observer)
1577 if (img == null)
1578 return false;
1580 int sourceWidth = sx2 - sx1;
1581 int sourceHeight = sy2 - sy1;
1583 int destWidth = dx2 - dx1;
1584 int destHeight = dy2 - dy1;
1586 if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 ||
1587 sourceHeight == 0)
1588 return true;
1590 double scaleX = destWidth / (double) sourceWidth;
1591 double scaleY = destHeight / (double) sourceHeight;
1593 // FIXME: Avoid using an AT if possible here - it's at least twice as slow.
1595 Shape oldClip = getClip();
1596 int cx, cy, cw, ch;
1597 if( dx1 < dx2 )
1598 { cx = dx1; cw = dx2 - dx1; }
1599 else
1600 { cx = dx2; cw = dx1 - dx2; }
1601 if( dy1 < dy2 )
1602 { cy = dy1; ch = dy2 - dy1; }
1603 else
1604 { cy = dy2; ch = dy1 - dy2; }
1606 clipRect( cx, cy, cw, ch );
1608 AffineTransform tx = new AffineTransform();
1609 tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY );
1610 tx.scale( scaleX, scaleY );
1612 boolean retval = drawImage(img, tx, bgcolor, observer);
1613 setClip( oldClip );
1614 return retval;
1617 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1618 int sx1, int sy1, int sx2, int sy2,
1619 ImageObserver observer)
1621 return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
1625 * Optimized method for drawing a CairoSurface onto this graphics context.
1627 * @param surface The surface to draw.
1628 * @param tx The transformation matrix (cannot be null).
1629 * @param alpha The alpha value to paint with ( 0 <= alpha <= 1).
1630 * @param interpolation The interpolation type.
1632 protected void drawCairoSurface(CairoSurface surface, AffineTransform tx,
1633 double alpha, int interpolation)
1635 // Find offset required if this surface is a sub-raster, and append offset
1636 // to transformation.
1637 if (surface.getSampleModelTranslateX() != 0
1638 || surface.getSampleModelTranslateY() != 0)
1640 Point2D origin = new Point2D.Double(0, 0);
1641 Point2D offset = new Point2D.Double(surface.getSampleModelTranslateX(),
1642 surface.getSampleModelTranslateY());
1644 tx.transform(origin, origin);
1645 tx.transform(offset, offset);
1647 tx.translate(offset.getX() - origin.getX(),
1648 offset.getY() - origin.getY());
1651 // Find dimensions of this surface relative to the root parent surface
1652 Rectangle bounds = new Rectangle(-surface.getSampleModelTranslateX(),
1653 -surface.getSampleModelTranslateY(),
1654 surface.width, surface.height);
1656 // Clip to the translated image
1657 // We use direct cairo methods to avoid the overhead of maintaining a
1658 // java copy of the clip, since we will be reverting it immediately
1659 // after drawing
1660 Shape newBounds = tx.createTransformedShape(bounds);
1661 cairoSave(nativePointer);
1662 walkPath(newBounds.getPathIterator(null), false);
1663 cairoClip(nativePointer);
1665 // Draw the surface
1668 double[] i2u = new double[6];
1669 tx.createInverse().getMatrix(i2u);
1670 surface.nativeDrawSurface(surface.surfacePointer, nativePointer, i2u,
1671 alpha, interpolation);
1673 catch (NoninvertibleTransformException ex)
1675 // This should never happen(?), so we don't need to do anything here.
1679 // Restore clip
1680 cairoRestore(nativePointer);
1684 ///////////////////////// TEXT METHODS ////////////////////////////////////
1686 public void drawString(String str, float x, float y)
1688 if (str == null || str.length() == 0)
1689 return;
1690 GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
1691 TextLayout tl = (TextLayout) fontPeer.textLayoutCache.get(str);
1692 if (tl == null)
1694 tl = new TextLayout( str, getFont(), getFontRenderContext() );
1695 fontPeer.textLayoutCache.put(str, tl);
1698 // Set antialias to text_antialiasing, and set the ignoreAA flag so that
1699 // the setting doesn't get overridden in a draw() or fill() call.
1700 setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING)
1701 .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
1702 ignoreAA = true;
1704 tl.draw(this, x, y);
1705 ignoreAA = false;
1708 public void drawString(String str, int x, int y)
1710 drawString (str, (float) x, (float) y);
1713 public void drawString(AttributedCharacterIterator ci, int x, int y)
1715 drawString (ci, (float) x, (float) y);
1718 public void drawGlyphVector(GlyphVector gv, float x, float y)
1720 double alpha = 1.0;
1722 if( gv.getNumGlyphs() <= 0 )
1723 return;
1725 if (customPaint)
1726 setCustomPaint(gv.getOutline().getBounds());
1728 if (comp instanceof AlphaComposite)
1729 alpha = ((AlphaComposite) comp).getAlpha();
1731 setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING)
1732 .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
1733 ignoreAA = true;
1735 if (gv instanceof FreetypeGlyphVector && alpha == 1.0
1736 && !((FreetypeGlyphVector)gv).hasTransforms())
1738 int n = gv.getNumGlyphs ();
1739 int[] codes = gv.getGlyphCodes (0, n, null);
1740 long[] fontset = ((FreetypeGlyphVector)gv).getGlyphFonts (0, n, null);
1741 float[] positions = gv.getGlyphPositions (0, n, null);
1743 setFont (gv.getFont ());
1744 GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
1745 synchronized (fontPeer)
1747 cairoDrawGlyphVector(nativePointer, fontPeer,
1748 x, y, n, codes, positions, fontset);
1751 else
1753 translate(x, y);
1754 fill(gv.getOutline());
1755 translate(-x, -y);
1758 ignoreAA = false;
1761 public void drawString(AttributedCharacterIterator ci, float x, float y)
1763 GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci);
1764 drawGlyphVector(gv, x, y);
1768 * Should perhaps be contexct dependent, but this is left for now as an
1769 * overloadable default implementation.
1771 public FontRenderContext getFontRenderContext()
1773 return new FontRenderContext(transform, true, true);
1776 // Until such time as pango is happy to talk directly to cairo, we
1777 // actually need to redirect some calls from the GtkFontPeer and
1778 // GtkFontMetrics into the drawing kit and ask cairo ourselves.
1780 public FontMetrics getFontMetrics()
1782 return getFontMetrics(getFont());
1785 public FontMetrics getFontMetrics(Font f)
1787 return ((GdkFontPeer) f.getPeer()).getFontMetrics(f);
1790 public void setFont(Font f)
1792 // Sun's JDK does not throw NPEs, instead it leaves the current setting
1793 // unchanged. So do we.
1794 if (f == null)
1795 return;
1797 if (f.getPeer() instanceof GdkFontPeer)
1798 font = f;
1799 else
1800 font =
1801 ((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
1802 .getFont(f.getName(), f.getAttributes());
1804 GdkFontPeer fontpeer = (GdkFontPeer) getFont().getPeer();
1805 synchronized (fontpeer)
1807 cairoSetFont(nativePointer, fontpeer);
1811 public Font getFont()
1813 if (font == null)
1814 return new Font("SansSerif", Font.PLAIN, 12);
1815 return font;
1818 /////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
1820 public boolean hit(Rectangle rect, Shape s, boolean onStroke)
1822 if( onStroke )
1824 Shape stroked = stroke.createStrokedShape( s );
1825 return stroked.intersects( (double)rect.x, (double)rect.y,
1826 (double)rect.width, (double)rect.height );
1828 return s.intersects( (double)rect.x, (double)rect.y,
1829 (double)rect.width, (double)rect.height );
1832 public String toString()
1834 return (getClass().getName()
1835 + "[font=" + getFont().toString()
1836 + ",color=" + fg.toString()
1837 + "]");
1840 ///////////////////////// PRIVATE METHODS ///////////////////////////////////
1843 * All the drawImage() methods eventually get delegated here if the image
1844 * is not a Cairo surface.
1846 * @param bgcolor - if non-null draws the background color before
1847 * drawing the image.
1849 private boolean drawRaster(ColorModel cm, Raster r,
1850 AffineTransform imageToUser, Color bgcolor)
1852 if (r == null)
1853 return false;
1855 SampleModel sm = r.getSampleModel();
1856 DataBuffer db = r.getDataBuffer();
1858 if (db == null || sm == null)
1859 return false;
1861 if (cm == null)
1862 cm = ColorModel.getRGBdefault();
1864 double[] i2u = new double[6];
1865 if (imageToUser != null)
1866 imageToUser.getMatrix(i2u);
1867 else
1869 i2u[0] = 1;
1870 i2u[1] = 0;
1871 i2u[2] = 0;
1872 i2u[3] = 1;
1873 i2u[4] = 0;
1874 i2u[5] = 0;
1877 int[] pixels = findSimpleIntegerArray(cm, r);
1879 if (pixels == null)
1881 // FIXME: I don't think this code will work correctly with a non-RGB
1882 // MultiPixelPackedSampleModel. Although this entire method should
1883 // probably be rewritten to better utilize Cairo's different supported
1884 // data formats.
1885 if (sm instanceof MultiPixelPackedSampleModel)
1887 pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
1888 for (int i = 0; i < pixels.length; i++)
1889 pixels[i] = cm.getRGB(pixels[i]);
1891 else
1893 pixels = new int[r.getWidth() * r.getHeight()];
1894 for (int i = 0; i < pixels.length; i++)
1895 pixels[i] = cm.getRGB(db.getElem(i));
1899 // Change all transparent pixels in the image to the specified bgcolor,
1900 // or (if there's no alpha) fill in an alpha channel so that it paints
1901 // correctly.
1902 if (cm.hasAlpha())
1904 if (bgcolor != null && cm.hasAlpha())
1905 for (int i = 0; i < pixels.length; i++)
1907 if (cm.getAlpha(pixels[i]) == 0)
1908 pixels[i] = bgcolor.getRGB();
1911 else
1912 for (int i = 0; i < pixels.length; i++)
1913 pixels[i] |= 0xFF000000;
1915 double alpha = 1.0;
1916 if (comp instanceof AlphaComposite)
1917 alpha = ((AlphaComposite) comp).getAlpha();
1919 drawPixels(nativePointer, pixels, r.getWidth(), r.getHeight(),
1920 r.getWidth(), i2u, alpha, getInterpolation());
1922 // Cairo seems to lose the current color which must be restored.
1923 updateColor();
1925 return true;
1929 * Shifts an x-coordinate by 0.5 in device space.
1931 private double shiftX(double coord, boolean doShift)
1933 if (doShift)
1935 double shift = 0.5;
1936 if (!transform.isIdentity())
1937 shift /= transform.getScaleX();
1938 return (coord + shift);
1940 else
1941 return coord;
1945 * Shifts a y-coordinate by 0.5 in device space.
1947 private double shiftY(double coord, boolean doShift)
1949 if (doShift)
1951 double shift = 0.5;
1952 if (!transform.isIdentity())
1953 shift /= transform.getScaleY();
1954 return (coord + shift);
1956 else
1957 return coord;
1961 * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
1963 private void walkPath(PathIterator p, boolean doShift)
1965 double x = 0;
1966 double y = 0;
1967 double[] coords = new double[6];
1969 cairoSetFillRule(nativePointer, p.getWindingRule());
1970 for (; ! p.isDone(); p.next())
1972 int seg = p.currentSegment(coords);
1973 switch (seg)
1975 case PathIterator.SEG_MOVETO:
1976 x = shiftX(coords[0], doShift);
1977 y = shiftY(coords[1], doShift);
1978 cairoMoveTo(nativePointer, x, y);
1979 break;
1980 case PathIterator.SEG_LINETO:
1981 x = shiftX(coords[0], doShift);
1982 y = shiftY(coords[1], doShift);
1983 cairoLineTo(nativePointer, x, y);
1984 break;
1985 case PathIterator.SEG_QUADTO:
1986 // splitting a quadratic bezier into a cubic:
1987 // see: http://pfaedit.sourceforge.net/bezier.html
1988 double x1 = x + (2.0 / 3.0) * (shiftX(coords[0], doShift) - x);
1989 double y1 = y + (2.0 / 3.0) * (shiftY(coords[1], doShift) - y);
1991 double x2 = x1 + (1.0 / 3.0) * (shiftX(coords[2], doShift) - x);
1992 double y2 = y1 + (1.0 / 3.0) * (shiftY(coords[3], doShift) - y);
1994 x = shiftX(coords[2], doShift);
1995 y = shiftY(coords[3], doShift);
1996 cairoCurveTo(nativePointer, x1, y1, x2, y2, x, y);
1997 break;
1998 case PathIterator.SEG_CUBICTO:
1999 x = shiftX(coords[4], doShift);
2000 y = shiftY(coords[5], doShift);
2001 cairoCurveTo(nativePointer, shiftX(coords[0], doShift),
2002 shiftY(coords[1], doShift),
2003 shiftX(coords[2], doShift),
2004 shiftY(coords[3], doShift), x, y);
2005 break;
2006 case PathIterator.SEG_CLOSE:
2007 cairoClosePath(nativePointer);
2008 break;
2014 * Used by setRenderingHints()
2016 private Map<RenderingHints.Key, Object> getDefaultHints()
2018 HashMap<RenderingHints.Key, Object> defaultHints =
2019 new HashMap<RenderingHints.Key, Object>();
2021 defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
2022 RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
2024 defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
2025 RenderingHints.VALUE_STROKE_DEFAULT);
2027 defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
2028 RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
2030 defaultHints.put(RenderingHints.KEY_ANTIALIASING,
2031 RenderingHints.VALUE_ANTIALIAS_OFF);
2033 defaultHints.put(RenderingHints.KEY_RENDERING,
2034 RenderingHints.VALUE_RENDER_DEFAULT);
2036 return defaultHints;
2040 * Used by drawRaster and GdkPixbufDecoder
2042 public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
2044 if (cm == null || raster == null)
2045 return null;
2047 if (! cm.getColorSpace().isCS_sRGB())
2048 return null;
2050 if (! (cm instanceof DirectColorModel))
2051 return null;
2053 DirectColorModel dcm = (DirectColorModel) cm;
2055 if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
2056 || dcm.getBlueMask() != 0x000000FF)
2057 return null;
2059 if (! (raster instanceof WritableRaster))
2060 return null;
2062 if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
2063 return null;
2065 if (! (raster.getDataBuffer() instanceof DataBufferInt))
2066 return null;
2068 DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
2070 if (db.getNumBanks() != 1)
2071 return null;
2073 // Finally, we have determined that this is a single bank, [A]RGB-int
2074 // buffer in sRGB space. It's worth checking all this, because it means
2075 // that cairo can paint directly into the data buffer, which is very
2076 // fast compared to all the normal copying and converting.
2078 return db.getData();
2082 * Helper method to transform the clip. This is called by the various
2083 * transformation-manipulation methods to update the clip (which is in
2084 * userspace) accordingly.
2086 * The transform usually is the inverse transform that was applied to the
2087 * graphics object.
2089 * @param t the transform to apply to the clip
2091 private void updateClip(AffineTransform t)
2093 if (clip == null)
2094 return;
2096 // If the clip is a rectangle, and the transformation preserves the shape
2097 // (translate/stretch only), then keep the clip as a rectangle
2098 double[] matrix = new double[4];
2099 t.getMatrix(matrix);
2100 if (clip instanceof Rectangle2D && matrix[1] == 0 && matrix[2] == 0)
2102 Rectangle2D rect = (Rectangle2D)clip;
2103 double[] origin = new double[] {rect.getX(), rect.getY()};
2104 double[] dimensions = new double[] {rect.getWidth(), rect.getHeight()};
2105 t.transform(origin, 0, origin, 0, 1);
2106 t.deltaTransform(dimensions, 0, dimensions, 0, 1);
2107 rect.setRect(origin[0], origin[1], dimensions[0], dimensions[1]);
2109 else
2111 if (! (clip instanceof GeneralPath))
2112 clip = new GeneralPath(clip);
2114 GeneralPath p = (GeneralPath) clip;
2115 p.transform(t);
2119 private static Rectangle computeIntersection(int x, int y, int w, int h,
2120 Rectangle rect)
2122 int x2 = rect.x;
2123 int y2 = rect.y;
2124 int w2 = rect.width;
2125 int h2 = rect.height;
2127 int dx = (x > x2) ? x : x2;
2128 int dy = (y > y2) ? y : y2;
2129 int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx);
2130 int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy);
2132 if (dw >= 0 && dh >= 0)
2133 rect.setBounds(dx, dy, dw, dh);
2134 else
2135 rect.setBounds(0, 0, 0, 0);
2137 return rect;
2140 static Rectangle2D getTransformedBounds(Rectangle2D bounds, AffineTransform tx)
2142 double x1 = bounds.getX();
2143 double x2 = bounds.getX() + bounds.getWidth();
2144 double x3 = x1;
2145 double x4 = x2;
2146 double y1 = bounds.getY();
2147 double y2 = y1;
2148 double y3 = bounds.getY() + bounds.getHeight();
2149 double y4 = y3;
2151 double[] points = new double[] {x1, y1, x2, y2, x3, y3, x4, y4};
2152 tx.transform(points, 0, points, 0, 4);
2154 double minX = points[0];
2155 double maxX = minX;
2156 double minY = points[1];
2157 double maxY = minY;
2158 for (int i = 0; i < 8; i++)
2160 if (points[i] < minX)
2161 minX = points[i];
2162 if (points[i] > maxX)
2163 maxX = points[i];
2164 i++;
2166 if (points[i] < minY)
2167 minY = points[i];
2168 if (points[i] > maxY)
2169 maxY = points[i];
2172 return new Rectangle2D.Double(minX, minY, (maxX - minX), (maxY - minY));