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)
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
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
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
;
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
;
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:
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
123 System
.loadLibrary("gtkpeer");
127 * Important: This is a pointer to the native cairographics2d structure
129 * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
133 // Drawing state variables
146 * Current foreground and background color.
151 * Current clip shape.
158 AffineTransform transform
;
166 * The current compositing context, if any.
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,
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
);
248 setRenderingHints(g
.hints
);
252 if (g
.fg
.getAlpha() != -1)
253 foreground
= new Color(g
.fg
.getRed(), g
.fg
.getGreen(), g
.fg
.getBlue(),
256 foreground
= new Color(g
.fg
.getRGB());
260 if (g
.bg
.getAlpha() != -1)
261 bg
= new Color(g
.bg
.getRed(), g
.bg
.getGreen(), g
.bg
.getBlue(),
264 bg
= new Color(g
.bg
.getRGB());
267 firstClip
= g
.firstClip
;
268 originalClip
= g
.originalClip
;
271 if (g
.transform
== null)
274 transform
= new AffineTransform(g
.transform
);
277 setColor(foreground
);
281 setTransformImpl(transform
);
285 antialias
= !g
.antialias
;
286 setAntialias(g
.antialias
);
290 * Generic destructor - call the native dispose() method.
292 public void finalize()
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
);
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
,
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
,
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
,
362 * Set the current transform matrix
364 protected native void cairoSetMatrix(long pointer
, double[] m
);
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
);
397 protected native void cairoSetDash(long pointer
, double[] dashes
, int ndash
,
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
);
433 protected native void cairoNewPath(long pointer
);
438 protected native void cairoClosePath(long pointer
);
441 protected native void cairoMoveTo(long pointer
, double x
, double y
);
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
);
459 protected native void cairoFill(long pointer
, double alpha
);
464 protected native void cairoClip(long pointer
);
469 protected native void cairoResetClip(long pointer
);
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();
504 private void setTransformImpl(AffineTransform 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
);
520 transform
.concatenate(tx
);
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
);
563 transform
= AffineTransform
.getTranslateInstance(tx
, ty
);
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(),
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.
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.
611 // If the current clip is still null, initialize it.
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
);
628 if (clip
instanceof Area
)
629 current
= (Area
) clip
;
631 current
= new Area(clip
);
634 if (s
instanceof Area
)
635 intersect
= (Area
) s
;
637 intersect
= new Area(s
);
639 current
.intersect(intersect
);
641 // Call setClip so that the native side gets notified.
646 public Paint
getPaint()
651 public AffineTransform
getTransform()
653 return (AffineTransform
) transform
.clone();
656 public void setPaint(Paint p
)
662 if (paint
instanceof Color
)
664 setColor((Color
) paint
);
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);
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(),
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
)
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
,
738 Raster raster
= pc
.getRaster(deviceX
, deviceY
, deviceWidth
,
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
753 setPaintPixels(nativePointer
,
754 (int[])raster
.getDataElements(0, 0, deviceWidth
,
756 deviceWidth
, deviceHeight
, deviceWidth
, false,
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
,
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
);
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(),
784 BufferedImage img2
= new BufferedImage(pc
.getColorModel(), wr
,
785 pc
.getColorModel().isAlphaPremultiplied(),
788 setPaintPixels(nativePointer
,
789 img2
.getRGB(0, 0, deviceWidth
, deviceHeight
, null, 0,
791 deviceWidth
, deviceHeight
, deviceWidth
, false,
796 setTransformImpl(oldTx
);
799 public Stroke
getStroke()
804 public void setStroke(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();
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());
824 cairoSetDash(nativePointer
, new double[0], 0, 0.0);
829 * Utility method to find the bounds of a shape, including the stroke width.
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
;
848 Shape s2
= stroke
.createStrokedShape(s
);
855 public void setPaintMode()
857 setComposite(AlphaComposite
.SrcOver
);
860 public void setXORMode(Color c
)
865 public void setColor(Color c
)
876 * Set the current fg value as the cairo color.
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()
893 public void clipRect(int x
, int y
, int width
, int height
)
896 setClip(new Rectangle(x
, y
, width
, height
));
897 else if (clip
instanceof Rectangle
)
899 computeIntersection(x
, y
, width
, height
, (Rectangle
) clip
);
903 clip(new Rectangle(x
, y
, width
, height
));
906 public Shape
getClip()
910 else if (clip
instanceof Rectangle2D
)
911 return clip
.getBounds2D(); //getClipInDevSpace();
914 GeneralPath p
= new GeneralPath();
915 PathIterator pi
= clip
.getPathIterator(null);
921 public Rectangle
getClipBounds()
926 return clip
.getBounds();
929 protected Rectangle2D
getClipInDevSpace()
931 Rectangle2D uclip
= clip
.getBounds2D();
932 if (transform
== null)
935 return getTransformedBounds(clip
.getBounds2D(), transform
);
938 public void setClip(int x
, int y
, int width
, int height
)
940 if( width
< 0 || height
< 0 )
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.
959 cairoResetClip(nativePointer
);
963 cairoNewPath(nativePointer
);
964 if (clip
instanceof Rectangle2D
)
966 Rectangle2D r
= (Rectangle2D
) clip
;
967 cairoRectangle(nativePointer
, r
.getX(), r
.getY(), r
.getWidth(),
971 walkPath(clip
.getPathIterator(null), false);
973 cairoClip(nativePointer
);
977 public void setBackground(Color c
)
984 public Color
getBackground()
990 * Return the current composite.
992 public Composite
getComposite()
995 return AlphaComposite
.SrcOver
;
1001 * Sets the current composite context.
1003 public void setComposite(Composite comp
)
1005 if (this.comp
== comp
)
1009 if (compCtx
!= null)
1013 if (comp
instanceof AlphaComposite
)
1015 AlphaComposite a
= (AlphaComposite
) comp
;
1016 cairoSetOperator(nativePointer
, a
.getRule());
1021 cairoSetOperator(nativePointer
, AlphaComposite
.SRC_OVER
);
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();
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
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
));
1071 Rectangle r
= findStrokedBounds(s
);
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);
1086 setCustomPaint(s
.getBounds());
1088 setAntialias(!hints
.get(RenderingHints
.KEY_ANTIALIASING
)
1089 .equals(RenderingHints
.VALUE_ANTIALIAS_OFF
));
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);
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
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
)
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
);
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
);
1194 public void drawArc(int x
, int y
, int width
, int height
, int startAngle
,
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));
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
,
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
,
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
,
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
,
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
),
1279 Point2D dim
= transform
.transform(new Point2D
.Double(ox
+ owidth
,
1282 Point2D p2
= transform
.transform(new Point2D
.Double(ox
+ odx
, oy
+ ody
),
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 )
1295 // Return if outside the surface
1296 if( x
+ dx
> r
.getWidth() || y
+ dy
> r
.getHeight() )
1299 if( x
+ dx
+ width
< r
.getX() || y
+ dy
+ height
< r
.getY() )
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)
1350 compCtx
= comp
.createContext(getNativeCM(), getNativeCM(), this.hints
);
1354 public void addRenderingHints(Map hints
)
1356 this.hints
.putAll(hints
);
1359 public RenderingHints
getRenderingHints()
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
)
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
)
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
1440 img
= AsyncImage
.realImage(img
, obs
);
1441 if( !(img
instanceof BufferedImage
) )
1443 ImageProducer source
= img
.getSource();
1446 img
= Toolkit
.getDefaultToolkit().createImage(source
);
1449 BufferedImage b
= (BufferedImage
) img
;
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
);
1461 raster
= b
.getRaster();
1463 invertedXform
.getMatrix(i2u
);
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());
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.
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
;
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,
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 )
1557 return drawImage(img
, new AffineTransform(scaleX
, 0f
, 0f
, scaleY
, x
, y
),
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
)
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 ||
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();
1592 { cx
= dx1
; cw
= dx2
- dx1
; }
1594 { cx
= dx2
; cw
= dx1
- dx2
; }
1596 { cy
= dy1
; ch
= dy2
- dy1
; }
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
);
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
1654 Shape newBounds
= tx
.createTransformedShape(bounds
);
1655 cairoSave(nativePointer
);
1656 walkPath(newBounds
.getPathIterator(null), false);
1657 cairoClip(nativePointer
);
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.
1674 cairoRestore(nativePointer
);
1678 ///////////////////////// TEXT METHODS ////////////////////////////////////
1680 public void drawString(String str
, float x
, float y
)
1682 if (str
== null || str
.length() == 0)
1684 GdkFontPeer fontPeer
= (GdkFontPeer
) font
.getPeer();
1685 TextLayout tl
= (TextLayout
) fontPeer
.textLayoutCache
.get(str
);
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
));
1698 tl
.draw(this, x
, y
);
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
)
1716 if( gv
.getNumGlyphs() <= 0 )
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
));
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
);
1748 fill(gv
.getOutline());
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.
1791 if (f
.getPeer() instanceof GdkFontPeer
)
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()
1808 return new Font("SansSerif", Font
.PLAIN
, 12);
1812 /////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
1814 public boolean hit(Rectangle rect
, Shape s
, boolean 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()
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
)
1849 SampleModel sm
= r
.getSampleModel();
1850 DataBuffer db
= r
.getDataBuffer();
1852 if (db
== null || sm
== null)
1856 cm
= ColorModel
.getRGBdefault();
1858 double[] i2u
= new double[6];
1859 if (imageToUser
!= null)
1860 imageToUser
.getMatrix(i2u
);
1871 int[] pixels
= findSimpleIntegerArray(cm
, r
);
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
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
]);
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
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();
1906 for (int i
= 0; i
< pixels
.length
; i
++)
1907 pixels
[i
] |= 0xFF000000;
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.
1923 * Shifts an x-coordinate by 0.5 in device space.
1925 private double shiftX(double coord
, boolean doShift
)
1930 if (!transform
.isIdentity())
1931 shift
/= transform
.getScaleX();
1932 return (coord
+ shift
);
1939 * Shifts a y-coordinate by 0.5 in device space.
1941 private double shiftY(double coord
, boolean doShift
)
1946 if (!transform
.isIdentity())
1947 shift
/= transform
.getScaleY();
1948 return (coord
+ shift
);
1955 * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
1957 private void walkPath(PathIterator p
, boolean doShift
)
1961 double[] coords
= new double[6];
1963 cairoSetFillRule(nativePointer
, p
.getWindingRule());
1964 for (; ! p
.isDone(); p
.next())
1966 int seg
= p
.currentSegment(coords
);
1969 case PathIterator
.SEG_MOVETO
:
1970 x
= shiftX(coords
[0], doShift
);
1971 y
= shiftY(coords
[1], doShift
);
1972 cairoMoveTo(nativePointer
, x
, y
);
1974 case PathIterator
.SEG_LINETO
:
1975 x
= shiftX(coords
[0], doShift
);
1976 y
= shiftY(coords
[1], doShift
);
1977 cairoLineTo(nativePointer
, x
, y
);
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
);
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
);
2000 case PathIterator
.SEG_CLOSE
:
2001 cairoClosePath(nativePointer
);
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)
2041 if (! cm
.getColorSpace().isCS_sRGB())
2044 if (! (cm
instanceof DirectColorModel
))
2047 DirectColorModel dcm
= (DirectColorModel
) cm
;
2049 if (dcm
.getRedMask() != 0x00FF0000 || dcm
.getGreenMask() != 0x0000FF00
2050 || dcm
.getBlueMask() != 0x000000FF)
2053 if (! (raster
instanceof WritableRaster
))
2056 if (raster
.getSampleModel().getDataType() != DataBuffer
.TYPE_INT
)
2059 if (! (raster
.getDataBuffer() instanceof DataBufferInt
))
2062 DataBufferInt db
= (DataBufferInt
) raster
.getDataBuffer();
2064 if (db
.getNumBanks() != 1)
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
2083 * @param t the transform to apply to the clip
2085 private void updateClip(AffineTransform t
)
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]);
2105 if (! (clip
instanceof GeneralPath
))
2106 clip
= new GeneralPath(clip
);
2108 GeneralPath p
= (GeneralPath
) clip
;
2113 private static Rectangle
computeIntersection(int x
, int y
, int w
, int h
,
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
);
2129 rect
.setBounds(0, 0, 0, 0);
2134 static Rectangle2D
getTransformedBounds(Rectangle2D bounds
, AffineTransform tx
)
2136 double x1
= bounds
.getX();
2137 double x2
= bounds
.getX() + bounds
.getWidth();
2140 double y1
= bounds
.getY();
2142 double y3
= bounds
.getY() + bounds
.getHeight();
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];
2150 double minY
= points
[1];
2152 for (int i
= 0; i
< 8; i
++)
2154 if (points
[i
] < minX
)
2156 if (points
[i
] > maxX
)
2160 if (points
[i
] < minY
)
2162 if (points
[i
] > maxY
)
2166 return new Rectangle2D
.Double(minX
, minY
, (maxX
- minX
), (maxY
- minY
));