Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: gnu/java/awt/peer/gtk/GdkGraphics2D.java


1   /* GdkGraphics2D.java --
2      Copyright (C) 2003, 2004, 2005  Free Software Foundation, Inc.
3   
4   This file is part of GNU Classpath.
5   
6   GNU Classpath is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10  
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.
15  
16  You should have received a copy of the GNU General Public License
17  along with GNU Classpath; see the file COPYING.  If not, write to the
18  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  02110-1301 USA.
20  
21  Linking this library statically or dynamically with other modules is
22  making a combined work based on this library.  Thus, the terms and
23  conditions of the GNU General Public License cover the whole
24  combination.
25  
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. */
37  
38  
39  package gnu.java.awt.peer.gtk;
40  
41  import gnu.classpath.Configuration;
42  import gnu.java.awt.ClasspathToolkit;
43  
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;
49  import java.awt.Font;
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.TexturePaint;
62  import java.awt.Toolkit;
63  import java.awt.font.FontRenderContext;
64  import java.awt.font.GlyphVector;
65  import java.awt.geom.AffineTransform;
66  import java.awt.geom.Arc2D;
67  import java.awt.geom.GeneralPath;
68  import java.awt.geom.NoninvertibleTransformException;
69  import java.awt.geom.PathIterator;
70  import java.awt.geom.Point2D;
71  import java.awt.geom.Rectangle2D;
72  import java.awt.image.AffineTransformOp;
73  import java.awt.image.BufferedImage;
74  import java.awt.image.BufferedImageOp;
75  import java.awt.image.ColorModel;
76  import java.awt.image.CropImageFilter;
77  import java.awt.image.DataBuffer;
78  import java.awt.image.DataBufferInt;
79  import java.awt.image.DirectColorModel;
80  import java.awt.image.FilteredImageSource;
81  import java.awt.image.ImageObserver;
82  import java.awt.image.ImagingOpException;
83  import java.awt.image.MultiPixelPackedSampleModel;
84  import java.awt.image.Raster;
85  import java.awt.image.RenderedImage;
86  import java.awt.image.SampleModel;
87  import java.awt.image.WritableRaster;
88  import java.awt.image.renderable.RenderContext;
89  import java.awt.image.renderable.RenderableImage;
90  import java.text.AttributedCharacterIterator;
91  import java.util.HashMap;
92  import java.util.Map;
93  import java.util.Stack;
94  
95  public class GdkGraphics2D extends Graphics2D
96  {
97    //////////////////////////////////////
98    ////// State Management Methods //////
99    //////////////////////////////////////
100 
101   static 
102   {
103     if (! Configuration.GTK_CAIRO_ENABLED)
104       throw new Error("Grahics2D not implemented. "
105           + "Cairo was not found or disabled at configure time");
106 
107     if (Configuration.INIT_LOAD_LIBRARY)
108       System.loadLibrary("gtkpeer");
109 
110     initStaticState();
111   }
112   
113   static native void initStaticState();
114   
115   private final int native_state = GtkGenericPeer.getUniqueInteger();  
116 
117   // These are package-private to avoid accessor methods.
118   Paint paint;
119   Stroke stroke;
120   Color fg;
121   Color bg;
122   Shape clip;
123   AffineTransform transform;
124   private GtkComponentPeer component;
125   // This is package-private to avoid an accessor method.
126   Font font;
127   private RenderingHints hints;
128   private BufferedImage bimage;
129   private boolean pixelConversionRequired;
130   private int[] pixelBuffer;
131   // This is package-private to avoid an accessor method.
132   Composite comp;
133   private Stack stateStack;
134 
135   private native void initStateUnlocked(GtkComponentPeer component);
136   private native void initState(GtkComponentPeer component);
137   private native void initState(int width, int height);
138   private native void initState(int[] pixes, int width, int height);
139   private native void copyState(GdkGraphics2D g);
140   public native void dispose();
141   private native void cairoSurfaceSetFilter(int filter);
142   private native void cairoSurfaceSetFilterUnlocked(int filter);
143   native void connectSignals(GtkComponentPeer component);
144 
145   public void finalize()
146   {
147     dispose();
148   }
149 
150   public Graphics create()
151   {
152     return new GdkGraphics2D(this);
153   }
154 
155   public Graphics create(int x, int y, int width, int height)
156   {
157     return new GdkGraphics2D(width, height);
158   }
159 
160   GdkGraphics2D(GdkGraphics2D g)
161   {
162     paint = g.paint;
163     stroke = g.stroke;
164     setRenderingHints(g.hints);
165 
166     if (g.fg.getAlpha() != -1)
167       fg = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
168                      g.fg.getAlpha());
169     else
170       fg = new Color(g.fg.getRGB());
171 
172     if (g.bg.getAlpha() != -1)
173       bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
174                      g.bg.getAlpha());
175     else
176       bg = new Color(g.bg.getRGB());
177 
178     if (g.clip == null)
179       clip = null;
180     else
181       clip = new Rectangle(g.getClipBounds());
182 
183     if (g.transform == null)
184       transform = new AffineTransform();
185     else
186       transform = new AffineTransform(g.transform);
187 
188     font = g.font;
189     component = g.component;
190     copyState(g);
191 
192     setColor(fg);
193     setBackground(bg);
194     setPaint(paint);
195     setStroke(stroke);
196     setTransform(transform);
197     setClip(clip);
198     stateStack = new Stack();
199   }
200 
201   GdkGraphics2D(int width, int height)
202   {
203     initState(width, height);
204 
205     setColor(Color.black);
206     setBackground(new Color(0, 0, 0, 0));
207     setPaint(getColor());
208     setFont(new Font("SansSerif", Font.PLAIN, 12));
209     setTransform(new AffineTransform());
210     setStroke(new BasicStroke());
211     setRenderingHints(getDefaultHints());
212 
213     stateStack = new Stack();
214   }
215 
216   GdkGraphics2D(GtkComponentPeer component)
217   {
218     this.component = component;
219     
220     if (component.isRealized())
221       initComponentGraphics2D();
222     else
223       connectSignals(component);
224   }
225 
226   void initComponentGraphics2D()
227   {
228     initState(component);
229 
230     setColor(component.awtComponent.getForeground());
231     setBackground(component.awtComponent.getBackground());
232     setPaint(getColor());
233     setTransform(new AffineTransform());
234     setStroke(new BasicStroke());
235     setRenderingHints(getDefaultHints());
236     setFont(new Font("SansSerif", Font.PLAIN, 12));
237 
238     stateStack = new Stack();
239   }
240 
241   void initComponentGraphics2DUnlocked()
242   {
243     initStateUnlocked(component);
244 
245     setColorUnlocked(component.awtComponent.getForeground());
246     setBackgroundUnlocked(component.awtComponent.getBackground());
247     setPaintUnlocked(getColorUnlocked());
248     setTransformUnlocked(new AffineTransform());
249     setStrokeUnlocked(new BasicStroke());
250     setRenderingHintsUnlocked(getDefaultHints());
251     setFontUnlocked(new Font("SansSerif", Font.PLAIN, 12));
252 
253     stateStack = new Stack();
254   }
255 
256   GdkGraphics2D(BufferedImage bimage)
257   {
258     this.bimage = bimage;
259     this.pixelBuffer = findSimpleIntegerArray(bimage.getColorModel(),
260                                               bimage.getRaster());
261     if (this.pixelBuffer == null)
262       {
263   this.pixelBuffer = new int[bimage.getRaster().getWidth() * bimage.getRaster()
264                                                                    .getHeight()];
265   this.pixelConversionRequired = true;
266       }
267     else
268       {
269         this.pixelConversionRequired = false;
270       }
271 
272     initState(this.pixelBuffer, bimage.getWidth(), bimage.getHeight());
273 
274     setColor(Color.black);
275     setBackground(new Color(0, 0, 0, 0));
276     setPaint(getColor());
277     setFont(new Font("SansSerif", Font.PLAIN, 12));
278     setTransform(new AffineTransform());
279     setStroke(new BasicStroke());
280     setRenderingHints(getDefaultHints());
281 
282     stateStack = new Stack();
283 
284     // draw current buffered image to the pixmap associated 
285     // with it, if the image is not equal to our paint buffer.
286     if (pixelConversionRequired)
287       drawImage(bimage, new AffineTransform(1, 0, 0, 1, 0, 0), bg, null);
288   }
289 
290   ////////////////////////////////////
291   ////// Native Drawing Methods //////
292   ////////////////////////////////////
293 
294   // GDK drawing methods
295   private native void gdkDrawDrawable(GdkGraphics2D other, int x, int y);
296 
297   // drawing utility methods
298   private native void drawPixels(int[] pixels, int w, int h, int stride,
299                                  double[] i2u);
300   private native void setTexturePixelsUnlocked(int[] pixels, int w, int h, int stride);
301   private native void setTexturePixels(int[] pixels, int w, int h, int stride);
302   private native void setGradient(double x1, double y1, double x2, double y2,
303                                   int r1, int g1, int b1, int a1, int r2,
304                                   int g2, int b2, int a2, boolean cyclic);
305   private native void setGradientUnlocked(double x1, double y1, double x2, double y2,
306                                   int r1, int g1, int b1, int a1, int r2,
307                                   int g2, int b2, int a2, boolean cyclic);
308 
309   // simple passthroughs to cairo
310   private native void cairoSave();
311   private native void cairoRestore();
312   private native void cairoSetMatrix(double[] m);
313   private native void cairoSetMatrixUnlocked(double[] m);
314   private native void cairoSetOperator(int cairoOperator);
315   private native void cairoSetRGBAColor(double red, double green,
316                                         double blue, double alpha);
317   private native void cairoSetRGBAColorUnlocked(double red, double green,
318                                         double blue, double alpha);
319   private native void cairoSetFillRule(int cairoFillRule);
320   private native void cairoSetLineWidth(double width);
321   private native void cairoSetLineWidthUnlocked(double width);
322   private native void cairoSetLineCap(int cairoLineCap);
323   private native void cairoSetLineCapUnlocked(int cairoLineCap);
324   private native void cairoSetLineJoin(int cairoLineJoin);
325   private native void cairoSetLineJoinUnlocked(int cairoLineJoin);
326   private native void cairoSetDash(double[] dashes, int ndash, double offset);
327   private native void cairoSetDashUnlocked(double[] dashes, int ndash, double offset);
328 
329   private native void cairoSetMiterLimit(double limit);
330   private native void cairoSetMiterLimitUnlocked(double limit);
331   private native void cairoNewPath();
332   private native void cairoMoveTo(double x, double y);
333   private native void cairoLineTo(double x, double y);
334   private native void cairoCurveTo(double x1, double y1, double x2, double y2,
335                                    double x3, double y3);
336   private native void cairoRelMoveTo(double dx, double dy);
337   private native void cairoRelLineTo(double dx, double dy);
338   private native void cairoRelCurveTo(double dx1, double dy1, double dx2,
339                                       double dy2, double dx3, double dy3);
340   private native void cairoRectangle(double x, double y, double width,
341                                      double height);
342   private native void cairoClosePath();
343   private native void cairoStroke();
344   private native void cairoFill();
345   private native void cairoClip();
346 
347   /////////////////////////////////////////////
348   ////// General Drawing Support Methods //////
349   /////////////////////////////////////////////
350 
351   private class DrawState
352   {
353     private Paint paint;
354     private Stroke stroke;
355     private Color fg;
356     private Color bg;
357     private Shape clip;
358     private AffineTransform transform;
359     private Font font;
360     private Composite comp;
361 
362     DrawState(GdkGraphics2D g)
363     {
364       this.paint = g.paint;
365       this.stroke = g.stroke;
366       this.fg = g.fg;
367       this.bg = g.bg;
368       this.clip = g.clip;
369       if (g.transform != null)
370   this.transform = (AffineTransform) g.transform.clone();
371       this.font = g.font;
372       this.comp = g.comp;
373     }
374 
375     public void restore(GdkGraphics2D g)
376     {
377       g.paint = this.paint;
378       g.stroke = this.stroke;
379       g.fg = this.fg;
380       g.bg = this.bg;
381       g.clip = this.clip;
382       g.transform = this.transform;
383       g.font = this.font;
384       g.comp = this.comp;
385     }
386   }
387 
388   private void stateSave()
389   {
390     stateStack.push(new DrawState(this));
391     cairoSave();
392   }
393 
394   private void stateRestore()
395   {
396     ((DrawState) (stateStack.pop())).restore(this);
397     cairoRestore();
398   }
399 
400   // Some operations (drawing rather than filling) require that their
401   // coords be shifted to land on 0.5-pixel boundaries, in order to land on
402   // "middle of pixel" coordinates and light up complete pixels.
403   private boolean shiftDrawCalls = false;
404 
405   private double shifted(double coord, boolean doShift)
406   {
407     if (doShift)
408       return Math.floor(coord) + 0.5;
409     else
410       return coord;
411   }
412 
413   private void walkPath(PathIterator p, boolean doShift)
414   {
415     double x = 0;
416     double y = 0;
417     double[] coords = new double[6];
418 
419     cairoSetFillRule(p.getWindingRule());
420     for (; ! p.isDone(); p.next())
421       {
422   int seg = p.currentSegment(coords);
423   switch (seg)
424     {
425     case PathIterator.SEG_MOVETO:
426       x = shifted(coords[0], doShift);
427       y = shifted(coords[1], doShift);
428       cairoMoveTo(x, y);
429       break;
430     case PathIterator.SEG_LINETO:
431       x = shifted(coords[0], doShift);
432       y = shifted(coords[1], doShift);
433       cairoLineTo(x, y);
434       break;
435     case PathIterator.SEG_QUADTO:
436       // splitting a quadratic bezier into a cubic:
437       // see: http://pfaedit.sourceforge.net/bezier.html
438       double x1 = x + (2.0 / 3.0) * (shifted(coords[0], doShift) - x);
439       double y1 = y + (2.0 / 3.0) * (shifted(coords[1], doShift) - y);
440 
441       double x2 = x1 + (1.0 / 3.0) * (shifted(coords[2], doShift) - x);
442       double y2 = y1 + (1.0 / 3.0) * (shifted(coords[3], doShift) - y);
443 
444       x = shifted(coords[2], doShift);
445       y = shifted(coords[3], doShift);
446       cairoCurveTo(x1, y1, x2, y2, x, y);
447       break;
448     case PathIterator.SEG_CUBICTO:
449       x = shifted(coords[4], doShift);
450       y = shifted(coords[5], doShift);
451       cairoCurveTo(shifted(coords[0], doShift),
452                    shifted(coords[1], doShift),
453                    shifted(coords[2], doShift),
454                    shifted(coords[3], doShift), x, y);
455       break;
456     case PathIterator.SEG_CLOSE:
457       cairoClosePath();
458       break;
459     }
460       }
461   }
462 
463   private Map getDefaultHints()
464   {
465     HashMap defaultHints = new HashMap();
466 
467     defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
468                      RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
469 
470     defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
471                      RenderingHints.VALUE_STROKE_DEFAULT);
472 
473     defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
474                      RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
475 
476     defaultHints.put(RenderingHints.KEY_ANTIALIASING,
477                      RenderingHints.VALUE_ANTIALIAS_OFF);
478 
479     defaultHints.put(RenderingHints.KEY_RENDERING,
480                      RenderingHints.VALUE_RENDER_DEFAULT);
481 
482     return defaultHints;
483   }
484 
485   public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
486   {
487     if (cm == null || raster == null)
488       return null;
489 
490     if (! cm.getColorSpace().isCS_sRGB())
491       return null;
492 
493     if (! (cm instanceof DirectColorModel))
494       return null;
495 
496     DirectColorModel dcm = (DirectColorModel) cm;
497 
498     if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
499         || dcm.getBlueMask() != 0x000000FF)
500       return null;
501 
502     if (! (raster instanceof WritableRaster))
503       return null;
504 
505     if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
506       return null;
507 
508     if (! (raster.getDataBuffer() instanceof DataBufferInt))
509       return null;
510 
511     DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
512 
513     if (db.getNumBanks() != 1)
514       return null;
515 
516     // Finally, we have determined that this is a single bank, [A]RGB-int
517     // buffer in sRGB space. It's worth checking all this, because it means
518     // that cairo can paint directly into the data buffer, which is very
519     // fast compared to all the normal copying and converting.
520 
521     return db.getData();
522   }
523 
524   private void updateBufferedImage()
525   {
526     if (bimage != null && pixelConversionRequired)
527       {
528         int height = bimage.getHeight();
529         int width = bimage.getWidth();
530         int index = 0;
531         for (int y = 0; y < height; ++y)
532           for (int x = 0; x < width; ++x)
533             bimage.setRGB(x, y, pixelBuffer[index++]);
534       }
535   }
536 
537   private boolean drawImage(Image img, AffineTransform xform,
538                             Color bgcolor, ImageObserver obs)
539   {
540     if (img == null)
541       return false;
542 
543     // FIXME: I'll fix this, /Sven
544 //     if (img instanceof GtkOffScreenImage
545 //         && img.getGraphics() instanceof GdkGraphics2D
546 //         && (xform == null || xform.getType() == AffineTransform.TYPE_IDENTITY
547 //         || xform.getType() == AffineTransform.TYPE_TRANSLATION))
548 //       {
549 //   // we are being asked to flush a double buffer from Gdk
550 //   GdkGraphics2D g2 = (GdkGraphics2D) img.getGraphics();
551 //   gdkDrawDrawable(g2, (int) xform.getTranslateX(),
552 //                   (int) xform.getTranslateY());
553 
554 //   updateBufferedImage();
555 
556 //   return true;
557 //       }
558 //     else
559       {
560   // In this case, xform is an AffineTransform that transforms bounding
561   // box of the specified image from image space to user space. However
562   // when we pass this transform to cairo, cairo will use this transform
563   // to map "user coordinates" to "pixel" coordinates, which is the 
564   // other way around. Therefore to get the "user -> pixel" transform 
565   // that cairo wants from "image -> user" transform that we currently
566   // have, we will need to invert the transformation matrix.
567   AffineTransform invertedXform = new AffineTransform();
568 
569   try
570     {
571       invertedXform = xform.createInverse();
572       if (img instanceof BufferedImage)
573         {
574     // draw an image which has actually been loaded 
575     // into memory fully
576     BufferedImage b = (BufferedImage) img;
577     return drawRaster(b.getColorModel(), b.getTile(0, 0),
578                       invertedXform, bgcolor);
579         }
580       else
581         return this.drawImage(GdkPixbufDecoder.createBufferedImage(img
582                                                                    .getSource()),
583                               xform, bgcolor, obs);
584     }
585   catch (NoninvertibleTransformException e)
586     {
587       throw new ImagingOpException("Unable to invert transform "
588                                    + xform.toString());
589     }
590       }
591   }
592 
593   //////////////////////////////////////////////////
594   ////// Implementation of Graphics2D Methods //////
595   //////////////////////////////////////////////////
596 
597   public void draw(Shape s)
598   {
599     if (stroke != null && ! (stroke instanceof BasicStroke))
600       {
601   fill(stroke.createStrokedShape(s));
602   return;
603       }
604 
605     cairoNewPath();
606 
607     if (s instanceof Rectangle2D)
608       {
609   Rectangle2D r = (Rectangle2D) s;
610   cairoRectangle(shifted(r.getX(), shiftDrawCalls),
611                  shifted(r.getY(), shiftDrawCalls), r.getWidth(),
612                  r.getHeight());
613       }
614     else
615       walkPath(s.getPathIterator(null), shiftDrawCalls);
616     cairoStroke();
617 
618     updateBufferedImage();
619   }
620 
621   public void fill(Shape s)
622   {
623     cairoNewPath();
624     if (s instanceof Rectangle2D)
625       {
626   Rectangle2D r = (Rectangle2D) s;
627   cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
628       }
629     else
630       walkPath(s.getPathIterator(null), false);
631 
632     cairoFill();
633 
634     updateBufferedImage();
635   }
636 
637   public void clip(Shape s)
638   {
639     // update it
640     if (clip == null || s == null)
641       clip = s;
642     else if (s instanceof Rectangle2D && clip instanceof Rectangle2D)
643       {
644   Rectangle2D r = (Rectangle2D) s;
645   Rectangle2D curr = (Rectangle2D) clip;
646   clip = curr.createIntersection(r);
647       }
648     else
649       throw new UnsupportedOperationException();
650 
651     // draw it
652     if (clip != null)
653       {
654   cairoNewPath();
655   if (clip instanceof Rectangle2D)
656     {
657       Rectangle2D r = (Rectangle2D) clip;
658       cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
659     }
660   else
661     walkPath(clip.getPathIterator(null), false);
662 
663   // cairoClosePath ();
664   cairoClip();
665       }
666   }
667 
668   public Paint getPaint()
669   {
670     return paint;
671   }
672 
673   public AffineTransform getTransform()
674   {
675     return (AffineTransform) transform.clone();
676   }
677 
678   public void setPaint(Paint p)
679   {
680     if (paint == null)
681       return;
682 
683     paint = p;
684     if (paint instanceof Color)
685       {
686         setColor((Color) paint);
687       }
688     else if (paint instanceof TexturePaint)
689       {
690   TexturePaint tp = (TexturePaint) paint;
691   BufferedImage img = tp.getImage();
692 
693   // map the image to the anchor rectangle  
694   int width = (int) tp.getAnchorRect().getWidth();
695   int height = (int) tp.getAnchorRect().getHeight();
696 
697   double scaleX = width / (double) img.getWidth();
698   double scaleY = width / (double) img.getHeight();
699 
700   AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
701   AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
702   BufferedImage texture = op.filter(img, null);
703   int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
704   setTexturePixels(pixels, width, height, width);
705       }
706     else if (paint instanceof GradientPaint)
707       {
708   GradientPaint gp = (GradientPaint) paint;
709   Point2D p1 = gp.getPoint1();
710   Point2D p2 = gp.getPoint2();
711   Color c1 = gp.getColor1();
712   Color c2 = gp.getColor2();
713   setGradient(p1.getX(), p1.getY(), p2.getX(), p2.getY(), c1.getRed(),
714               c1.getGreen(), c1.getBlue(), c1.getAlpha(), c2.getRed(),
715               c2.getGreen(), c2.getBlue(), c2.getAlpha(), gp.isCyclic());
716       }
717     else
718       throw new java.lang.UnsupportedOperationException();
719   }
720 
721   public void setPaintUnlocked(Paint p)
722   {
723     if (paint == null)
724       return;
725 
726     paint = p;
727     if (paint instanceof Color)
728       {
729         setColorUnlocked((Color) paint);
730       }
731     else if (paint instanceof TexturePaint)
732       {
733   TexturePaint tp = (TexturePaint) paint;
734   BufferedImage img = tp.getImage();
735 
736   // map the image to the anchor rectangle  
737   int width = (int) tp.getAnchorRect().getWidth();
738   int height = (int) tp.getAnchorRect().getHeight();
739 
740   double scaleX = width / (double) img.getWidth();
741   double scaleY = width / (double) img.getHeight();
742 
743   AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
744   AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
745   BufferedImage texture = op.filter(img, null);
746   int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
747   setTexturePixelsUnlocked(pixels, width, height, width);
748       }
749     else if (paint instanceof GradientPaint)
750       {
751   GradientPaint gp = (GradientPaint) paint;
752   Point2D p1 = gp.getPoint1();
753   Point2D p2 = gp.getPoint2();
754   Color c1 = gp.getColor1();
755   Color c2 = gp.getColor2();
756   setGradientUnlocked(p1.getX(), p1.getY(), p2.getX(), p2.getY(), c1.getRed(),
757               c1.getGreen(), c1.getBlue(), c1.getAlpha(), c2.getRed(),
758               c2.getGreen(), c2.getBlue(), c2.getAlpha(), gp.isCyclic());
759       }
760     else
761       throw new java.lang.UnsupportedOperationException();
762   }
763 
764   public void setTransform(AffineTransform tx)
765   {
766     transform = tx;
767     if (transform != null)
768       {
769   double[] m = new double[6];
770   transform.getMatrix(m);
771   cairoSetMatrix(m);
772       }
773   }
774 
775   public void setTransformUnlocked(AffineTransform tx)
776   {
777     transform = tx;
778     if (transform != null)
779       {
780   double[] m = new double[6];
781   transform.getMatrix(m);
782   cairoSetMatrixUnlocked(m);
783       }
784   }
785 
786   public void transform(AffineTransform tx)
787   {
788     if (transform == null)
789       transform = new AffineTransform(tx);
790     else
791       transform.concatenate(tx);
792     setTransform(transform);
793     if (clip != null)
794       {
795   // FIXME: this should actuall try to transform the shape
796   // rather than degrade to bounds.
797   Rectangle2D r = clip.getBounds2D();
798   double[] coords = new double[]
799                     {
800                       r.getX(), r.getY(), r.getX() + r.getWidth(),
801                       r.getY() + r.getHeight()
802                     };
803   try
804     {
805       tx.createInverse().transform(coords, 0, coords, 0, 2);
806       r.setRect(coords[0], coords[1], coords[2] - coords[0],
807                 coords[3] - coords[1]);
808       clip = r;
809     }
810   catch (java.awt.geom.NoninvertibleTransformException e)
811     {
812     }
813       }
814   }
815 
816   public void rotate(double theta)
817   {
818     transform(AffineTransform.getRotateInstance(theta));
819   }
820 
821   public void rotate(double theta, double x, double y)
822   {
823     transform(AffineTransform.getRotateInstance(theta, x, y));
824   }
825 
826   public void scale(double sx, double sy)
827   {
828     transform(AffineTransform.getScaleInstance(sx, sy));
829   }
830 
831   public void translate(double tx, double ty)
832   {
833     transform(AffineTransform.getTranslateInstance(tx, ty));
834   }
835 
836   public void translate(int x, int y)
837   {
838     translate((double) x, (double) y);
839   }
840 
841   public void shear(double shearX, double shearY)
842   {
843     transform(AffineTransform.getShearInstance(shearX, shearY));
844   }
845 
846   public Stroke getStroke()
847   {
848     return stroke;
849   }
850 
851   public void setStroke(Stroke st)
852   {
853     stroke = st;
854     if (stroke instanceof BasicStroke)
855       {
856   BasicStroke bs = (BasicStroke) stroke;
857   cairoSetLineCap(bs.getEndCap());
858   cairoSetLineWidth(bs.getLineWidth());
859   cairoSetLineJoin(bs.getLineJoin());
860   cairoSetMiterLimit(bs.getMiterLimit());
861   float[] dashes = bs.getDashArray();
862   if (dashes != null)
863     {
864       double[] double_dashes = new double[dashes.length];
865       for (int i = 0; i < dashes.length; i++)
866         double_dashes[i] = dashes[i];
867       cairoSetDash(double_dashes, double_dashes.length,
868                    (double) bs.getDashPhase());
869     }
870   else
871     cairoSetDash(new double[0], 0, 0.0);
872       }
873   }
874 
875   public void setStrokeUnlocked(Stroke st)
876   {
877     stroke = st;
878     if (stroke instanceof BasicStroke)
879       {
880   BasicStroke bs = (BasicStroke) stroke;
881   cairoSetLineCapUnlocked(bs.getEndCap());
882   cairoSetLineWidthUnlocked(bs.getLineWidth());
883   cairoSetLineJoinUnlocked(bs.getLineJoin());
884   cairoSetMiterLimitUnlocked(bs.getMiterLimit());
885   float[] dashes = bs.getDashArray();
886   if (dashes != null)
887     {
888       double[] double_dashes = new double[dashes.length];
889       for (int i = 0; i < dashes.length; i++)
890         double_dashes[i] = dashes[i];
891       cairoSetDashUnlocked(double_dashes, double_dashes.length,
892                                  (double) bs.getDashPhase());
893     }
894   else
895     cairoSetDashUnlocked(new double[0], 0, 0.0);
896       }
897   }
898 
899   ////////////////////////////////////////////////
900   ////// Implementation of Graphics Methods //////
901   ////////////////////////////////////////////////
902 
903   public void setPaintMode()
904   {
905     setComposite(java.awt.AlphaComposite.SrcOver);
906   }
907 
908   public void setXORMode(Color c)
909   {
910     setComposite(new gnu.java.awt.BitwiseXORComposite(c));
911   }
912 
913   public void setColor(Color c)
914   {
915     if (c == null)
916       c = Color.BLACK;
917 
918     fg = c;
919     paint = c;
920     cairoSetRGBAColor(fg.getRed() / 255.0, fg.getGreen() / 255.0,
921                       fg.getBlue() / 255.0, fg.getAlpha() / 255.0);
922   }
923 
924   public void setColorUnlocked(Color c)
925   {
926     if (c == null)
927       c = Color.BLACK;
928 
929     fg = c;
930     paint = c;
931     cairoSetRGBAColorUnlocked(fg.getRed() / 255.0, fg.getGreen() / 255.0,
932                       fg.getBlue() / 255.0, fg.getAlpha() / 255.0);
933   }
934 
935   public Color getColor()
936   {
937     return fg;
938   }
939 
940   public Color getColorUnlocked()
941   {
942     return getColor();
943   }
944 
945   public void clipRect(int x, int y, int width, int height)
946   {
947     clip(new Rectangle(x, y, width, height));
948   }
949 
950   public Shape getClip()
951   {
952     return clip.getBounds2D(); //getClipInDevSpace();
953   }
954 
955   public Rectangle getClipBounds()
956   {
957     if (clip == null)
958       return null;
959     else
960       return clip.getBounds();
961   }
962 
963   protected Rectangle2D getClipInDevSpace()
964   {
965     Rectangle2D uclip = clip.getBounds2D();
966     if (transform == null)
967       return uclip;
968     else
969       {
970   Point2D pos = transform.transform(new Point2D.Double(uclip.getX(),
971                                                        uclip.getY()),
972                                     (Point2D) null);
973   Point2D extent = transform.deltaTransform(new Point2D.Double(uclip
974                                                                .getWidth(),
975                                                                uclip
976                                                                .getHeight()),
977                                             (Point2D) null);
978   return new Rectangle2D.Double(pos.getX(), pos.getY(), extent.getX(),
979                                 extent.getY());
980       }
981   }
982 
983   public void setClip(int x, int y, int width, int height)
984   {
985     setClip(new Rectangle2D.Double((double) x, (double) y, (double) width,
986                                    (double) height));
987   }
988 
989   public void setClip(Shape s)
990   {
991     clip = s;
992     if (clip == null)
993       {
994   // Reset clipping.
995   Dimension d = component.awtComponent.getSize();
996   setClip(0, 0, d.width, d.height);
997       }
998     else
999       {
1000  cairoNewPath();
1001  if (s instanceof Rectangle2D)
1002    {
1003      Rectangle2D r = (Rectangle2D) s;
1004      cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
1005    }
1006  else
1007    walkPath(s.getPathIterator(null), false);
1008
1009  // cairoClosePath ();
1010  cairoClip();
1011      }
1012  }
1013
1014  private static BasicStroke draw3DRectStroke = new BasicStroke();
1015
1016  public void draw3DRect(int x, int y, int width, int height, boolean raised)
1017  {
1018    Stroke tmp = stroke;
1019    setStroke(draw3DRectStroke);
1020    super.draw3DRect(x, y, width, height, raised);
1021    setStroke(tmp);
1022    updateBufferedImage();
1023  }
1024
1025  public void fill3DRect(int x, int y, int width, int height, boolean raised)
1026  {
1027    Stroke tmp = stroke;
1028    setStroke(draw3DRectStroke);
1029    super.fill3DRect(x, y, width, height, raised);
1030    setStroke(tmp);
1031    updateBufferedImage();
1032  }
1033
1034  public void drawRect(int x, int y, int width, int height)
1035  {
1036    draw(new Rectangle(x, y, width, height));
1037  }
1038
1039  public void fillRect(int x, int y, int width, int height)
1040  {
1041    cairoNewPath();
1042    cairoRectangle(x, y, width, height);
1043    cairoFill();
1044  }
1045
1046  public void clearRect(int x, int y, int width, int height)
1047  {
1048    cairoSetRGBAColor(bg.getRed() / 255.0, bg.getGreen() / 255.0,
1049                      bg.getBlue() / 255.0, 1.0);
1050    cairoNewPath();
1051    cairoRectangle(x, y, width, height);
1052    cairoFill();
1053    setColor(fg);
1054
1055    updateBufferedImage();
1056  }
1057
1058  public void setBackground(Color c)
1059  {
1060    bg = c;
1061  }
1062
1063  public void setBackgroundUnlocked(Color c)
1064  {
1065    setBackground(c);
1066  }
1067
1068  public Color getBackground()
1069  {
1070    return bg;
1071  }
1072
1073  private void doPolygon(int[] xPoints, int[] yPoints, int nPoints,
1074                         boolean close, boolean fill)
1075  {
1076    if (nPoints < 1)
1077      return;
1078    GeneralPath gp = new GeneralPath(PathIterator.WIND_EVEN_ODD);
1079    gp.moveTo((float) xPoints[0], (float) yPoints[0]);
1080    for (int i = 1; i < nPoints; i++)
1081      gp.lineTo((float) xPoints[i], (float) yPoints[i]);
1082
1083    if (close)
1084      gp.closePath();
1085
1086    Shape sh = gp;
1087    if (fill == false && stroke != null && ! (stroke instanceof BasicStroke))
1088      {
1089  sh = stroke.createStrokedShape(gp);
1090  fill = true;
1091      }
1092
1093    if (fill)
1094      fill(sh);
1095    else
1096      draw(sh);
1097  }
1098
1099  public void drawLine(int x1, int y1, int x2, int y2)
1100  {
1101    int[] xp = new int[2];
1102    int[] yp = new int[2];
1103
1104    xp[0] = x1;
1105    xp[1] = x2;
1106    yp[0] = y1;
1107    yp[1] = y2;
1108
1109    doPolygon(xp, yp, 2, false, false);
1110  }
1111
1112  public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1113  {
1114    doPolygon(xPoints, yPoints, nPoints, true, true);
1115  }
1116
1117  public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1118  {
1119    doPolygon(xPoints, yPoints, nPoints, true, false);
1120  }
1121
1122  public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1123  {
1124    doPolygon(xPoints, yPoints, nPoints, false, false);
1125  }
1126
1127  private boolean drawRaster(ColorModel cm, Raster r,
1128                             AffineTransform imageToUser, Color bgcolor)
1129  {
1130    if (r == null)
1131      return false;
1132
1133    SampleModel sm = r.getSampleModel();
1134    DataBuffer db = r.getDataBuffer();
1135
1136    if (db == null || sm == null)
1137      return false;
1138
1139    if (cm == null)
1140      cm = ColorModel.getRGBdefault();
1141
1142    double[] i2u = new double[6];
1143    if (imageToUser != null)
1144      imageToUser.getMatrix(i2u);
1145    else
1146      {
1147  i2u[0] = 1;
1148  i2u[1] = 0;
1149  i2u[2] = 0;
1150  i2u[3] = 1;
1151  i2u[4] = 0;
1152  i2u[5] = 0;
1153      }
1154
1155    int[] pixels = findSimpleIntegerArray(cm, r);
1156
1157    if (pixels == null)
1158      {
1159  // FIXME: I don't think this code will work correctly with a non-RGB
1160  // MultiPixelPackedSampleModel. Although this entire method should 
1161  // probably be rewritten to better utilize Cairo's different supported
1162  // data formats.
1163  if (sm instanceof MultiPixelPackedSampleModel)
1164    {
1165      pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
1166      for (int i = 0; i < pixels.length; i++)
1167        pixels[i] = cm.getRGB(pixels[i]);
1168    }
1169  else
1170    {
1171      pixels = new int[r.getWidth() * r.getHeight()];
1172      for (int i = 0; i < pixels.length; i++)
1173        pixels[i] = cm.getRGB(db.getElem(i));
1174    }
1175      }
1176
1177    // Change all transparent pixels in the image to the specified bgcolor,
1178    // or (if there's no alpha) fill in an alpha channel so that it paints
1179    // correctly.
1180    if (cm.hasAlpha())
1181      {
1182  if (bgcolor != null && cm.hasAlpha())
1183    for (int i = 0; i < pixels.length; i++)
1184      {
1185        if (cm.getAlpha(pixels[i]) == 0)
1186    pixels[i] = bgcolor.getRGB();
1187      }
1188      }
1189    else
1190      for (int i = 0; i < pixels.length; i++)
1191  pixels[i] |= 0xFF000000;
1192
1193    drawPixels(pixels, r.getWidth(), r.getHeight(), r.getWidth(), i2u);
1194
1195    updateBufferedImage();
1196
1197    return true;
1198  }
1199
1200  public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1201  {
1202    drawRaster(image.getColorModel(), image.getData(), xform, bg);
1203  }
1204
1205  public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1206  {
1207    drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1208  }
1209
1210  public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1211  {
1212    return drawImage(img, xform, bg, obs);
1213  }
1214
1215  public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
1216  {
1217    Image filtered = op.filter(image, null);
1218    drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), bg, null);
1219  }
1220
1221  public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1222  {
1223    return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), bg,
1224                     observer);
1225  }
1226
1227  ///////////////////////////////////////////////
1228  ////// Unimplemented Stubs and Overloads //////
1229  ///////////////////////////////////////////////
1230
1231  public boolean hit(Rectangle rect, Shape text, boolean onStroke)
1232  {
1233    throw new java.lang.UnsupportedOperationException();
1234  }
1235
1236  public GraphicsConfiguration getDeviceConfiguration()
1237  {
1238    throw new java.lang.UnsupportedOperationException();
1239  }
1240
1241  public void setComposite(Composite comp)
1242  {
1243    this.comp = comp;
1244
1245    if (comp instanceof AlphaComposite)
1246      {
1247  AlphaComposite a = (AlphaComposite) comp;
1248  cairoSetOperator(a.getRule());
1249  Color c = getColor();
1250  setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(),
1251                     (int) (a.getAlpha() * ((float) c.getAlpha()))));
1252      }
1253    else
1254      throw new java.lang.UnsupportedOperationException();
1255  }
1256
1257  public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
1258  {
1259    hints.put(hintKey, hintValue);
1260
1261    if (hintKey.equals(RenderingHints.KEY_INTERPOLATION)
1262        || hintKey.equals(RenderingHints.KEY_ALPHA_INTERPOLATION))
1263      {
1264  if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1265    cairoSurfaceSetFilter(0);
1266
1267  else if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1268    cairoSurfaceSetFilter(1);
1269
1270  else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1271    cairoSurfaceSetFilter(2);
1272
1273  else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1274    cairoSurfaceSetFilter(3);
1275
1276  else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1277    cairoSurfaceSetFilter(4);
1278      }
1279
1280    shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1281                     || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1282  }
1283
1284  public Object getRenderingHint(RenderingHints.Key hintKey)
1285  {
1286    return hints.get(hintKey);
1287  }
1288
1289  public void setRenderingHints(Map hints)
1290  {
1291    this.hints = new RenderingHints(getDefaultHints());
1292    this.hints.add(new