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
.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
;
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:
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
125 if (true) // GCJ LOCAL
127 System
.loadLibrary("gtkpeer");
132 * Important: This is a pointer to the native cairographics2d structure
134 * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
138 // Drawing state variables
151 * Current foreground and background color.
156 * Current clip shape.
163 AffineTransform transform
;
171 * The current compositing context, if any.
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,
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
);
253 setRenderingHints(g
.hints
);
257 if (g
.fg
.getAlpha() != -1)
258 foreground
= new Color(g
.fg
.getRed(), g
.fg
.getGreen(), g
.fg
.getBlue(),
261 foreground
= new Color(g
.fg
.getRGB());
265 if (g
.bg
.getAlpha() != -1)
266 bg
= new Color(g
.bg
.getRed(), g
.bg
.getGreen(), g
.bg
.getBlue(),
269 bg
= new Color(g
.bg
.getRGB());
272 firstClip
= g
.firstClip
;
273 originalClip
= g
.originalClip
;
276 if (g
.transform
== null)
279 transform
= new AffineTransform(g
.transform
);
282 setColor(foreground
);
286 setTransformImpl(transform
);
290 antialias
= !g
.antialias
;
291 setAntialias(g
.antialias
);
295 * Generic destructor - call the native dispose() method.
297 public void finalize()
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
);
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
,
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
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
,
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
,
368 * Set the current transform matrix
370 protected native void cairoSetMatrix(long pointer
, double[] m
);
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
);
403 protected native void cairoSetDash(long pointer
, double[] dashes
, int ndash
,
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
);
439 protected native void cairoNewPath(long pointer
);
444 protected native void cairoClosePath(long pointer
);
447 protected native void cairoMoveTo(long pointer
, double x
, double y
);
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
);
465 protected native void cairoFill(long pointer
, double alpha
);
470 protected native void cairoClip(long pointer
);
475 protected native void cairoResetClip(long pointer
);
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();
510 private void setTransformImpl(AffineTransform 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
);
526 transform
.concatenate(tx
);
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
);
569 transform
= AffineTransform
.getTranslateInstance(tx
, ty
);
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(),
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.
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.
617 // If the current clip is still null, initialize it.
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
);
634 if (clip
instanceof Area
)
635 current
= (Area
) clip
;
637 current
= new Area(clip
);
640 if (s
instanceof Area
)
641 intersect
= (Area
) s
;
643 intersect
= new Area(s
);
645 current
.intersect(intersect
);
647 // Call setClip so that the native side gets notified.
652 public Paint
getPaint()
657 public AffineTransform
getTransform()
659 return (AffineTransform
) transform
.clone();
662 public void setPaint(Paint p
)
668 if (paint
instanceof Color
)
670 setColor((Color
) paint
);
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);
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(),
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
)
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
,
744 Raster raster
= pc
.getRaster(deviceX
, deviceY
, deviceWidth
,
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
759 setPaintPixels(nativePointer
,
760 (int[])raster
.getDataElements(0, 0, deviceWidth
,
762 deviceWidth
, deviceHeight
, deviceWidth
, false,
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
,
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
);
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(),
790 BufferedImage img2
= new BufferedImage(pc
.getColorModel(), wr
,
791 pc
.getColorModel().isAlphaPremultiplied(),
794 setPaintPixels(nativePointer
,
795 img2
.getRGB(0, 0, deviceWidth
, deviceHeight
, null, 0,
797 deviceWidth
, deviceHeight
, deviceWidth
, false,
802 setTransformImpl(oldTx
);
805 public Stroke
getStroke()
810 public void setStroke(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();
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());
830 cairoSetDash(nativePointer
, new double[0], 0, 0.0);
835 * Utility method to find the bounds of a shape, including the stroke width.
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
;
854 Shape s2
= stroke
.createStrokedShape(s
);
861 public void setPaintMode()
863 setComposite(AlphaComposite
.SrcOver
);
866 public void setXORMode(Color c
)
871 public void setColor(Color c
)
882 * Set the current fg value as the cairo color.
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()
899 public void clipRect(int x
, int y
, int width
, int height
)
902 setClip(new Rectangle(x
, y
, width
, height
));
903 else if (clip
instanceof Rectangle
)
905 computeIntersection(x
, y
, width
, height
, (Rectangle
) clip
);
909 clip(new Rectangle(x
, y
, width
, height
));
912 public Shape
getClip()
916 else if (clip
instanceof Rectangle2D
)
917 return clip
.getBounds2D(); //getClipInDevSpace();
920 GeneralPath p
= new GeneralPath();
921 PathIterator pi
= clip
.getPathIterator(null);
927 public Rectangle
getClipBounds()
932 return clip
.getBounds();
935 protected Rectangle2D
getClipInDevSpace()
937 Rectangle2D uclip
= clip
.getBounds2D();
938 if (transform
== null)
941 return getTransformedBounds(clip
.getBounds2D(), transform
);
944 public void setClip(int x
, int y
, int width
, int height
)
946 if( width
< 0 || height
< 0 )
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.
965 cairoResetClip(nativePointer
);
969 cairoNewPath(nativePointer
);
970 if (clip
instanceof Rectangle2D
)
972 Rectangle2D r
= (Rectangle2D
) clip
;
973 cairoRectangle(nativePointer
, r
.getX(), r
.getY(), r
.getWidth(),
977 walkPath(clip
.getPathIterator(null), false);
979 cairoClip(nativePointer
);
983 public void setBackground(Color c
)
990 public Color
getBackground()
996 * Return the current composite.
998 public Composite
getComposite()
1001 return AlphaComposite
.SrcOver
;
1007 * Sets the current composite context.
1009 public void setComposite(Composite comp
)
1011 if (this.comp
== comp
)
1015 if (compCtx
!= null)
1019 if (comp
instanceof AlphaComposite
)
1021 AlphaComposite a
= (AlphaComposite
) comp
;
1022 cairoSetOperator(nativePointer
, a
.getRule());
1027 cairoSetOperator(nativePointer
, AlphaComposite
.SRC_OVER
);
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();
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
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
));
1077 Rectangle r
= findStrokedBounds(s
);
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);
1092 setCustomPaint(s
.getBounds());
1094 setAntialias(!hints
.get(RenderingHints
.KEY_ANTIALIASING
)
1095 .equals(RenderingHints
.VALUE_ANTIALIAS_OFF
));
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);
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
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
)
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
);
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
);
1200 public void drawArc(int x
, int y
, int width
, int height
, int startAngle
,
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));
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
,
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
,
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
,
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
,
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
),
1285 Point2D dim
= transform
.transform(new Point2D
.Double(ox
+ owidth
,
1288 Point2D p2
= transform
.transform(new Point2D
.Double(ox
+ odx
, oy
+ ody
),
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 )
1301 // Return if outside the surface
1302 if( x
+ dx
> r
.getWidth() || y
+ dy
> r
.getHeight() )
1305 if( x
+ dx
+ width
< r
.getX() || y
+ dy
+ height
< r
.getY() )
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)
1356 compCtx
= comp
.createContext(getNativeCM(), getNativeCM(), this.hints
);
1360 public void addRenderingHints(Map hints
)
1362 this.hints
.putAll(hints
);
1365 public RenderingHints
getRenderingHints()
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
)
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
)
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
1446 img
= AsyncImage
.realImage(img
, obs
);
1447 if( !(img
instanceof BufferedImage
) )
1449 ImageProducer source
= img
.getSource();
1452 img
= Toolkit
.getDefaultToolkit().createImage(source
);
1455 BufferedImage b
= (BufferedImage
) img
;
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
);
1467 raster
= b
.getRaster();
1469 invertedXform
.getMatrix(i2u
);
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());
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.
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
;
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,
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 )
1563 return drawImage(img
, new AffineTransform(scaleX
, 0f
, 0f
, scaleY
, x
, y
),
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
)
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 ||
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();
1598 { cx
= dx1
; cw
= dx2
- dx1
; }
1600 { cx
= dx2
; cw
= dx1
- dx2
; }
1602 { cy
= dy1
; ch
= dy2
- dy1
; }
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
);
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
1660 Shape newBounds
= tx
.createTransformedShape(bounds
);
1661 cairoSave(nativePointer
);
1662 walkPath(newBounds
.getPathIterator(null), false);
1663 cairoClip(nativePointer
);
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.
1680 cairoRestore(nativePointer
);
1684 ///////////////////////// TEXT METHODS ////////////////////////////////////
1686 public void drawString(String str
, float x
, float y
)
1688 if (str
== null || str
.length() == 0)
1690 GdkFontPeer fontPeer
= (GdkFontPeer
) font
.getPeer();
1691 TextLayout tl
= (TextLayout
) fontPeer
.textLayoutCache
.get(str
);
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
));
1704 tl
.draw(this, x
, y
);
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
)
1722 if( gv
.getNumGlyphs() <= 0 )
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
));
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
);
1754 fill(gv
.getOutline());
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.
1797 if (f
.getPeer() instanceof GdkFontPeer
)
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()
1814 return new Font("SansSerif", Font
.PLAIN
, 12);
1818 /////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
1820 public boolean hit(Rectangle rect
, Shape s
, boolean 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()
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
)
1855 SampleModel sm
= r
.getSampleModel();
1856 DataBuffer db
= r
.getDataBuffer();
1858 if (db
== null || sm
== null)
1862 cm
= ColorModel
.getRGBdefault();
1864 double[] i2u
= new double[6];
1865 if (imageToUser
!= null)
1866 imageToUser
.getMatrix(i2u
);
1877 int[] pixels
= findSimpleIntegerArray(cm
, r
);
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
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
]);
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
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();
1912 for (int i
= 0; i
< pixels
.length
; i
++)
1913 pixels
[i
] |= 0xFF000000;
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.
1929 * Shifts an x-coordinate by 0.5 in device space.
1931 private double shiftX(double coord
, boolean doShift
)
1936 if (!transform
.isIdentity())
1937 shift
/= transform
.getScaleX();
1938 return (coord
+ shift
);
1945 * Shifts a y-coordinate by 0.5 in device space.
1947 private double shiftY(double coord
, boolean doShift
)
1952 if (!transform
.isIdentity())
1953 shift
/= transform
.getScaleY();
1954 return (coord
+ shift
);
1961 * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
1963 private void walkPath(PathIterator p
, boolean doShift
)
1967 double[] coords
= new double[6];
1969 cairoSetFillRule(nativePointer
, p
.getWindingRule());
1970 for (; ! p
.isDone(); p
.next())
1972 int seg
= p
.currentSegment(coords
);
1975 case PathIterator
.SEG_MOVETO
:
1976 x
= shiftX(coords
[0], doShift
);
1977 y
= shiftY(coords
[1], doShift
);
1978 cairoMoveTo(nativePointer
, x
, y
);
1980 case PathIterator
.SEG_LINETO
:
1981 x
= shiftX(coords
[0], doShift
);
1982 y
= shiftY(coords
[1], doShift
);
1983 cairoLineTo(nativePointer
, x
, y
);
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
);
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
);
2006 case PathIterator
.SEG_CLOSE
:
2007 cairoClosePath(nativePointer
);
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)
2047 if (! cm
.getColorSpace().isCS_sRGB())
2050 if (! (cm
instanceof DirectColorModel
))
2053 DirectColorModel dcm
= (DirectColorModel
) cm
;
2055 if (dcm
.getRedMask() != 0x00FF0000 || dcm
.getGreenMask() != 0x0000FF00
2056 || dcm
.getBlueMask() != 0x000000FF)
2059 if (! (raster
instanceof WritableRaster
))
2062 if (raster
.getSampleModel().getDataType() != DataBuffer
.TYPE_INT
)
2065 if (! (raster
.getDataBuffer() instanceof DataBufferInt
))
2068 DataBufferInt db
= (DataBufferInt
) raster
.getDataBuffer();
2070 if (db
.getNumBanks() != 1)
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
2089 * @param t the transform to apply to the clip
2091 private void updateClip(AffineTransform t
)
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]);
2111 if (! (clip
instanceof GeneralPath
))
2112 clip
= new GeneralPath(clip
);
2114 GeneralPath p
= (GeneralPath
) clip
;
2119 private static Rectangle
computeIntersection(int x
, int y
, int w
, int h
,
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
);
2135 rect
.setBounds(0, 0, 0, 0);
2140 static Rectangle2D
getTransformedBounds(Rectangle2D bounds
, AffineTransform tx
)
2142 double x1
= bounds
.getX();
2143 double x2
= bounds
.getX() + bounds
.getWidth();
2146 double y1
= bounds
.getY();
2148 double y3
= bounds
.getY() + bounds
.getHeight();
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];
2156 double minY
= points
[1];
2158 for (int i
= 0; i
< 8; i
++)
2160 if (points
[i
] < minX
)
2162 if (points
[i
] > maxX
)
2166 if (points
[i
] < minY
)
2168 if (points
[i
] > maxY
)
2172 return new Rectangle2D
.Double(minX
, minY
, (maxX
- minX
), (maxY
- minY
));