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;
42 import gnu.java.awt.ClasspathToolkit;
44 import java.awt.AlphaComposite;
45 import java.awt.BasicStroke;
46 import java.awt.Color;
47 import java.awt.Composite;
48 import java.awt.Dimension;
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.Rectangle;
58 import java.awt.RenderingHints;
59 import java.awt.Shape;
60 import java.awt.Stroke;
61 import java.awt.Polygon;
62 import java.awt.TexturePaint;
63 import java.awt.Toolkit;
64 import java.awt.font.FontRenderContext;
65 import java.awt.font.GlyphVector;
66 import java.awt.geom.AffineTransform;
67 import java.awt.geom.Arc2D;
68 import java.awt.geom.Area;
69 import java.awt.geom.Line2D;
70 import java.awt.geom.GeneralPath;
71 import java.awt.geom.NoninvertibleTransformException;
72 import java.awt.geom.PathIterator;
73 import java.awt.geom.Point2D;
74 import java.awt.geom.Rectangle2D;
75 import java.awt.geom.RoundRectangle2D;
76 import java.awt.image.AffineTransformOp;
77 import java.awt.image.BufferedImage;
78 import java.awt.image.BufferedImageOp;
79 import java.awt.image.ColorModel;
80 import java.awt.image.CropImageFilter;
81 import java.awt.image.DataBuffer;
82 import java.awt.image.DataBufferInt;
83 import java.awt.image.DirectColorModel;
84 import java.awt.image.FilteredImageSource;
85 import java.awt.image.ImageObserver;
86 import java.awt.image.ImagingOpException;
87 import java.awt.image.MultiPixelPackedSampleModel;
88 import java.awt.image.Raster;
89 import java.awt.image.RenderedImage;
90 import java.awt.image.SampleModel;
91 import java.awt.image.WritableRaster;
92 import java.awt.image.renderable.RenderContext;
93 import java.awt.image.renderable.RenderableImage;
94 import java.text.AttributedCharacterIterator;
95 import java.util.HashMap;
97 import java.util.Stack;
100 * This is an abstract implementation of Graphics2D on Cairo.
102 * It should be subclassed for different Cairo contexts.
104 * Note for subclassers: Apart from the constructor (see comments below),
105 * The following abstract methods must be implemented:
108 * GraphicsConfiguration getDeviceConfiguration()
109 * copyArea(int x, int y, int width, int height, int dx, int dy)
111 * Also, dispose() must be overloaded to free any native datastructures
112 * used by subclass and in addition call super.dispose() to free the
113 * native cairographics2d structure and cairo_t.
115 * @author Sven de Marothy
117 public abstract class CairoGraphics2D extends Graphics2D
121 System.loadLibrary("gtkpeer");
125 * Important: This is a pointer to the native cairographics2d structure
127 * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
131 // Drawing state variables
143 * Current foreground and background color.
148 * Current clip shape.
155 AffineTransform transform;
163 * The current compositing context, if any.
168 * Rendering hint map.
170 private RenderingHints hints;
173 * Some operations (drawing rather than filling) require that their
174 * coords be shifted to land on 0.5-pixel boundaries, in order to land on
175 * "middle of pixel" coordinates and light up complete pixels.
177 private boolean shiftDrawCalls = false;
180 * Keep track if the first clip to be set, which is restored on setClip(null);
182 private boolean firstClip = true;
183 private Shape originalClip;
186 * Stroke used for 3DRects
188 private static BasicStroke draw3DRectStroke = new BasicStroke();
190 static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
191 static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF,
195 * Constructor does nothing.
197 public CairoGraphics2D()
202 * Sets up the default values and allocates the native cairographics2d structure
203 * @param cairo_t_pointer, a native pointer to a cairo_t of the context.
205 public void setup(long cairo_t_pointer)
207 nativePointer = init(cairo_t_pointer);
208 setRenderingHints(new RenderingHints(getDefaultHints()));
209 font = new Font("SansSerif", Font.PLAIN, 12);
210 setColor(Color.black);
211 setBackground(Color.white);
212 setPaint(Color.black);
213 setStroke(new BasicStroke());
214 setTransform(new AffineTransform());
218 * Same as above, but copies the state of another CairoGraphics2D.
220 public void copy(CairoGraphics2D g, long cairo_t_pointer)
222 nativePointer = init(cairo_t_pointer);
225 setRenderingHints(g.hints);
229 if (g.fg.getAlpha() != -1)
230 foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
233 foreground = new Color(g.fg.getRGB());
237 if (g.bg.getAlpha() != -1)
238 bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
241 bg = new Color(g.bg.getRGB());
247 clip = new Rectangle(g.getClipBounds());
249 if (g.transform == null)
250 transform = new AffineTransform();
252 transform = new AffineTransform(g.transform);
256 setColor(foreground);
260 setTransform(transform);
264 * Generic destructor - call the native dispose() method.
266 public void finalize()
272 * Disposes the native cairographics2d structure, including the
273 * cairo_t and any gradient stuff, if allocated.
274 * Subclasses should of course overload and call this if
275 * they have additional native structures.
277 public void dispose()
284 * Allocate the cairographics2d structure and set the cairo_t pointer in it.
285 * @param pointer - a cairo_t pointer, casted to a long.
287 private native long init(long pointer);
290 * These are declared abstract as there may be context-specific issues.
292 public abstract Graphics create();
294 public abstract GraphicsConfiguration getDeviceConfiguration();
296 protected abstract void copyAreaImpl(int x, int y,
297 int width, int height, int dx, int dy);
300 protected abstract Rectangle2D getRealBounds();
302 ////// Native Methods ////////////////////////////////////////////////////
305 * Dispose of allocate native resouces.
307 public native void disposeNative();
310 * Draw pixels as an RGBA int matrix
311 * @param w, h - width and height
312 * @param stride - stride of the array width
313 * @param i2u - affine transform array
315 private native void drawPixels(int[] pixels, int w, int h, int stride,
318 private native void setGradient(double x1, double y1, double x2, double y2,
319 int r1, int g1, int b1, int a1, int r2,
320 int g2, int b2, int a2, boolean cyclic);
322 private native void setTexturePixels(int[] pixels, int w, int h, int stride);
325 * Set the current transform matrix
327 private native void cairoSetMatrix(double[] m);
330 * Set the compositing operator
332 private native void cairoSetOperator(int cairoOperator);
335 * Sets the current color in RGBA as a 0.0-1.0 double
337 private native void cairoSetRGBAColor(double red, double green,
338 double blue, double alpha);
341 * Sets the current winding rule in Cairo
343 private native void cairoSetFillRule(int cairoFillRule);
346 * Set the line style, cap, join and miter limit.
347 * Cap and join parameters are in the BasicStroke enumerations.
349 private native void cairoSetLine(double width, int cap, int join, double miterLimit);
354 private native void cairoSetDash(double[] dashes, int ndash, double offset);
357 * Draws a Glyph Vector
359 native void cairoDrawGlyphVector(GdkFontPeer font,
360 float x, float y, int n,
361 int[] codes, float[] positions);
364 private native void cairoRelCurveTo(double dx1, double dy1, double dx2,
365 double dy2, double dx3, double dy3);
368 * Appends a rectangle to the current path
370 private native void cairoRectangle(double x, double y, double width,
376 private native void cairoNewPath();
381 private native void cairoClosePath();
384 private native void cairoMoveTo(double x, double y);
386 /** relative moveTo */
387 private native void cairoRelMoveTo(double dx, double dy);
390 private native void cairoLineTo(double x, double y);
392 /** relative lineTo */
393 private native void cairoRelLineTo(double dx, double dy);
395 /** Cubic curve-to */
396 private native void cairoCurveTo(double x1, double y1, double x2, double y2,
397 double x3, double y3);
400 * Stroke current path
402 private native void cairoStroke();
407 private native void cairoFill();
412 private native void cairoClip();
417 private native void cairoPreserveClip();
422 private native void cairoResetClip();
425 * Set interpolation types
427 private native void cairoSurfaceSetFilter(int filter);
429 ///////////////////////// TRANSFORMS ///////////////////////////////////
431 * Set the current transform
433 public void setTransform(AffineTransform tx)
436 if (transform != null)
438 double[] m = new double[6];
439 transform.getMatrix(m);
444 public void transform(AffineTransform tx)
446 if (transform == null)
447 transform = new AffineTransform(tx);
449 transform.concatenate(tx);
450 setTransform(transform);
453 // FIXME: this should actuall try to transform the shape
454 // rather than degrade to bounds.
455 Rectangle2D r = clip.getBounds2D();
456 double[] coords = new double[]
458 r.getX(), r.getY(), r.getX() + r.getWidth(),
459 r.getY() + r.getHeight()
463 tx.createInverse().transform(coords, 0, coords, 0, 2);
464 r.setRect(coords[0], coords[1], coords[2] - coords[0],
465 coords[3] - coords[1]);
468 catch (java.awt.geom.NoninvertibleTransformException e)
474 public void rotate(double theta)
476 transform(AffineTransform.getRotateInstance(theta));
479 public void rotate(double theta, double x, double y)
481 transform(AffineTransform.getRotateInstance(theta, x, y));
484 public void scale(double sx, double sy)
486 transform(AffineTransform.getScaleInstance(sx, sy));
490 * Translate the system of the co-ordinates. As translation is a frequent
491 * operation, it is done in an optimised way, unlike scaling and rotating.
493 public void translate(double tx, double ty)
495 if (transform != null)
496 transform.translate(tx, ty);
498 transform = AffineTransform.getTranslateInstance(tx, ty);
502 // FIXME: this should actuall try to transform the shape
503 // rather than degrade to bounds.
506 if (clip instanceof Rectangle2D)
507 r = (Rectangle2D) clip;
509 r = clip.getBounds2D();
511 r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(), r.getHeight());
515 setTransform(transform);
518 public void translate(int x, int y)
520 translate((double) x, (double) y);
523 public void shear(double shearX, double shearY)
525 transform(AffineTransform.getShearInstance(shearX, shearY));
528 ///////////////////////// DRAWING STATE ///////////////////////////////////
530 public void clip(Shape s)
532 // Do not touch clip when s == null.
536 // If the current clip is still null, initialize it.
540 // This is so common, let's optimize this.
541 else if (clip instanceof Rectangle2D && s instanceof Rectangle2D)
543 Rectangle2D clipRect = (Rectangle2D) clip;
544 Rectangle2D r = (Rectangle2D) s;
545 Rectangle2D.intersect(clipRect, r, clipRect);
546 // Call setClip so that subclasses get notified.
552 if (clip instanceof Area)
553 current = (Area) clip;
555 current = new Area(clip);
558 if (s instanceof Area)
559 intersect = (Area) s;
561 intersect = new Area(s);
563 current.intersect(intersect);
565 // Call setClip so that the native side gets notified.
570 public Paint getPaint()
575 public AffineTransform getTransform()
577 return (AffineTransform) transform.clone();
580 public void setPaint(Paint p)
586 if (paint instanceof Color)
588 setColor((Color) paint);
590 else if (paint instanceof TexturePaint)
592 TexturePaint tp = (TexturePaint) paint;
593 BufferedImage img = tp.getImage();
595 // map the image to the anchor rectangle
596 int width = (int) tp.getAnchorRect().getWidth();
597 int height = (int) tp.getAnchorRect().getHeight();
599 double scaleX = (width+1) / (double) img.getWidth();
600 double scaleY = (height+1) / (double) img.getHeight();
602 AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
603 AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
604 BufferedImage texture = op.filter(img, null);
605 int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
606 setTexturePixels(pixels, width, height, width);
608 else if (paint instanceof GradientPaint)
610 GradientPaint gp = (GradientPaint) paint;
611 Point2D p1 = gp.getPoint1();
612 Point2D p2 = gp.getPoint2();
613 Color c1 = gp.getColor1();
614 Color c2 = gp.getColor2();
615 setGradient(p1.getX(), p1.getY(), p2.getX(), p2.getY(), c1.getRed(),
616 c1.getGreen(), c1.getBlue(), c1.getAlpha(), c2.getRed(),
617 c2.getGreen(), c2.getBlue(), c2.getAlpha(), gp.isCyclic());
620 throw new java.lang.UnsupportedOperationException();
623 public Stroke getStroke()
628 public void setStroke(Stroke st)
631 if (stroke instanceof BasicStroke)
633 BasicStroke bs = (BasicStroke) stroke;
634 cairoSetLine(bs.getLineWidth(), bs.getEndCap(),
635 bs.getLineJoin(), bs.getMiterLimit());
637 float[] dashes = bs.getDashArray();
640 double[] double_dashes = new double[dashes.length];
641 for (int i = 0; i < dashes.length; i++)
642 double_dashes[i] = dashes[i];
643 cairoSetDash(double_dashes, double_dashes.length,
644 (double) bs.getDashPhase());
647 cairoSetDash(new double[0], 0, 0.0);
651 public void setPaintMode()
653 setComposite(AlphaComposite.SrcOver);
656 public void setXORMode(Color c)
661 public void setColor(Color c)
672 * Set the current fg value as the cairo color.
678 cairoSetRGBAColor(fg.getRed() / 255.0, fg.getGreen() / 255.0,
679 fg.getBlue() / 255.0, fg.getAlpha() / 255.0);
682 public Color getColor()
687 public void clipRect(int x, int y, int width, int height)
689 clip(new Rectangle(x, y, width, height));
692 public Shape getClip()
697 return clip.getBounds2D(); //getClipInDevSpace();
700 public Rectangle getClipBounds()
705 return clip.getBounds();
708 protected Rectangle2D getClipInDevSpace()
710 Rectangle2D uclip = clip.getBounds2D();
711 if (transform == null)
715 Point2D pos = transform.transform(new Point2D.Double(uclip.getX(),
718 Point2D extent = transform.deltaTransform(new Point2D.Double(uclip
723 return new Rectangle2D.Double(pos.getX(), pos.getY(), extent.getX(),
728 public void setClip(int x, int y, int width, int height)
730 if( width < 0 || height < 0 )
733 setClip(new Rectangle2D.Double(x, y, width, height));
736 public void setClip(Shape s)
738 // The first time the clip is set, save it as the original clip
739 // to reset to on s == null. We can rely on this being non-null
740 // because the constructor in subclasses is expected to set the
741 // initial clip properly.
756 if (clip instanceof Rectangle2D)
758 Rectangle2D r = (Rectangle2D) clip;
759 cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
762 walkPath(clip.getPathIterator(null), false);
767 public void setBackground(Color c)
774 public Color getBackground()
780 * Return the current composite.
782 public Composite getComposite()
785 return AlphaComposite.SrcOver;
791 * Sets the current composite context.
793 public void setComposite(Composite comp)
797 if (comp instanceof AlphaComposite)
799 AlphaComposite a = (AlphaComposite) comp;
800 cairoSetOperator(a.getRule());
801 Color c = getColor();
802 setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(),
803 (int) (a.getAlpha() * ((float) c.getAlpha()))));
807 // FIXME: implement general Composite support
808 throw new java.lang.UnsupportedOperationException();
812 ///////////////////////// DRAWING PRIMITIVES ///////////////////////////////////
814 public void draw(Shape s)
816 if (stroke != null && ! (stroke instanceof BasicStroke))
818 fill(stroke.createStrokedShape(s));
824 if (s instanceof Rectangle2D)
826 Rectangle2D r = (Rectangle2D) s;
827 cairoRectangle(shifted(r.getX(), shiftDrawCalls),
828 shifted(r.getY(), shiftDrawCalls), r.getWidth(),
832 walkPath(s.getPathIterator(null), shiftDrawCalls);
836 public void fill(Shape s)
839 if (s instanceof Rectangle2D)
841 Rectangle2D r = (Rectangle2D) s;
842 cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
845 walkPath(s.getPathIterator(null), false);
851 * Note that the rest of the drawing methods go via fill() or draw() for the drawing,
852 * although subclasses may with to overload these methods where context-specific
853 * optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int)
856 public void clearRect(int x, int y, int width, int height)
859 cairoSetRGBAColor(bg.getRed() / 255.0, bg.getGreen() / 255.0,
860 bg.getBlue() / 255.0, 1.0);
861 fillRect(x, y, width, height);
865 public void draw3DRect(int x, int y, int width, int height, boolean raised)
868 setStroke(draw3DRectStroke);
869 super.draw3DRect(x, y, width, height, raised);
873 public void drawArc(int x, int y, int width, int height, int startAngle,
876 draw(new Arc2D.Double((double) x, (double) y, (double) width,
877 (double) height, (double) startAngle,
878 (double) arcAngle, Arc2D.OPEN));
881 public void drawLine(int x1, int y1, int x2, int y2)
883 draw(new Line2D.Double(x1, y1, x2, y2));
886 public void drawRect(int x, int y, int width, int height)
888 draw(new Rectangle(x, y, width, height));
891 public void fillArc(int x, int y, int width, int height, int startAngle,
894 fill(new Arc2D.Double((double) x, (double) y, (double) width,
895 (double) height, (double) startAngle,
896 (double) arcAngle, Arc2D.OPEN));
899 public void fillRect(int x, int y, int width, int height)
901 fill(new Rectangle(x, y, width, height));
904 public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
906 fill(new Polygon(xPoints, yPoints, nPoints));
909 public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
911 draw(new Polygon(xPoints, yPoints, nPoints));
914 public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
916 draw(new Polygon(xPoints, yPoints, nPoints));
919 public void drawOval(int x, int y, int width, int height)
921 drawArc(x, y, width, height, 0, 360);
924 public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
927 draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
930 public void fillOval(int x, int y, int width, int height)
932 fillArc(x, y, width, height, 0, 360);
935 public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
938 fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
942 * CopyArea - performs clipping to the native surface as a convenience
943 * (requires getRealBounds). Then calls copyAreaImpl.
945 public void copyArea(int ox, int oy, int owidth, int oheight,
948 Point2D pos = transform.transform(new Point2D.Double(ox, oy),
950 Point2D dim = transform.transform(new Point2D.Double(ox + owidth,
953 Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody),
955 int x = (int)pos.getX();
956 int y = (int)pos.getY();
957 int width = (int)(dim.getX() - pos.getX());
958 int height = (int)(dim.getY() - pos.getY());
959 int dx = (int)(p2.getX() - pos.getX());
960 int dy = (int)(p2.getY() - pos.getY());
962 Rectangle2D r = getRealBounds();
964 if( width < 0 || height < 0 )
966 // Return if outside the surface
967 if( x + dx > r.getWidth() || y + dy > r.getHeight() )
970 if( x + dx + width < r.getX() || y + dy + height < r.getY() )
973 // Clip edges if necessary
974 if( x + dx < r.getX() ) // left
976 width = x + dx + width;
977 x = (int)r.getX() - dx;
980 if( y + dy < r.getY() ) // top
982 height = y + dy + height;
983 y = (int)r.getY() - dy;
986 if( x + dx + width >= r.getWidth() ) // right
987 width = (int)r.getWidth() - dx - x;
989 if( y + dy + height >= r.getHeight() ) // bottom
990 height = (int)r.getHeight() - dy - y;
992 copyAreaImpl(x, y, width, height, dx, dy);
995 ///////////////////////// RENDERING HINTS ///////////////////////////////////
998 * FIXME- support better
1000 public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
1002 hints.put(hintKey, hintValue);
1004 if (hintKey.equals(RenderingHints.KEY_INTERPOLATION)
1005 || hintKey.equals(RenderingHints.KEY_ALPHA_INTERPOLATION))
1007 if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1008 cairoSurfaceSetFilter(0);
1010 else if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1011 cairoSurfaceSetFilter(1);
1013 else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1014 cairoSurfaceSetFilter(2);
1016 else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1017 cairoSurfaceSetFilter(3);
1019 else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1020 cairoSurfaceSetFilter(4);
1023 shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1024 || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1027 public Object getRenderingHint(RenderingHints.Key hintKey)
1029 return hints.get(hintKey);
1032 public void setRenderingHints(Map hints)
1034 this.hints = new RenderingHints(getDefaultHints());
1035 this.hints.add(new RenderingHints(hints));
1037 if (hints.containsKey(RenderingHints.KEY_INTERPOLATION))
1039 if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1040 cairoSurfaceSetFilter(0);
1042 else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1043 cairoSurfaceSetFilter(1);
1046 if (hints.containsKey(RenderingHints.KEY_ALPHA_INTERPOLATION))
1048 if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1049 cairoSurfaceSetFilter(2);
1051 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1052 cairoSurfaceSetFilter(3);
1054 else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1055 cairoSurfaceSetFilter(4);
1058 shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1059 || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1062 public void addRenderingHints(Map hints)
1064 this.hints.add(new RenderingHints(hints));
1067 public RenderingHints getRenderingHints()
1072 ///////////////////////// IMAGE. METHODS ///////////////////////////////////
1074 protected boolean drawImage(Image img, AffineTransform xform,
1075 Color bgcolor, ImageObserver obs)
1080 // In this case, xform is an AffineTransform that transforms bounding
1081 // box of the specified image from image space to user space. However
1082 // when we pass this transform to cairo, cairo will use this transform
1083 // to map "user coordinates" to "pixel" coordinates, which is the
1084 // other way around. Therefore to get the "user -> pixel" transform
1085 // that cairo wants from "image -> user" transform that we currently
1086 // have, we will need to invert the transformation matrix.
1087 AffineTransform invertedXform = new AffineTransform();
1091 invertedXform = xform.createInverse();
1093 catch (NoninvertibleTransformException e)
1095 throw new ImagingOpException("Unable to invert transform "
1096 + xform.toString());
1099 // Unrecognized image - convert to a BufferedImage and come back.
1100 if( !(img instanceof BufferedImage) )
1101 return this.drawImage(Toolkit.getDefaultToolkit().
1102 createImage(img.getSource()),
1103 xform, bgcolor, obs);
1105 BufferedImage b = (BufferedImage) img;
1107 double[] i2u = new double[6];
1108 int width = b.getWidth();
1109 int height = b.getHeight();
1111 // If this BufferedImage has a BufferedImageGraphics object,
1112 // use the cached CairoSurface that BIG is drawing onto
1113 if( BufferedImageGraphics.bufferedImages.get( b ) != null )
1114 db = (DataBuffer)BufferedImageGraphics.bufferedImages.get( b );
1116 db = b.getRaster().getDataBuffer();
1118 invertedXform.getMatrix(i2u);
1120 if(db instanceof CairoSurface)
1122 ((CairoSurface)db).drawSurface(this, i2u);
1126 if( bgcolor != null )
1128 // Fill a rectangle with the background color
1129 // to composite the image onto.
1130 Paint oldPaint = paint;
1131 AffineTransform oldTransform = transform;
1132 setPaint( bgcolor );
1133 setTransform( invertedXform );
1134 fillRect(0, 0, width, height);
1135 setTransform( oldTransform );
1136 setPaint( oldPaint );
1141 // Shortcut for easy color models.
1142 if( b.getColorModel().equals(rgb32) )
1144 pixels = ((DataBufferInt)db).getData();
1145 for(int i = 0; i < pixels.length; i++)
1146 pixels[i] |= 0xFF000000;
1148 else if( b.getColorModel().equals(argb32) )
1150 pixels = ((DataBufferInt)db).getData();
1154 pixels = b.getRGB(0, 0, width, height,
1158 drawPixels(pixels, width, height, width, i2u);
1160 // Cairo seems to lose the current color which must be restored.
1165 public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1167 drawRaster(image.getColorModel(), image.getData(), xform, null);
1170 public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1172 drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1175 public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1177 return drawImage(img, xform, null, obs);
1180 public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
1182 Image filtered = op.filter(image, null);
1183 drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null);
1186 public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1188 return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null,
1192 public boolean drawImage(Image img, int x, int y, Color bgcolor,
1193 ImageObserver observer)
1195 return drawImage(img, x, y, img.getWidth(observer),
1196 img.getHeight(observer), bgcolor, observer);
1199 public boolean drawImage(Image img, int x, int y, int width, int height,
1200 Color bgcolor, ImageObserver observer)
1202 double scaleX = width / (double) img.getWidth(observer);
1203 double scaleY = height / (double) img.getHeight(observer);
1204 if( scaleX == 0 || scaleY == 0 )
1207 return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y),
1211 public boolean drawImage(Image img, int x, int y, int width, int height,
1212 ImageObserver observer)
1214 return drawImage(img, x, y, width, height, null, observer);
1217 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1218 int sx1, int sy1, int sx2, int sy2, Color bgcolor,
1219 ImageObserver observer)
1224 int sourceWidth = sx2 - sx1;
1225 int sourceHeight = sy2 - sy1;
1227 int destWidth = dx2 - dx1;
1228 int destHeight = dy2 - dy1;
1230 if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 ||
1234 double scaleX = destWidth / (double) sourceWidth;
1235 double scaleY = destHeight / (double) sourceHeight;
1237 // FIXME: Avoid using an AT if possible here - it's at least twice as slow.
1239 Shape oldClip = getClip();
1242 { cx = dx1; cw = dx2 - dx1; }
1244 { cx = dx2; cw = dx1 - dx2; }
1246 { cy = dy1; ch = dy2 - dy1; }
1248 { cy = dy2; ch = dy1 - dy2; }
1250 setClip( cx, cy, cw, ch );
1252 AffineTransform tx = new AffineTransform();
1253 tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY );
1254 tx.scale( scaleX, scaleY );
1256 boolean retval = drawImage(img, tx, bgcolor, observer);
1261 public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1262 int sx1, int sy1, int sx2, int sy2,
1263 ImageObserver observer)
1265 return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
1268 ///////////////////////// TEXT METHODS ////////////////////////////////////
1270 public void drawString(String str, float x, float y)
1272 if (str == null || str.length() == 0)
1275 drawGlyphVector(getFont().createGlyphVector(null, str), x, y);
1278 public void drawString(String str, int x, int y)
1280 drawString (str, (float) x, (float) y);
1283 public void drawString(AttributedCharacterIterator ci, int x, int y)
1285 drawString (ci, (float) x, (float) y);
1288 public void drawGlyphVector(GlyphVector gv, float x, float y)
1290 int n = gv.getNumGlyphs ();
1291 int[] codes = gv.getGlyphCodes (0, n, null);
1292 float[] positions = gv.getGlyphPositions (0, n, null);
1294 setFont (gv.getFont ());
1295 cairoDrawGlyphVector( (GdkFontPeer)getFont().getPeer(), x, y, n, codes, positions);
1298 public void drawString(AttributedCharacterIterator ci, float x, float y)
1300 GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci);
1301 drawGlyphVector(gv, x, y);
1305 * Should perhaps be contexct dependent, but this is left for now as an
1306 * overloadable default implementation.
1308 public FontRenderContext getFontRenderContext()
1310 return new FontRenderContext(transform, true, true);
1313 // Until such time as pango is happy to talk directly to cairo, we
1314 // actually need to redirect some calls from the GtkFontPeer and
1315 // GtkFontMetrics into the drawing kit and ask cairo ourselves.
1317 public FontMetrics getFontMetrics()
1319 return getFontMetrics(getFont());
1322 public FontMetrics getFontMetrics(Font f)
1324 // the reason we go via the toolkit here is to try to get
1325 // a cached object. the toolkit keeps such a cache.
1326 return Toolkit.getDefaultToolkit().getFontMetrics(f);
1329 public void setFont(Font f)
1331 // Sun's JDK does not throw NPEs, instead it leaves the current setting
1332 // unchanged. So do we.
1336 if (f.getPeer() instanceof GdkFontPeer)
1340 ((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
1341 .getFont(f.getName(), f.getAttributes());
1344 public Font getFont()
1347 return new Font("SansSerif", Font.PLAIN, 12);
1351 /////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
1353 public boolean hit(Rectangle rect, Shape s, boolean onStroke)
1357 Shape stroked = stroke.createStrokedShape( s );
1358 return stroked.intersects( (double)rect.x, (double)rect.y,
1359 (double)rect.width, (double)rect.height );
1361 return s.intersects( (double)rect.x, (double)rect.y,
1362 (double)rect.width, (double)rect.height );
1365 public String toString()
1367 return (getClass().getName()
1368 + "[font=" + getFont().toString()
1369 + ",color=" + fg.toString()
1373 ///////////////////////// PRIVATE METHODS ///////////////////////////////////
1376 * All the drawImage() methods eventually get delegated here if the image
1377 * is not a Cairo surface.
1379 * @param bgcolor - if non-null draws the background color before
1380 * drawing the image.
1382 private boolean drawRaster(ColorModel cm, Raster r,
1383 AffineTransform imageToUser, Color bgcolor)
1388 SampleModel sm = r.getSampleModel();
1389 DataBuffer db = r.getDataBuffer();
1391 if (db == null || sm == null)
1395 cm = ColorModel.getRGBdefault();
1397 double[] i2u = new double[6];
1398 if (imageToUser != null)
1399 imageToUser.getMatrix(i2u);
1410 int[] pixels = findSimpleIntegerArray(cm, r);
1414 // FIXME: I don't think this code will work correctly with a non-RGB
1415 // MultiPixelPackedSampleModel. Although this entire method should
1416 // probably be rewritten to better utilize Cairo's different supported
1418 if (sm instanceof MultiPixelPackedSampleModel)
1420 pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
1421 for (int i = 0; i < pixels.length; i++)
1422 pixels[i] = cm.getRGB(pixels[i]);
1426 pixels = new int[r.getWidth() * r.getHeight()];
1427 for (int i = 0; i < pixels.length; i++)
1428 pixels[i] = cm.getRGB(db.getElem(i));
1432 // Change all transparent pixels in the image to the specified bgcolor,
1433 // or (if there's no alpha) fill in an alpha channel so that it paints
1437 if (bgcolor != null && cm.hasAlpha())
1438 for (int i = 0; i < pixels.length; i++)
1440 if (cm.getAlpha(pixels[i]) == 0)
1441 pixels[i] = bgcolor.getRGB();
1445 for (int i = 0; i < pixels.length; i++)
1446 pixels[i] |= 0xFF000000;
1448 drawPixels(pixels, r.getWidth(), r.getHeight(), r.getWidth(), i2u);
1450 // Cairo seems to lose the current color which must be restored.
1457 * Shifts coordinates by 0.5.
1459 private double shifted(double coord, boolean doShift)
1462 return Math.floor(coord) + 0.5;
1468 * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
1470 private void walkPath(PathIterator p, boolean doShift)
1474 double[] coords = new double[6];
1476 cairoSetFillRule(p.getWindingRule());
1477 for (; ! p.isDone(); p.next())
1479 int seg = p.currentSegment(coords);
1482 case PathIterator.SEG_MOVETO:
1483 x = shifted(coords[0], doShift);
1484 y = shifted(coords[1], doShift);
1487 case PathIterator.SEG_LINETO:
1488 x = shifted(coords[0], doShift);
1489 y = shifted(coords[1], doShift);
1492 case PathIterator.SEG_QUADTO:
1493 // splitting a quadratic bezier into a cubic:
1494 // see: http://pfaedit.sourceforge.net/bezier.html
1495 double x1 = x + (2.0 / 3.0) * (shifted(coords[0], doShift) - x);
1496 double y1 = y + (2.0 / 3.0) * (shifted(coords[1], doShift) - y);
1498 double x2 = x1 + (1.0 / 3.0) * (shifted(coords[2], doShift) - x);
1499 double y2 = y1 + (1.0 / 3.0) * (shifted(coords[3], doShift) - y);
1501 x = shifted(coords[2], doShift);
1502 y = shifted(coords[3], doShift);
1503 cairoCurveTo(x1, y1, x2, y2, x, y);
1505 case PathIterator.SEG_CUBICTO:
1506 x = shifted(coords[4], doShift);
1507 y = shifted(coords[5], doShift);
1508 cairoCurveTo(shifted(coords[0], doShift),
1509 shifted(coords[1], doShift),
1510 shifted(coords[2], doShift),
1511 shifted(coords[3], doShift), x, y);
1513 case PathIterator.SEG_CLOSE:
1521 * Used by setRenderingHints()
1523 private Map getDefaultHints()
1525 HashMap defaultHints = new HashMap();
1527 defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
1528 RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
1530 defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
1531 RenderingHints.VALUE_STROKE_DEFAULT);
1533 defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
1534 RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
1536 defaultHints.put(RenderingHints.KEY_ANTIALIASING,
1537 RenderingHints.VALUE_ANTIALIAS_OFF);
1539 defaultHints.put(RenderingHints.KEY_RENDERING,
1540 RenderingHints.VALUE_RENDER_DEFAULT);
1542 return defaultHints;
1546 * Used by drawRaster and GdkPixbufDecoder
1548 public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
1550 if (cm == null || raster == null)
1553 if (! cm.getColorSpace().isCS_sRGB())
1556 if (! (cm instanceof DirectColorModel))
1559 DirectColorModel dcm = (DirectColorModel) cm;
1561 if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
1562 || dcm.getBlueMask() != 0x000000FF)
1565 if (! (raster instanceof WritableRaster))
1568 if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
1571 if (! (raster.getDataBuffer() instanceof DataBufferInt))
1574 DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
1576 if (db.getNumBanks() != 1)
1579 // Finally, we have determined that this is a single bank, [A]RGB-int
1580 // buffer in sRGB space. It's worth checking all this, because it means
1581 // that cairo can paint directly into the data buffer, which is very
1582 // fast compared to all the normal copying and converting.
1584 return db.getData();