1 /* GdkGraphics2D.java --
2 Copyright (C) 2003, 2004, 2005 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., 59 Temple Place, Suite 330, 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
;
49 import java
.awt
.FontMetrics
;
50 import java
.awt
.GradientPaint
;
51 import java
.awt
.Graphics
;
52 import java
.awt
.Graphics2D
;
53 import java
.awt
.GraphicsConfiguration
;
54 import java
.awt
.Image
;
55 import java
.awt
.Paint
;
56 import java
.awt
.Rectangle
;
57 import java
.awt
.RenderingHints
;
58 import java
.awt
.Shape
;
59 import java
.awt
.Stroke
;
60 import java
.awt
.TexturePaint
;
61 import java
.awt
.Toolkit
;
62 import java
.awt
.font
.FontRenderContext
;
63 import java
.awt
.font
.GlyphVector
;
64 import java
.awt
.geom
.AffineTransform
;
65 import java
.awt
.geom
.Arc2D
;
66 import java
.awt
.geom
.GeneralPath
;
67 import java
.awt
.geom
.NoninvertibleTransformException
;
68 import java
.awt
.geom
.PathIterator
;
69 import java
.awt
.geom
.Point2D
;
70 import java
.awt
.geom
.Rectangle2D
;
71 import java
.awt
.image
.AffineTransformOp
;
72 import java
.awt
.image
.BufferedImage
;
73 import java
.awt
.image
.BufferedImageOp
;
74 import java
.awt
.image
.ColorModel
;
75 import java
.awt
.image
.CropImageFilter
;
76 import java
.awt
.image
.DataBuffer
;
77 import java
.awt
.image
.DataBufferInt
;
78 import java
.awt
.image
.DirectColorModel
;
79 import java
.awt
.image
.FilteredImageSource
;
80 import java
.awt
.image
.ImageObserver
;
81 import java
.awt
.image
.ImagingOpException
;
82 import java
.awt
.image
.MultiPixelPackedSampleModel
;
83 import java
.awt
.image
.Raster
;
84 import java
.awt
.image
.RenderedImage
;
85 import java
.awt
.image
.SampleModel
;
86 import java
.awt
.image
.WritableRaster
;
87 import java
.awt
.image
.renderable
.RenderContext
;
88 import java
.awt
.image
.renderable
.RenderableImage
;
89 import java
.text
.AttributedCharacterIterator
;
90 import java
.util
.HashMap
;
92 import java
.util
.Stack
;
94 public class GdkGraphics2D
extends Graphics2D
96 //////////////////////////////////////
97 ////// State Management Methods //////
98 //////////////////////////////////////
102 if (Configuration
.INIT_LOAD_LIBRARY
)
103 System
.loadLibrary("gtkpeer");
105 if (GtkToolkit
.useGraphics2D())
109 static native void initStaticState();
111 private final int native_state
= GtkGenericPeer
.getUniqueInteger();
114 private Stroke stroke
;
118 private AffineTransform transform
;
119 private GtkComponentPeer component
;
121 private RenderingHints hints
;
122 private BufferedImage bimage
;
123 private boolean pixelConversionRequired
;
124 private int[] pixelBuffer
;
125 private Composite comp
;
126 private Stack stateStack
;
128 private native void initState(GtkComponentPeer component
);
129 private native void initState(int width
, int height
);
130 private native void initState(int[] pixes
, int width
, int height
);
131 private native void copyState(GdkGraphics2D g
);
132 public native void dispose();
133 private native void cairoSurfaceSetFilter(int filter
);
134 native void connectSignals(GtkComponentPeer component
);
136 public void finalize()
141 public Graphics
create()
143 return new GdkGraphics2D(this);
146 public Graphics
create(int x
, int y
, int width
, int height
)
148 return new GdkGraphics2D(width
, height
);
151 GdkGraphics2D(GdkGraphics2D g
)
155 setRenderingHints(g
.hints
);
157 if (g
.fg
.getAlpha() != -1)
158 fg
= new Color(g
.fg
.getRed(), g
.fg
.getGreen(), g
.fg
.getBlue(),
161 fg
= new Color(g
.fg
.getRGB());
163 if (g
.bg
.getAlpha() != -1)
164 bg
= new Color(g
.bg
.getRed(), g
.bg
.getGreen(), g
.bg
.getBlue(),
167 bg
= new Color(g
.bg
.getRGB());
172 clip
= new Rectangle(g
.getClipBounds());
174 if (g
.transform
== null)
175 transform
= new AffineTransform();
177 transform
= new AffineTransform(g
.transform
);
180 component
= g
.component
;
187 setTransform(transform
);
189 stateStack
= new Stack();
192 GdkGraphics2D(int width
, int height
)
194 initState(width
, height
);
196 setColor(Color
.black
);
197 setBackground(Color
.black
);
198 setPaint(getColor());
199 setFont(new Font("SansSerif", Font
.PLAIN
, 12));
200 setTransform(new AffineTransform());
201 setStroke(new BasicStroke());
202 setRenderingHints(getDefaultHints());
204 stateStack
= new Stack();
207 GdkGraphics2D(GtkComponentPeer component
)
209 this.component
= component
;
211 if (component
.isRealized())
212 initComponentGraphics2D();
214 connectSignals(component
);
217 void initComponentGraphics2D()
219 initState(component
);
221 setColor(component
.awtComponent
.getForeground());
222 setBackground(component
.awtComponent
.getBackground());
223 setPaint(getColor());
224 setTransform(new AffineTransform());
225 setStroke(new BasicStroke());
226 setRenderingHints(getDefaultHints());
227 setFont(new Font("SansSerif", Font
.PLAIN
, 12));
229 stateStack
= new Stack();
232 GdkGraphics2D(BufferedImage bimage
)
234 this.bimage
= bimage
;
235 this.pixelBuffer
= findSimpleIntegerArray(bimage
.getColorModel(),
237 if (this.pixelBuffer
== null)
239 this.pixelBuffer
= new int[bimage
.getRaster().getWidth() * bimage
.getRaster()
241 this.pixelConversionRequired
= true;
245 this.pixelConversionRequired
= false;
248 initState(this.pixelBuffer
, bimage
.getWidth(), bimage
.getHeight());
250 setColor(Color
.black
);
251 setBackground(Color
.black
);
252 setPaint(getColor());
253 setFont(new Font("SansSerif", Font
.PLAIN
, 12));
254 setTransform(new AffineTransform());
255 setStroke(new BasicStroke());
256 setRenderingHints(getDefaultHints());
258 stateStack
= new Stack();
260 // draw current buffered image to the pixmap associated
261 // with it, if the image is not equal to our paint buffer.
262 if (pixelConversionRequired
)
263 drawImage(bimage
, new AffineTransform(1, 0, 0, 1, 0, 0), bg
, null);
266 ////////////////////////////////////
267 ////// Native Drawing Methods //////
268 ////////////////////////////////////
270 // GDK drawing methods
271 private native void gdkDrawDrawable(GdkGraphics2D other
, int x
, int y
);
273 // drawing utility methods
274 private native void drawPixels(int[] pixels
, int w
, int h
, int stride
,
276 private native void setTexturePixels(int[] pixels
, int w
, int h
, int stride
);
277 private native void setGradient(double x1
, double y1
, double x2
, double y2
,
278 int r1
, int g1
, int b1
, int a1
, int r2
,
279 int g2
, int b2
, int a2
, boolean cyclic
);
281 // simple passthroughs to cairo
282 private native void cairoSave();
283 private native void cairoRestore();
284 private native void cairoSetMatrix(double[] m
);
285 private native void cairoSetOperator(int cairoOperator
);
286 private native void cairoSetRGBColor(double red
, double green
, double blue
);
287 private native void cairoSetAlpha(double alpha
);
288 private native void cairoSetFillRule(int cairoFillRule
);
289 private native void cairoSetLineWidth(double width
);
290 private native void cairoSetLineCap(int cairoLineCap
);
291 private native void cairoSetLineJoin(int cairoLineJoin
);
292 private native void cairoSetDash(double[] dashes
, int ndash
, double offset
);
294 private native void cairoSetMiterLimit(double limit
);
295 private native void cairoNewPath();
296 private native void cairoMoveTo(double x
, double y
);
297 private native void cairoLineTo(double x
, double y
);
298 private native void cairoCurveTo(double x1
, double y1
, double x2
, double y2
,
299 double x3
, double y3
);
300 private native void cairoRelMoveTo(double dx
, double dy
);
301 private native void cairoRelLineTo(double dx
, double dy
);
302 private native void cairoRelCurveTo(double dx1
, double dy1
, double dx2
,
303 double dy2
, double dx3
, double dy3
);
304 private native void cairoRectangle(double x
, double y
, double width
,
306 private native void cairoClosePath();
307 private native void cairoStroke();
308 private native void cairoFill();
309 private native void cairoClip();
311 /////////////////////////////////////////////
312 ////// General Drawing Support Methods //////
313 /////////////////////////////////////////////
315 private class DrawState
318 private Stroke stroke
;
322 private AffineTransform transform
;
324 private Composite comp
;
326 DrawState(GdkGraphics2D g
)
328 this.paint
= g
.paint
;
329 this.stroke
= g
.stroke
;
333 if (g
.transform
!= null)
334 this.transform
= (AffineTransform
) g
.transform
.clone();
339 public void restore(GdkGraphics2D g
)
341 g
.paint
= this.paint
;
342 g
.stroke
= this.stroke
;
346 g
.transform
= this.transform
;
352 private void stateSave()
354 stateStack
.push(new DrawState(this));
358 private void stateRestore()
360 ((DrawState
) (stateStack
.pop())).restore(this);
364 // Some operations (drawing rather than filling) require that their
365 // coords be shifted to land on 0.5-pixel boundaries, in order to land on
366 // "middle of pixel" coordinates and light up complete pixels.
367 private boolean shiftDrawCalls
= false;
369 private double shifted(double coord
, boolean doShift
)
372 return Math
.floor(coord
) + 0.5;
377 private void walkPath(PathIterator p
, boolean doShift
)
381 double[] coords
= new double[6];
383 cairoSetFillRule(p
.getWindingRule());
384 for (; ! p
.isDone(); p
.next())
386 int seg
= p
.currentSegment(coords
);
389 case PathIterator
.SEG_MOVETO
:
390 x
= shifted(coords
[0], doShift
);
391 y
= shifted(coords
[1], doShift
);
394 case PathIterator
.SEG_LINETO
:
395 x
= shifted(coords
[0], doShift
);
396 y
= shifted(coords
[1], doShift
);
399 case PathIterator
.SEG_QUADTO
:
400 // splitting a quadratic bezier into a cubic:
401 // see: http://pfaedit.sourceforge.net/bezier.html
402 double x1
= x
+ (2.0 / 3.0) * (shifted(coords
[0], doShift
) - x
);
403 double y1
= y
+ (2.0 / 3.0) * (shifted(coords
[1], doShift
) - y
);
405 double x2
= x1
+ (1.0 / 3.0) * (shifted(coords
[2], doShift
) - x
);
406 double y2
= y1
+ (1.0 / 3.0) * (shifted(coords
[3], doShift
) - y
);
408 x
= shifted(coords
[2], doShift
);
409 y
= shifted(coords
[3], doShift
);
410 cairoCurveTo(x1
, y1
, x2
, y2
, x
, y
);
412 case PathIterator
.SEG_CUBICTO
:
413 x
= shifted(coords
[4], doShift
);
414 y
= shifted(coords
[5], doShift
);
415 cairoCurveTo(shifted(coords
[0], doShift
),
416 shifted(coords
[1], doShift
),
417 shifted(coords
[2], doShift
),
418 shifted(coords
[3], doShift
), x
, y
);
420 case PathIterator
.SEG_CLOSE
:
427 private Map
getDefaultHints()
429 HashMap defaultHints
= new HashMap();
431 defaultHints
.put(RenderingHints
.KEY_TEXT_ANTIALIASING
,
432 RenderingHints
.VALUE_TEXT_ANTIALIAS_DEFAULT
);
434 defaultHints
.put(RenderingHints
.KEY_STROKE_CONTROL
,
435 RenderingHints
.VALUE_STROKE_DEFAULT
);
437 defaultHints
.put(RenderingHints
.KEY_FRACTIONALMETRICS
,
438 RenderingHints
.VALUE_FRACTIONALMETRICS_OFF
);
440 defaultHints
.put(RenderingHints
.KEY_ANTIALIASING
,
441 RenderingHints
.VALUE_ANTIALIAS_OFF
);
443 defaultHints
.put(RenderingHints
.KEY_RENDERING
,
444 RenderingHints
.VALUE_RENDER_DEFAULT
);
449 public static int[] findSimpleIntegerArray (ColorModel cm
, Raster raster
)
451 if (cm
== null || raster
== null)
454 if (! cm
.getColorSpace().isCS_sRGB())
457 if (! (cm
instanceof DirectColorModel
))
460 DirectColorModel dcm
= (DirectColorModel
) cm
;
462 if (dcm
.getRedMask() != 0x00FF0000 || dcm
.getGreenMask() != 0x0000FF00
463 || dcm
.getBlueMask() != 0x000000FF)
466 if (! (raster
instanceof WritableRaster
))
469 if (raster
.getSampleModel().getDataType() != DataBuffer
.TYPE_INT
)
472 if (! (raster
.getDataBuffer() instanceof DataBufferInt
))
475 DataBufferInt db
= (DataBufferInt
) raster
.getDataBuffer();
477 if (db
.getNumBanks() != 1)
480 // Finally, we have determined that this is a single bank, [A]RGB-int
481 // buffer in sRGB space. It's worth checking all this, because it means
482 // that cairo can paint directly into the data buffer, which is very
483 // fast compared to all the normal copying and converting.
488 private void updateBufferedImage()
490 if (bimage
!= null && pixelConversionRequired
)
492 int height
= bimage
.getHeight();
493 int width
= bimage
.getWidth();
494 for (int y
= 0; y
< height
; ++y
)
495 for (int x
= 0; x
< width
; ++x
)
496 bimage
.setRGB(x
, y
, pixelBuffer
[y
*width
+height
]);
500 private boolean drawImage(Image img
, AffineTransform xform
,
501 Color bgcolor
, ImageObserver obs
)
506 if (img
instanceof GtkOffScreenImage
507 && img
.getGraphics() instanceof GdkGraphics2D
508 && (xform
== null || xform
.getType() == AffineTransform
.TYPE_IDENTITY
509 || xform
.getType() == AffineTransform
.TYPE_TRANSLATION
))
511 // we are being asked to flush a double buffer from Gdk
512 GdkGraphics2D g2
= (GdkGraphics2D
) img
.getGraphics();
513 gdkDrawDrawable(g2
, (int) xform
.getTranslateX(),
514 (int) xform
.getTranslateY());
516 updateBufferedImage();
522 // In this case, xform is an AffineTransform that transforms bounding
523 // box of the specified image from image space to user space. However
524 // when we pass this transform to cairo, cairo will use this transform
525 // to map "user coordinates" to "pixel" coordinates, which is the
526 // other way around. Therefore to get the "user -> pixel" transform
527 // that cairo wants from "image -> user" transform that we currently
528 // have, we will need to invert the transformation matrix.
529 AffineTransform invertedXform
= new AffineTransform();
533 invertedXform
= xform
.createInverse();
534 if (img
instanceof BufferedImage
)
536 // draw an image which has actually been loaded
538 BufferedImage b
= (BufferedImage
) img
;
539 return drawRaster(b
.getColorModel(), b
.getData(),
540 invertedXform
, bgcolor
);
543 return this.drawImage(GdkPixbufDecoder
.createBufferedImage(img
545 xform
, bgcolor
, obs
);
547 catch (NoninvertibleTransformException e
)
549 throw new ImagingOpException("Unable to invert transform "
555 //////////////////////////////////////////////////
556 ////// Implementation of Graphics2D Methods //////
557 //////////////////////////////////////////////////
559 public void draw(Shape s
)
561 if (stroke
!= null && ! (stroke
instanceof BasicStroke
))
563 fill(stroke
.createStrokedShape(s
));
569 if (s
instanceof Rectangle2D
)
571 Rectangle2D r
= (Rectangle2D
) s
;
572 cairoRectangle(shifted(r
.getX(), shiftDrawCalls
),
573 shifted(r
.getY(), shiftDrawCalls
), r
.getWidth(),
577 walkPath(s
.getPathIterator(null), shiftDrawCalls
);
580 updateBufferedImage();
583 public void fill(Shape s
)
586 if (s
instanceof Rectangle2D
)
588 Rectangle2D r
= (Rectangle2D
) s
;
589 cairoRectangle(r
.getX(), r
.getY(), r
.getWidth(), r
.getHeight());
592 walkPath(s
.getPathIterator(null), false);
596 updateBufferedImage();
599 public void clip(Shape s
)
602 if (clip
== null || s
== null)
604 else if (s
instanceof Rectangle2D
&& clip
instanceof Rectangle2D
)
606 Rectangle2D r
= (Rectangle2D
) s
;
607 Rectangle2D curr
= (Rectangle2D
) clip
;
608 clip
= curr
.createIntersection(r
);
611 throw new UnsupportedOperationException();
617 if (clip
instanceof Rectangle2D
)
619 Rectangle2D r
= (Rectangle2D
) clip
;
620 cairoRectangle(r
.getX(), r
.getY(), r
.getWidth(), r
.getHeight());
623 walkPath(clip
.getPathIterator(null), false);
625 // cairoClosePath ();
630 public Paint
getPaint()
635 public AffineTransform
getTransform()
637 return (AffineTransform
) transform
.clone();
640 public void setPaint(Paint p
)
646 if (paint
instanceof Color
)
648 setColor((Color
) paint
);
650 else if (paint
instanceof TexturePaint
)
652 TexturePaint tp
= (TexturePaint
) paint
;
653 BufferedImage img
= tp
.getImage();
655 // map the image to the anchor rectangle
656 int width
= (int) tp
.getAnchorRect().getWidth();
657 int height
= (int) tp
.getAnchorRect().getHeight();
659 double scaleX
= width
/ (double) img
.getWidth();
660 double scaleY
= width
/ (double) img
.getHeight();
662 AffineTransform at
= new AffineTransform(scaleX
, 0, 0, scaleY
, 0, 0);
663 AffineTransformOp op
= new AffineTransformOp(at
, getRenderingHints());
664 BufferedImage texture
= op
.filter(img
, null);
665 int[] pixels
= texture
.getRGB(0, 0, width
, height
, null, 0, width
);
666 setTexturePixels(pixels
, width
, height
, width
);
668 else if (paint
instanceof GradientPaint
)
670 GradientPaint gp
= (GradientPaint
) paint
;
671 Point2D p1
= gp
.getPoint1();
672 Point2D p2
= gp
.getPoint2();
673 Color c1
= gp
.getColor1();
674 Color c2
= gp
.getColor2();
675 setGradient(p1
.getX(), p1
.getY(), p2
.getX(), p2
.getY(), c1
.getRed(),
676 c1
.getGreen(), c1
.getBlue(), c1
.getAlpha(), c2
.getRed(),
677 c2
.getGreen(), c2
.getBlue(), c2
.getAlpha(), gp
.isCyclic());
680 throw new java
.lang
.UnsupportedOperationException();
683 public void setTransform(AffineTransform tx
)
686 if (transform
!= null)
688 double[] m
= new double[6];
689 transform
.getMatrix(m
);
694 public void transform(AffineTransform tx
)
696 if (transform
== null)
697 transform
= new AffineTransform(tx
);
699 transform
.concatenate(tx
);
700 setTransform(transform
);
703 // FIXME: this should actuall try to transform the shape
704 // rather than degrade to bounds.
705 Rectangle2D r
= clip
.getBounds2D();
706 double[] coords
= new double[]
708 r
.getX(), r
.getY(), r
.getX() + r
.getWidth(),
709 r
.getY() + r
.getHeight()
713 tx
.createInverse().transform(coords
, 0, coords
, 0, 2);
714 r
.setRect(coords
[0], coords
[1], coords
[2] - coords
[0],
715 coords
[3] - coords
[1]);
718 catch (java
.awt
.geom
.NoninvertibleTransformException e
)
724 public void rotate(double theta
)
726 transform(AffineTransform
.getRotateInstance(theta
));
729 public void rotate(double theta
, double x
, double y
)
731 transform(AffineTransform
.getRotateInstance(theta
, x
, y
));
734 public void scale(double sx
, double sy
)
736 transform(AffineTransform
.getScaleInstance(sx
, sy
));
739 public void translate(double tx
, double ty
)
741 transform(AffineTransform
.getTranslateInstance(tx
, ty
));
744 public void translate(int x
, int y
)
746 translate((double) x
, (double) y
);
749 public void shear(double shearX
, double shearY
)
751 transform(AffineTransform
.getShearInstance(shearX
, shearY
));
754 public Stroke
getStroke()
759 public void setStroke(Stroke st
)
762 if (stroke
instanceof BasicStroke
)
764 BasicStroke bs
= (BasicStroke
) stroke
;
765 cairoSetLineCap(bs
.getEndCap());
766 cairoSetLineWidth(bs
.getLineWidth());
767 cairoSetLineJoin(bs
.getLineJoin());
768 cairoSetMiterLimit(bs
.getMiterLimit());
769 float[] dashes
= bs
.getDashArray();
772 double[] double_dashes
= new double[dashes
.length
];
773 for (int i
= 0; i
< dashes
.length
; i
++)
774 double_dashes
[i
] = dashes
[i
];
775 cairoSetDash(double_dashes
, double_dashes
.length
,
776 (double) bs
.getDashPhase());
781 ////////////////////////////////////////////////
782 ////// Implementation of Graphics Methods //////
783 ////////////////////////////////////////////////
785 public void setPaintMode()
787 setComposite(java
.awt
.AlphaComposite
.SrcOver
);
790 public void setXORMode(Color c
)
792 setComposite(new gnu
.java
.awt
.BitwiseXORComposite(c
));
795 public void setColor(Color c
)
802 cairoSetRGBColor(fg
.getRed() / 255.0, fg
.getGreen() / 255.0,
803 fg
.getBlue() / 255.0);
804 cairoSetAlpha((fg
.getAlpha() & 255) / 255.0);
807 public Color
getColor()
812 public void clipRect(int x
, int y
, int width
, int height
)
814 clip(new Rectangle(x
, y
, width
, height
));
817 public Shape
getClip()
819 return clip
.getBounds2D(); //getClipInDevSpace();
822 public Rectangle
getClipBounds()
827 return clip
.getBounds();
830 protected Rectangle2D
getClipInDevSpace()
832 Rectangle2D uclip
= clip
.getBounds2D();
833 if (transform
== null)
837 Point2D pos
= transform
.transform(new Point2D
.Double(uclip
.getX(),
840 Point2D extent
= transform
.deltaTransform(new Point2D
.Double(uclip
845 return new Rectangle2D
.Double(pos
.getX(), pos
.getY(), extent
.getX(),
850 public void setClip(int x
, int y
, int width
, int height
)
852 setClip(new Rectangle2D
.Double((double) x
, (double) y
, (double) width
,
856 public void setClip(Shape s
)
862 if (s
instanceof Rectangle2D
)
864 Rectangle2D r
= (Rectangle2D
) s
;
865 cairoRectangle(r
.getX(), r
.getY(), r
.getWidth(), r
.getHeight());
868 walkPath(s
.getPathIterator(null), false);
870 // cairoClosePath ();
875 private static BasicStroke draw3DRectStroke
= new BasicStroke();
877 public void draw3DRect(int x
, int y
, int width
, int height
, boolean raised
)
880 setStroke(draw3DRectStroke
);
881 super.draw3DRect(x
, y
, width
, height
, raised
);
883 updateBufferedImage();
886 public void fill3DRect(int x
, int y
, int width
, int height
, boolean raised
)
889 setStroke(draw3DRectStroke
);
890 super.fill3DRect(x
, y
, width
, height
, raised
);
892 updateBufferedImage();
895 public void drawRect(int x
, int y
, int width
, int height
)
897 draw(new Rectangle(x
, y
, width
, height
));
900 public void fillRect(int x
, int y
, int width
, int height
)
903 cairoRectangle(x
, y
, width
, height
);
907 public void clearRect(int x
, int y
, int width
, int height
)
909 cairoSetRGBColor(bg
.getRed() / 255.0, bg
.getGreen() / 255.0,
910 bg
.getBlue() / 255.0);
913 cairoRectangle(x
, y
, width
, height
);
917 updateBufferedImage();
920 public void setBackground(Color c
)
925 public Color
getBackground()
930 private void doPolygon(int[] xPoints
, int[] yPoints
, int nPoints
,
931 boolean close
, boolean fill
)
935 GeneralPath gp
= new GeneralPath(PathIterator
.WIND_EVEN_ODD
);
936 gp
.moveTo((float) xPoints
[0], (float) yPoints
[0]);
937 for (int i
= 1; i
< nPoints
; i
++)
938 gp
.lineTo((float) xPoints
[i
], (float) yPoints
[i
]);
944 if (fill
== false && stroke
!= null && ! (stroke
instanceof BasicStroke
))
946 sh
= stroke
.createStrokedShape(gp
);
956 public void drawLine(int x1
, int y1
, int x2
, int y2
)
958 int[] xp
= new int[2];
959 int[] yp
= new int[2];
966 doPolygon(xp
, yp
, 2, false, false);
969 public void fillPolygon(int[] xPoints
, int[] yPoints
, int nPoints
)
971 doPolygon(xPoints
, yPoints
, nPoints
, true, true);
974 public void drawPolygon(int[] xPoints
, int[] yPoints
, int nPoints
)
976 doPolygon(xPoints
, yPoints
, nPoints
, true, false);
979 public void drawPolyline(int[] xPoints
, int[] yPoints
, int nPoints
)
981 doPolygon(xPoints
, yPoints
, nPoints
, false, false);
984 private boolean drawRaster(ColorModel cm
, Raster r
,
985 AffineTransform imageToUser
, Color bgcolor
)
990 SampleModel sm
= r
.getSampleModel();
991 DataBuffer db
= r
.getDataBuffer();
993 if (db
== null || sm
== null)
997 cm
= ColorModel
.getRGBdefault();
999 double[] i2u
= new double[6];
1000 if (imageToUser
!= null)
1001 imageToUser
.getMatrix(i2u
);
1012 int[] pixels
= findSimpleIntegerArray(cm
, r
);
1016 // FIXME: I don't think this code will work correctly with a non-RGB
1017 // MultiPixelPackedSampleModel. Although this entire method should
1018 // probably be rewritten to better utilize Cairo's different supported
1020 if (sm
instanceof MultiPixelPackedSampleModel
)
1022 pixels
= r
.getPixels(0, 0, r
.getWidth(), r
.getHeight(), pixels
);
1023 for (int i
= 0; i
< pixels
.length
; i
++)
1024 pixels
[i
] = cm
.getRGB(pixels
[i
]);
1028 pixels
= new int[r
.getWidth() * r
.getHeight()];
1029 for (int i
= 0; i
< pixels
.length
; i
++)
1030 pixels
[i
] = cm
.getRGB(db
.getElem(i
));
1034 // Change all transparent pixels in the image to the specified bgcolor,
1035 // or (if there's no alpha) fill in an alpha channel so that it paints
1039 if (bgcolor
!= null && cm
.hasAlpha())
1040 for (int i
= 0; i
< pixels
.length
; i
++)
1042 if (cm
.getAlpha(pixels
[i
]) == 0)
1043 pixels
[i
] = bgcolor
.getRGB();
1047 for (int i
= 0; i
< pixels
.length
; i
++)
1048 pixels
[i
] |= 0xFF000000;
1050 drawPixels(pixels
, r
.getWidth(), r
.getHeight(), r
.getWidth(), i2u
);
1052 updateBufferedImage();
1057 public void drawRenderedImage(RenderedImage image
, AffineTransform xform
)
1059 drawRaster(image
.getColorModel(), image
.getData(), xform
, bg
);
1062 public void drawRenderableImage(RenderableImage image
, AffineTransform xform
)
1064 drawRenderedImage(image
.createRendering(new RenderContext(xform
)), xform
);
1067 public boolean drawImage(Image img
, AffineTransform xform
, ImageObserver obs
)
1069 return drawImage(img
, xform
, bg
, obs
);
1072 public void drawImage(BufferedImage image
, BufferedImageOp op
, int x
, int y
)
1074 Image filtered
= op
.filter(image
, null);
1075 drawImage(filtered
, new AffineTransform(1f
, 0f
, 0f
, 1f
, x
, y
), bg
, null);
1078 public boolean drawImage(Image img
, int x
, int y
, ImageObserver observer
)
1080 return drawImage(img
, new AffineTransform(1f
, 0f
, 0f
, 1f
, x
, y
), bg
,
1084 ///////////////////////////////////////////////
1085 ////// Unimplemented Stubs and Overloads //////
1086 ///////////////////////////////////////////////
1088 public boolean hit(Rectangle rect
, Shape text
, boolean onStroke
)
1090 throw new java
.lang
.UnsupportedOperationException();
1093 public GraphicsConfiguration
getDeviceConfiguration()
1095 throw new java
.lang
.UnsupportedOperationException();
1098 public void setComposite(Composite comp
)
1102 if (comp
instanceof AlphaComposite
)
1104 AlphaComposite a
= (AlphaComposite
) comp
;
1105 cairoSetOperator(a
.getRule());
1106 Color c
= getColor();
1107 setColor(new Color(c
.getRed(), c
.getGreen(), c
.getBlue(),
1108 (int) (a
.getAlpha() * ((float) c
.getAlpha()))));
1111 throw new java
.lang
.UnsupportedOperationException();
1114 public void setRenderingHint(RenderingHints
.Key hintKey
, Object hintValue
)
1116 hints
.put(hintKey
, hintValue
);
1118 if (hintKey
.equals(RenderingHints
.KEY_INTERPOLATION
)
1119 || hintKey
.equals(RenderingHints
.KEY_ALPHA_INTERPOLATION
))
1121 if (hintValue
.equals(RenderingHints
.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
))
1122 cairoSurfaceSetFilter(0);
1124 else if (hintValue
.equals(RenderingHints
.VALUE_INTERPOLATION_BILINEAR
))
1125 cairoSurfaceSetFilter(1);
1127 else if (hintValue
.equals(RenderingHints
.VALUE_ALPHA_INTERPOLATION_SPEED
))
1128 cairoSurfaceSetFilter(2);
1130 else if (hintValue
.equals(RenderingHints
.VALUE_ALPHA_INTERPOLATION_QUALITY
))
1131 cairoSurfaceSetFilter(3);
1133 else if (hintValue
.equals(RenderingHints
.VALUE_ALPHA_INTERPOLATION_DEFAULT
))
1134 cairoSurfaceSetFilter(4);
1137 shiftDrawCalls
= hints
.containsValue(RenderingHints
.VALUE_STROKE_NORMALIZE
)
1138 || hints
.containsValue(RenderingHints
.VALUE_STROKE_DEFAULT
);
1141 public Object
getRenderingHint(RenderingHints
.Key hintKey
)
1143 return hints
.get(hintKey
);
1146 public void setRenderingHints(Map hints
)
1148 this.hints
= new RenderingHints(getDefaultHints());
1149 this.hints
.add(new RenderingHints(hints
));
1151 if (hints
.containsKey(RenderingHints
.KEY_INTERPOLATION
))
1153 if (hints
.containsValue(RenderingHints
.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
))
1154 cairoSurfaceSetFilter(0);
1156 else if (hints
.containsValue(RenderingHints
.VALUE_INTERPOLATION_BILINEAR
))
1157 cairoSurfaceSetFilter(1);
1160 if (hints
.containsKey(RenderingHints
.KEY_ALPHA_INTERPOLATION
))
1162 if (hints
.containsValue(RenderingHints
.VALUE_ALPHA_INTERPOLATION_SPEED
))
1163 cairoSurfaceSetFilter(2);
1165 else if (hints
.containsValue(RenderingHints
.VALUE_ALPHA_INTERPOLATION_QUALITY
))
1166 cairoSurfaceSetFilter(3);
1168 else if (hints
.containsValue(RenderingHints
.VALUE_ALPHA_INTERPOLATION_DEFAULT
))
1169 cairoSurfaceSetFilter(4);
1172 shiftDrawCalls
= hints
.containsValue(RenderingHints
.VALUE_STROKE_NORMALIZE
)
1173 || hints
.containsValue(RenderingHints
.VALUE_STROKE_DEFAULT
);
1176 public void addRenderingHints(Map hints
)
1178 this.hints
.add(new RenderingHints(hints
));
1181 public RenderingHints
getRenderingHints()
1186 public Composite
getComposite()
1189 return AlphaComposite
.SrcOver
;
1194 public FontRenderContext
getFontRenderContext()
1196 return new FontRenderContext(transform
, true, true);
1199 public void copyArea(int x
, int y
, int width
, int height
, int dx
, int dy
)
1201 throw new java
.lang
.UnsupportedOperationException();
1204 public void drawArc(int x
, int y
, int width
, int height
, int startAngle
,
1207 draw(new Arc2D
.Double((double) x
, (double) y
, (double) width
,
1208 (double) height
, (double) startAngle
,
1209 (double) arcAngle
, Arc2D
.OPEN
));
1212 public boolean drawImage(Image img
, int x
, int y
, Color bgcolor
,
1213 ImageObserver observer
)
1215 return drawImage(img
, x
, y
, img
.getWidth(observer
),
1216 img
.getHeight(observer
), bgcolor
, observer
);
1219 public boolean drawImage(Image img
, int x
, int y
, int width
, int height
,
1220 Color bgcolor
, ImageObserver observer
)
1222 double scaleX
= width
/ (double) img
.getWidth(observer
);
1223 double scaleY
= height
/ (double) img
.getHeight(observer
);
1225 return drawImage(img
, new AffineTransform(scaleX
, 0f
, 0f
, scaleY
, x
, y
),
1229 public boolean drawImage(Image img
, int x
, int y
, int width
, int height
,
1230 ImageObserver observer
)
1232 return drawImage(img
, x
, y
, width
, height
, bg
, observer
);
1235 public boolean drawImage(Image img
, int dx1
, int dy1
, int dx2
, int dy2
,
1236 int sx1
, int sy1
, int sx2
, int sy2
, Color bgcolor
,
1237 ImageObserver observer
)
1244 int sourceWidth
= sx2
- sx1
;
1245 int sourceHeight
= sy2
- sy1
;
1247 int destWidth
= dx2
- dx1
;
1248 int destHeight
= dy2
- dy1
;
1250 double scaleX
= destWidth
/ (double) sourceWidth
;
1251 double scaleY
= destHeight
/ (double) sourceHeight
;
1253 // Get the subimage of the source enclosed in the
1254 // rectangle specified by sx1, sy1, sx2, sy2
1256 if (img
instanceof BufferedImage
)
1258 BufferedImage b
= (BufferedImage
) img
;
1259 subImage
= b
.getSubimage(sx1
, sy1
, sx2
, sy2
);
1263 // FIXME: This code currently doesn't work. Null Pointer
1264 // exception is thrown in this case. This happens
1265 // because img.getSource() always returns null, since source gets
1266 // never initialized when it is created with the help of
1267 // createImage(int width, int height).
1268 CropImageFilter filter
= new CropImageFilter(sx1
, sx2
, sx2
, sy2
);
1269 FilteredImageSource src
= new FilteredImageSource(img
.getSource(),
1272 subImage
= Toolkit
.getDefaultToolkit().createImage(src
);
1275 return drawImage(subImage
,
1276 new AffineTransform(scaleX
, 0, 0, scaleY
, dx1
, dy1
),
1280 public boolean drawImage(Image img
, int dx1
, int dy1
, int dx2
, int dy2
,
1281 int sx1
, int sy1
, int sx2
, int sy2
,
1282 ImageObserver observer
)
1284 return drawImage(img
, dx1
, dy1
, dx2
, dy2
, sx1
, sy1
, sx2
, sy2
, bg
, observer
);
1287 public void drawOval(int x
, int y
, int width
, int height
)
1289 drawArc(x
, y
, width
, height
, 0, 360);
1292 public void drawRoundRect(int x
, int y
, int width
, int height
, int arcWidth
,
1295 if (arcWidth
> width
)
1297 if (arcHeight
> height
)
1300 int xx
= x
+ width
- arcWidth
;
1301 int yy
= y
+ height
- arcHeight
;
1303 drawArc(x
, y
, arcWidth
, arcHeight
, 90, 90);
1304 drawArc(xx
, y
, arcWidth
, arcHeight
, 0, 90);
1305 drawArc(xx
, yy
, arcWidth
, arcHeight
, 270, 90);
1306 drawArc(x
, yy
, arcWidth
, arcHeight
, 180, 90);
1308 int y1
= y
+ arcHeight
/ 2;
1309 int y2
= y
+ height
- arcHeight
/ 2;
1310 drawLine(x
, y1
, x
, y2
);
1311 drawLine(x
+ width
, y1
, x
+ width
, y2
);
1313 int x1
= x
+ arcWidth
/ 2;
1314 int x2
= x
+ width
- arcWidth
/ 2;
1315 drawLine(x1
, y
, x2
, y
);
1316 drawLine(x1
, y
+ height
, x2
, y
+ height
);
1319 // these are the most accelerated painting paths
1320 native void cairoDrawGlyphVector(GdkFontPeer font
,
1321 float x
, float y
, int n
,
1322 int[] codes
, float[] positions
);
1324 native void cairoDrawGdkTextLayout(GdkTextLayout gl
,
1327 GdkFontPeer
getFontPeer()
1329 return (GdkFontPeer
) getFont().getPeer();
1332 public void drawGdkTextLayout(GdkTextLayout gl
, float x
, float y
)
1334 cairoDrawGdkTextLayout (gl
, x
, y
);
1335 updateBufferedImage ();
1338 public void drawString(String str
, float x
, float y
)
1340 drawGlyphVector(getFont().createGlyphVector(null, str
), x
, y
);
1341 updateBufferedImage ();
1344 public void drawString(String str
, int x
, int y
)
1346 drawString (str
, (float) x
, (float) y
);
1349 public void drawString(AttributedCharacterIterator ci
, int x
, int y
)
1351 drawString (ci
, (float) x
, (float) y
);
1354 public void drawGlyphVector(GlyphVector gv
, float x
, float y
)
1356 int n
= gv
.getNumGlyphs ();
1357 int[] codes
= gv
.getGlyphCodes (0, n
, null);
1358 float[] positions
= gv
.getGlyphPositions (0, n
, null);
1360 setFont (gv
.getFont ());
1361 cairoDrawGlyphVector (getFontPeer(), x
, y
, n
, codes
, positions
);
1362 updateBufferedImage ();
1365 public void drawString(AttributedCharacterIterator ci
, float x
, float y
)
1367 GlyphVector gv
= getFont().createGlyphVector(getFontRenderContext(), ci
);
1368 drawGlyphVector(gv
, x
, y
);
1371 public void fillArc(int x
, int y
, int width
, int height
, int startAngle
,
1374 fill(new Arc2D
.Double((double) x
, (double) y
, (double) width
,
1375 (double) height
, (double) startAngle
,
1376 (double) arcAngle
, Arc2D
.OPEN
));
1379 public void fillOval(int x
, int y
, int width
, int height
)
1381 fillArc(x
, y
, width
, height
, 0, 360);
1384 public void fillRoundRect(int x
, int y
, int width
, int height
, int arcWidth
,
1387 if (arcWidth
> width
)
1389 if (arcHeight
> height
)
1392 int xx
= x
+ width
- arcWidth
;
1393 int yy
= y
+ height
- arcHeight
;
1395 fillArc(x
, y
, arcWidth
, arcHeight
, 90, 90);
1396 fillArc(xx
, y
, arcWidth
, arcHeight
, 0, 90);
1397 fillArc(xx
, yy
, arcWidth
, arcHeight
, 270, 90);
1398 fillArc(x
, yy
, arcWidth
, arcHeight
, 180, 90);
1400 fillRect(x
, y
+ arcHeight
/ 2, width
, height
- arcHeight
+ 1);
1401 fillRect(x
+ arcWidth
/ 2, y
, width
- arcWidth
+ 1, height
);
1404 public Font
getFont()
1407 return new Font("SansSerif", Font
.PLAIN
, 12);
1411 // Until such time as pango is happy to talk directly to cairo, we
1412 // actually need to redirect some calls from the GtkFontPeer and
1413 // GtkFontMetrics into the drawing kit and ask cairo ourselves.
1415 static native void releasePeerGraphicsResource(GdkFontPeer f
);
1417 public FontMetrics
getFontMetrics()
1419 return getFontMetrics(getFont());
1422 public FontMetrics
getFontMetrics(Font f
)
1424 // the reason we go via the toolkit here is to try to get
1425 // a cached object. the toolkit keeps such a cache.
1426 return Toolkit
.getDefaultToolkit().getFontMetrics(f
);
1429 public void setFont(Font f
)
1431 if (f
.getPeer() instanceof GdkFontPeer
)
1435 ((ClasspathToolkit
)(Toolkit
.getDefaultToolkit()))
1436 .getFont(f
.getName(), f
.getAttributes());
1439 public String
toString()
1441 return (getClass().getName()
1442 + "[font=" + getFont().toString()
1443 + ",color=" + fg
.toString()