OSDN Git Service

3179d3379b90b539c8cbc56dea67ce3036a15fe5
[pf3gnuchains/gcc-fork.git] / libjava / classpath / gnu / java / awt / peer / gtk / CairoGraphics2D.java
1 /* CairoGraphics2D.java --
2    Copyright (C) 2006  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.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;
96 import java.util.Map;
97 import java.util.Stack;
98
99 /**
100  * This is an abstract implementation of Graphics2D on Cairo. 
101  *
102  * It should be subclassed for different Cairo contexts.
103  *
104  * Note for subclassers: Apart from the constructor (see comments below),
105  * The following abstract methods must be implemented:
106  *
107  * Graphics create()
108  * GraphicsConfiguration getDeviceConfiguration()
109  * copyArea(int x, int y, int width, int height, int dx, int dy)
110  *
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.
114  *
115  * @author Sven de Marothy
116  */
117 public abstract class CairoGraphics2D extends Graphics2D
118 {
119   static 
120   {
121     System.loadLibrary("gtkpeer");
122   }
123
124   /**
125    * Important: This is a pointer to the native cairographics2d structure
126    *
127    * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
128    */
129   long nativePointer;
130
131   // Drawing state variables
132   /**
133    * The current paint
134    */
135   Paint paint;
136
137   /**
138    * The current stroke
139    */
140   Stroke stroke;
141
142   /*
143    * Current foreground and background color.
144    */
145   Color fg, bg;
146
147   /**
148    * Current clip shape.
149    */
150   Shape clip;
151
152   /**
153    * Current transform.
154    */
155   AffineTransform transform;
156
157   /**
158    * Current font.
159    */
160   Font font;
161
162   /**
163    * The current compositing context, if any.
164    */
165   Composite comp;
166
167   /**
168    * Rendering hint map.
169    */
170   private RenderingHints hints;
171
172   /**
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. 
176    */
177   private boolean shiftDrawCalls = false;
178
179   /**
180    * Keep track if the first clip to be set, which is restored on setClip(null);
181    */
182   private boolean firstClip = true;
183   private Shape originalClip;
184
185   /**
186    * Stroke used for 3DRects
187    */
188   private static BasicStroke draw3DRectStroke = new BasicStroke();
189
190   static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
191   static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF, 
192                                                   0xFF000000);
193
194   /**
195    * Constructor does nothing.
196    */
197   public CairoGraphics2D()
198   {
199   }
200
201   /**
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.
204    */
205   public void setup(long cairo_t_pointer)
206   { 
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());
215   }
216
217   /**
218    * Same as above, but copies the state of another CairoGraphics2D.
219    */
220   public void copy(CairoGraphics2D g, long cairo_t_pointer)
221   {
222     nativePointer = init(cairo_t_pointer);
223     paint = g.paint;
224     stroke = g.stroke;
225     setRenderingHints(g.hints);
226     
227     Color foreground;
228
229     if (g.fg.getAlpha() != -1)
230       foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
231                      g.fg.getAlpha());
232     else
233       foreground = new Color(g.fg.getRGB());
234
235     if (g.bg != null)
236       {
237         if (g.bg.getAlpha() != -1)
238           bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
239                          g.bg.getAlpha());
240         else
241           bg = new Color(g.bg.getRGB());
242       }
243
244     if (g.clip == null)
245       clip = null;
246     else
247       clip = new Rectangle(g.getClipBounds());
248
249     if (g.transform == null)
250       transform = new AffineTransform();
251     else
252       transform = new AffineTransform(g.transform);
253
254     font = g.font;
255
256     setColor(foreground);
257     setBackground(bg);
258     setPaint(paint);
259     setStroke(stroke);
260     setTransform(transform);
261   }
262
263   /**
264    * Generic destructor - call the native dispose() method.
265    */
266   public void finalize()
267   {
268     dispose();
269   }
270
271   /**
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.
276    */
277   public void dispose()
278   {    
279     disposeNative();
280     nativePointer = 0;
281   }
282
283   /**
284    * Allocate the cairographics2d structure and set the cairo_t pointer in it.
285    * @param pointer - a cairo_t pointer, casted to a long.
286    */
287   private native long init(long pointer);
288
289   /**
290    * These are declared abstract as there may be context-specific issues.
291    */
292   public abstract Graphics create();
293
294   public abstract GraphicsConfiguration getDeviceConfiguration();
295
296   protected abstract void copyAreaImpl(int x, int y, 
297                                        int width, int height, int dx, int dy);
298
299
300   protected abstract Rectangle2D getRealBounds();
301
302   ////// Native Methods ////////////////////////////////////////////////////
303
304   /**
305    * Dispose of allocate native resouces.
306    */
307   public native void disposeNative();
308
309   /**
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
314    */
315   private native void drawPixels(int[] pixels, int w, int h, int stride,
316                                  double[] i2u);
317
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);
321   
322   private native void setTexturePixels(int[] pixels, int w, int h, int stride);
323
324   /**
325    * Set the current transform matrix
326    */
327   private native void cairoSetMatrix(double[] m);
328
329   /**
330    * Set the compositing operator
331    */
332   private native void cairoSetOperator(int cairoOperator);
333
334   /**
335    * Sets the current color in RGBA as a 0.0-1.0 double
336    */
337   private native void cairoSetRGBAColor(double red, double green,
338                                         double blue, double alpha);
339
340   /**
341    * Sets the current winding rule in Cairo
342    */
343   private native void cairoSetFillRule(int cairoFillRule);
344
345   /**
346    * Set the line style, cap, join and miter limit.
347    * Cap and join parameters are in the BasicStroke enumerations.
348    */
349   private native void cairoSetLine(double width, int cap, int join, double miterLimit);
350
351   /**
352    * Set the dash style
353    */
354   private native void cairoSetDash(double[] dashes, int ndash, double offset);
355
356   /*
357    * Draws a Glyph Vector
358    */
359   native void cairoDrawGlyphVector(GdkFontPeer font, 
360                                    float x, float y, int n, 
361                                    int[] codes, float[] positions);
362
363
364   private native void cairoRelCurveTo(double dx1, double dy1, double dx2,
365                                       double dy2, double dx3, double dy3);
366
367   /**
368    * Appends a rectangle to the current path
369    */
370   private native void cairoRectangle(double x, double y, double width,
371                                      double height);
372
373   /**
374    * New current path
375    */
376   private native void cairoNewPath();
377
378   /** 
379    * Close current path
380    */
381   private native void cairoClosePath();
382
383   /** moveTo */
384   private native void cairoMoveTo(double x, double y);
385
386   /** relative moveTo */
387   private native void cairoRelMoveTo(double dx, double dy);
388
389   /** lineTo */
390   private native void cairoLineTo(double x, double y);
391
392   /** relative lineTo */
393   private native void cairoRelLineTo(double dx, double dy);
394
395   /** Cubic curve-to */
396   private native void cairoCurveTo(double x1, double y1, double x2, double y2,
397                                    double x3, double y3);
398
399   /**
400    * Stroke current path
401    */
402   private native void cairoStroke();
403
404   /**
405    * Fill current path
406    */
407   private native void cairoFill();
408
409   /** 
410    * Clip current path
411    */
412   private native void cairoClip();
413
414   /** 
415    * Save clip
416    */
417   private native void cairoPreserveClip();
418
419   /** 
420    * Save clip
421    */
422   private native void cairoResetClip();
423
424   /**
425    * Set interpolation types
426    */
427   private native void cairoSurfaceSetFilter(int filter);
428
429   ///////////////////////// TRANSFORMS ///////////////////////////////////
430   /**
431    * Set the current transform
432    */ 
433   public void setTransform(AffineTransform tx)
434   {
435     transform = tx;
436     if (transform != null)
437       {
438         double[] m = new double[6];
439         transform.getMatrix(m);
440         cairoSetMatrix(m);
441       }
442   }
443   
444   public void transform(AffineTransform tx)
445   {
446     if (transform == null)
447       transform = new AffineTransform(tx);
448     else
449       transform.concatenate(tx);
450     setTransform(transform);
451     if (clip != null)
452       {
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[]
457           {
458             r.getX(), r.getY(), r.getX() + r.getWidth(),
459             r.getY() + r.getHeight()
460           };
461         try
462           {
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]);
466             clip = r;
467           }
468         catch (java.awt.geom.NoninvertibleTransformException e)
469           {
470           }
471       }
472   }
473
474   public void rotate(double theta)
475   {
476     transform(AffineTransform.getRotateInstance(theta));
477   }
478
479   public void rotate(double theta, double x, double y)
480   {
481     transform(AffineTransform.getRotateInstance(theta, x, y));
482   }
483
484   public void scale(double sx, double sy)
485   {
486     transform(AffineTransform.getScaleInstance(sx, sy));
487   }
488
489   /**
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.
492    */
493   public void translate(double tx, double ty)
494   {
495     if (transform != null)
496       transform.translate(tx, ty);
497     else
498       transform = AffineTransform.getTranslateInstance(tx, ty);
499
500     if (clip != null)
501       {
502         // FIXME: this should actuall try to transform the shape
503         // rather than degrade to bounds.
504         Rectangle2D r;
505
506         if (clip instanceof Rectangle2D)
507           r = (Rectangle2D) clip;
508         else
509           r = clip.getBounds2D();
510
511         r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(), r.getHeight());
512         clip = r;
513       }
514
515     setTransform(transform);
516   }
517   
518   public void translate(int x, int y)
519   {
520     translate((double) x, (double) y);
521   }
522
523   public void shear(double shearX, double shearY)
524   {
525     transform(AffineTransform.getShearInstance(shearX, shearY));
526   }
527
528   ///////////////////////// DRAWING STATE ///////////////////////////////////
529
530   public void clip(Shape s)
531   {
532     // Do not touch clip when s == null.
533     if (s == null)
534       return;
535
536     // If the current clip is still null, initialize it.
537     if (clip == null)
538       clip = originalClip;
539     
540     // This is so common, let's optimize this. 
541     else if (clip instanceof Rectangle2D && s instanceof Rectangle2D)
542       {
543         Rectangle2D clipRect = (Rectangle2D) clip;
544         Rectangle2D r = (Rectangle2D) s;
545         Rectangle2D.intersect(clipRect, r, clipRect);
546         // Call setClip so that subclasses get notified.
547         setClip(clipRect);
548       }
549    else
550      {
551        Area current;
552        if (clip instanceof Area)
553          current = (Area) clip;
554        else
555          current = new Area(clip);
556
557        Area intersect;
558        if (s instanceof Area)
559          intersect = (Area) s;
560        else
561          intersect = new Area(s);
562
563        current.intersect(intersect);
564        clip = current;
565        // Call setClip so that the native side gets notified.
566        setClip(clip);
567      }
568   }
569
570   public Paint getPaint()
571   {
572     return paint;
573   }
574
575   public AffineTransform getTransform()
576   {
577     return (AffineTransform) transform.clone();
578   }
579
580   public void setPaint(Paint p)
581   {
582     if (paint == null)
583       return;
584
585     paint = p;
586     if (paint instanceof Color)
587       {
588         setColor((Color) paint);
589       }
590     else if (paint instanceof TexturePaint)
591       {
592         TexturePaint tp = (TexturePaint) paint;
593         BufferedImage img = tp.getImage();
594
595         // map the image to the anchor rectangle  
596         int width = (int) tp.getAnchorRect().getWidth();
597         int height = (int) tp.getAnchorRect().getHeight();
598
599         double scaleX = (width+1) / (double) img.getWidth();
600         double scaleY = (height+1) / (double) img.getHeight();
601
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);
607       }
608     else if (paint instanceof GradientPaint)
609       {
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());
618       }
619     else
620       throw new java.lang.UnsupportedOperationException();
621   }
622
623   public Stroke getStroke()
624   {
625     return stroke;
626   }
627
628   public void setStroke(Stroke st)
629   {
630     stroke = st;
631     if (stroke instanceof BasicStroke)
632       {
633         BasicStroke bs = (BasicStroke) stroke;
634         cairoSetLine(bs.getLineWidth(), bs.getEndCap(), 
635                      bs.getLineJoin(), bs.getMiterLimit());
636
637         float[] dashes = bs.getDashArray();
638         if (dashes != null)
639           {
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());
645           }
646         else
647           cairoSetDash(new double[0], 0, 0.0);
648       }
649   }
650
651   public void setPaintMode()
652   {
653     setComposite(AlphaComposite.SrcOver);
654   }
655
656   public void setXORMode(Color c)
657   {
658     // FIXME: implement
659   }
660
661   public void setColor(Color c)
662   {
663     if (c == null)
664       c = Color.BLACK;
665
666     fg = c;
667     paint = c;
668     updateColor();
669   }
670   
671   /**
672    * Set the current fg value as the cairo color.
673    */
674   void updateColor()
675   {
676     if (fg == null)
677       fg = Color.BLACK;
678     cairoSetRGBAColor(fg.getRed() / 255.0, fg.getGreen() / 255.0,
679                       fg.getBlue() / 255.0, fg.getAlpha() / 255.0);
680   }
681
682   public Color getColor()
683   {
684     return fg;
685   }
686
687   public void clipRect(int x, int y, int width, int height)
688   {
689     clip(new Rectangle(x, y, width, height));
690   }
691
692   public Shape getClip()
693   {
694     if (clip == null)
695       return null;
696     else
697       return clip.getBounds2D(); //getClipInDevSpace();
698   }
699
700   public Rectangle getClipBounds()
701   {
702     if (clip == null)
703       return null;
704     else
705       return clip.getBounds();
706   }
707
708   protected Rectangle2D getClipInDevSpace()
709   {
710     Rectangle2D uclip = clip.getBounds2D();
711     if (transform == null)
712       return uclip;
713     else
714       {
715         Point2D pos = transform.transform(new Point2D.Double(uclip.getX(),
716                                                              uclip.getY()),
717                                           (Point2D) null);
718         Point2D extent = transform.deltaTransform(new Point2D.Double(uclip
719                                                                      .getWidth(),
720                                                                      uclip
721                                                                      .getHeight()),
722                                                   (Point2D) null);
723         return new Rectangle2D.Double(pos.getX(), pos.getY(), extent.getX(),
724                                       extent.getY());
725       }
726   }
727
728   public void setClip(int x, int y, int width, int height)
729   {
730     if( width < 0 || height < 0 )
731       return;
732
733     setClip(new Rectangle2D.Double(x, y, width, height));
734   }
735
736   public void setClip(Shape s)
737   {    
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.
742     if( firstClip )
743       {
744         originalClip = s;
745         firstClip = false;
746       }
747
748     if (s == null)
749       clip = originalClip;
750     else
751       clip = s;
752
753     cairoResetClip();
754
755     cairoNewPath();
756     if (clip instanceof Rectangle2D)
757       {
758         Rectangle2D r = (Rectangle2D) clip;
759         cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
760       }
761     else
762       walkPath(clip.getPathIterator(null), false);
763     
764     cairoClip();
765   }
766
767   public void setBackground(Color c)
768   {
769     if (c == null)
770       c = Color.WHITE;
771     bg = c;
772   }
773
774   public Color getBackground()
775   {
776     return bg;
777   }
778
779   /**
780    * Return the current composite.
781    */
782   public Composite getComposite()
783   {
784     if (comp == null)
785       return AlphaComposite.SrcOver;
786     else
787       return comp;
788   }
789
790   /**
791    * Sets the current composite context.
792    */
793   public void setComposite(Composite comp)
794   {
795     this.comp = comp;
796
797     if (comp instanceof AlphaComposite)
798       {
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()))));
804       }
805     else
806       {
807         // FIXME: implement general Composite support
808         throw new java.lang.UnsupportedOperationException();
809       }
810   }
811
812   ///////////////////////// DRAWING PRIMITIVES ///////////////////////////////////
813
814   public void draw(Shape s)
815   {
816     if (stroke != null && ! (stroke instanceof BasicStroke))
817       {
818         fill(stroke.createStrokedShape(s));
819         return;
820       }
821
822     cairoNewPath();
823
824     if (s instanceof Rectangle2D)
825       {
826         Rectangle2D r = (Rectangle2D) s;
827         cairoRectangle(shifted(r.getX(), shiftDrawCalls),
828                        shifted(r.getY(), shiftDrawCalls), r.getWidth(),
829                        r.getHeight());
830       }
831     else
832       walkPath(s.getPathIterator(null), shiftDrawCalls);
833     cairoStroke();
834   }
835
836   public void fill(Shape s)
837   {
838     cairoNewPath();
839     if (s instanceof Rectangle2D)
840       {
841         Rectangle2D r = (Rectangle2D) s;
842         cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
843       }
844     else
845       walkPath(s.getPathIterator(null), false);
846
847     cairoFill();
848   }
849
850   /**
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)
854    */
855
856   public void clearRect(int x, int y, int width, int height)
857   {
858     if (bg != null)
859       cairoSetRGBAColor(bg.getRed() / 255.0, bg.getGreen() / 255.0,
860                         bg.getBlue() / 255.0, 1.0);
861     fillRect(x, y, width, height);
862     updateColor();
863   }
864
865   public void draw3DRect(int x, int y, int width, int height, boolean raised)
866   {
867     Stroke tmp = stroke;
868     setStroke(draw3DRectStroke);
869     super.draw3DRect(x, y, width, height, raised);
870     setStroke(tmp);
871   }
872
873   public void drawArc(int x, int y, int width, int height, int startAngle,
874                       int arcAngle)
875   {
876     draw(new Arc2D.Double((double) x, (double) y, (double) width,
877                           (double) height, (double) startAngle,
878                           (double) arcAngle, Arc2D.OPEN));
879   }
880
881   public void drawLine(int x1, int y1, int x2, int y2)
882   {
883     draw(new Line2D.Double(x1, y1, x2, y2));
884   }
885
886   public void drawRect(int x, int y, int width, int height)
887   {
888     draw(new Rectangle(x, y, width, height));
889   }
890
891   public void fillArc(int x, int y, int width, int height, int startAngle,
892                       int arcAngle)
893   {
894     fill(new Arc2D.Double((double) x, (double) y, (double) width,
895                           (double) height, (double) startAngle,
896                           (double) arcAngle, Arc2D.OPEN));
897   }
898
899   public void fillRect(int x, int y, int width, int height)
900   {
901     fill(new Rectangle(x, y, width, height));
902   }
903
904   public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
905   {
906     fill(new Polygon(xPoints, yPoints, nPoints));
907   }
908
909   public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
910   {
911     draw(new Polygon(xPoints, yPoints, nPoints));
912   }
913
914   public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
915   {
916     draw(new Polygon(xPoints, yPoints, nPoints));
917   }
918
919   public void drawOval(int x, int y, int width, int height)
920   {
921     drawArc(x, y, width, height, 0, 360);
922   }
923
924   public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
925                             int arcHeight)
926   {
927     draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
928   }
929
930   public void fillOval(int x, int y, int width, int height)
931   {
932     fillArc(x, y, width, height, 0, 360);
933   }
934
935   public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
936                             int arcHeight)
937   {
938     fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
939   }
940
941   /**
942    * CopyArea - performs clipping to the native surface as a convenience 
943    * (requires getRealBounds). Then calls copyAreaImpl.
944    */
945   public void copyArea(int ox, int oy, int owidth, int oheight, 
946                        int odx, int ody)
947   {
948     Point2D pos = transform.transform(new Point2D.Double(ox, oy),
949                                       (Point2D) null);
950     Point2D dim = transform.transform(new Point2D.Double(ox + owidth, 
951                                                          oy + oheight),
952                                       (Point2D) null);
953     Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody),
954                                      (Point2D) null);
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());
961
962     Rectangle2D r = getRealBounds();
963
964     if( width < 0 || height < 0 )
965       return;
966     // Return if outside the surface
967     if( x + dx > r.getWidth() || y + dy > r.getHeight() )
968       return;
969
970     if( x + dx + width < r.getX() || y + dy + height < r.getY() )
971       return;
972
973     // Clip edges if necessary 
974     if( x + dx < r.getX() ) // left
975       {
976         width = x + dx + width;
977         x = (int)r.getX() - dx;
978       }
979
980     if( y + dy < r.getY() ) // top
981       {
982         height = y + dy + height;
983         y = (int)r.getY() - dy;
984       }
985
986     if( x + dx + width >= r.getWidth() ) // right
987       width = (int)r.getWidth() - dx - x;
988
989     if( y + dy + height >= r.getHeight() ) // bottom
990       height = (int)r.getHeight() - dy - y;
991
992     copyAreaImpl(x, y, width, height, dx, dy);
993   }
994
995   ///////////////////////// RENDERING HINTS ///////////////////////////////////
996
997   /**
998    * FIXME- support better
999    */
1000   public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
1001   {
1002     hints.put(hintKey, hintValue);
1003
1004     if (hintKey.equals(RenderingHints.KEY_INTERPOLATION)
1005         || hintKey.equals(RenderingHints.KEY_ALPHA_INTERPOLATION))
1006       {
1007         if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1008           cairoSurfaceSetFilter(0);
1009
1010         else if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1011           cairoSurfaceSetFilter(1);
1012
1013         else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1014           cairoSurfaceSetFilter(2);
1015
1016         else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1017           cairoSurfaceSetFilter(3);
1018
1019         else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1020           cairoSurfaceSetFilter(4);
1021       }
1022
1023     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1024       || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1025   }
1026
1027   public Object getRenderingHint(RenderingHints.Key hintKey)
1028   {
1029     return hints.get(hintKey);
1030   }
1031
1032   public void setRenderingHints(Map hints)
1033   {
1034     this.hints = new RenderingHints(getDefaultHints());
1035     this.hints.add(new RenderingHints(hints));
1036
1037     if (hints.containsKey(RenderingHints.KEY_INTERPOLATION))
1038       {
1039         if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1040           cairoSurfaceSetFilter(0);
1041
1042         else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1043           cairoSurfaceSetFilter(1);
1044       }
1045
1046     if (hints.containsKey(RenderingHints.KEY_ALPHA_INTERPOLATION))
1047       {
1048         if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1049           cairoSurfaceSetFilter(2);
1050
1051         else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1052           cairoSurfaceSetFilter(3);
1053
1054         else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1055           cairoSurfaceSetFilter(4);
1056       }
1057
1058     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1059       || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1060   }
1061
1062   public void addRenderingHints(Map hints)
1063   {
1064     this.hints.add(new RenderingHints(hints));
1065   }
1066
1067   public RenderingHints getRenderingHints()
1068   {
1069     return hints;
1070   }
1071
1072   ///////////////////////// IMAGE. METHODS ///////////////////////////////////
1073
1074   protected boolean drawImage(Image img, AffineTransform xform,
1075                             Color bgcolor, ImageObserver obs)
1076   {
1077     if (img == null)
1078       return false;
1079
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();
1088
1089     try
1090       {
1091         invertedXform = xform.createInverse();
1092       }
1093     catch (NoninvertibleTransformException e)
1094       {
1095         throw new ImagingOpException("Unable to invert transform "
1096                                      + xform.toString());
1097       }
1098
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);
1104
1105     BufferedImage b = (BufferedImage) img;
1106     DataBuffer db;
1107     double[] i2u = new double[6];
1108     int width = b.getWidth();
1109     int height = b.getHeight();
1110
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 );
1115     else
1116       db = b.getRaster().getDataBuffer();
1117
1118     invertedXform.getMatrix(i2u);
1119
1120     if(db instanceof CairoSurface)
1121       {
1122         ((CairoSurface)db).drawSurface(this, i2u);
1123         return true;
1124       }
1125             
1126     if( bgcolor != null )
1127       {
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 );
1137       }
1138
1139     int[] pixels;
1140
1141     // Shortcut for easy color models.
1142     if( b.getColorModel().equals(rgb32) )
1143       {
1144         pixels = ((DataBufferInt)db).getData();
1145         for(int i = 0; i < pixels.length; i++)
1146           pixels[i] |= 0xFF000000;
1147       }
1148     else if( b.getColorModel().equals(argb32) )
1149       {
1150         pixels = ((DataBufferInt)db).getData();
1151       }
1152     else
1153       {
1154         pixels = b.getRGB(0, 0, width, height,
1155                           null, 0, width);
1156       }
1157
1158     drawPixels(pixels, width, height, width, i2u);
1159
1160     // Cairo seems to lose the current color which must be restored.
1161     updateColor();
1162     return true;
1163   }
1164
1165   public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1166   {
1167     drawRaster(image.getColorModel(), image.getData(), xform, null);
1168   }
1169
1170   public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1171   {
1172     drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1173   }
1174
1175   public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1176   {
1177     return drawImage(img, xform, null, obs);
1178   }
1179
1180   public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
1181   {
1182     Image filtered = op.filter(image, null);
1183     drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null);
1184   }
1185
1186   public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1187   {
1188     return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null,
1189                      observer);
1190   }
1191
1192   public boolean drawImage(Image img, int x, int y, Color bgcolor,
1193                            ImageObserver observer)
1194   {
1195     return drawImage(img, x, y, img.getWidth(observer),
1196                      img.getHeight(observer), bgcolor, observer);
1197   }
1198
1199   public boolean drawImage(Image img, int x, int y, int width, int height,
1200                            Color bgcolor, ImageObserver observer)
1201   {
1202     double scaleX = width / (double) img.getWidth(observer);
1203     double scaleY = height / (double) img.getHeight(observer);
1204     if( scaleX == 0 || scaleY == 0 )
1205       return true;
1206
1207     return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y),
1208                      bgcolor, observer);
1209   }
1210
1211   public boolean drawImage(Image img, int x, int y, int width, int height,
1212                            ImageObserver observer)
1213   {
1214     return drawImage(img, x, y, width, height, null, observer);
1215   }
1216
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)
1220   {
1221     if (img == null)
1222       return false;
1223
1224     int sourceWidth = sx2 - sx1;
1225     int sourceHeight = sy2 - sy1;
1226
1227     int destWidth = dx2 - dx1;
1228     int destHeight = dy2 - dy1;
1229
1230     if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 || 
1231        sourceHeight == 0)
1232       return true;
1233
1234     double scaleX = destWidth / (double) sourceWidth;
1235     double scaleY = destHeight / (double) sourceHeight;
1236
1237     // FIXME: Avoid using an AT if possible here - it's at least twice as slow.
1238     
1239     Shape oldClip = getClip();
1240     int cx, cy, cw, ch;
1241     if( dx1 < dx2 ) 
1242       { cx = dx1; cw = dx2 - dx1; }
1243     else
1244       { cx = dx2; cw = dx1 - dx2; }
1245     if( dy1 < dy2 ) 
1246       { cy = dy1; ch = dy2 - dy1; }
1247     else
1248       { cy = dy2; ch = dy1 - dy2; }
1249     
1250     setClip( cx, cy, cw, ch );
1251
1252     AffineTransform tx = new AffineTransform();
1253     tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY );
1254     tx.scale( scaleX, scaleY );
1255
1256     boolean retval = drawImage(img, tx, bgcolor, observer);
1257     setClip( oldClip );
1258     return retval;
1259   }
1260
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)
1264   {
1265     return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
1266   }
1267
1268   ///////////////////////// TEXT METHODS ////////////////////////////////////
1269
1270   public void drawString(String str, float x, float y)
1271   {
1272     if (str == null || str.length() == 0)
1273       return;
1274
1275     drawGlyphVector(getFont().createGlyphVector(null, str), x, y);
1276   }
1277
1278   public void drawString(String str, int x, int y)
1279   {
1280     drawString (str, (float) x, (float) y);
1281   }
1282
1283   public void drawString(AttributedCharacterIterator ci, int x, int y)
1284   {
1285     drawString (ci, (float) x, (float) y);
1286   }
1287
1288   public void drawGlyphVector(GlyphVector gv, float x, float y)
1289   {
1290     int n = gv.getNumGlyphs ();
1291     int[] codes = gv.getGlyphCodes (0, n, null);
1292     float[] positions = gv.getGlyphPositions (0, n, null);
1293     
1294     setFont (gv.getFont ());
1295     cairoDrawGlyphVector( (GdkFontPeer)getFont().getPeer(), x, y, n, codes, positions);
1296   }
1297
1298   public void drawString(AttributedCharacterIterator ci, float x, float y)
1299   {
1300     GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci);
1301     drawGlyphVector(gv, x, y);
1302   }
1303
1304   /**
1305    * Should perhaps be contexct dependent, but this is left for now as an 
1306    * overloadable default implementation.
1307    */
1308   public FontRenderContext getFontRenderContext()
1309   {
1310     return new FontRenderContext(transform, true, true);
1311   }
1312
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.
1316
1317   public FontMetrics getFontMetrics()
1318   {
1319     return getFontMetrics(getFont());
1320   }
1321
1322   public FontMetrics getFontMetrics(Font f)
1323   {
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);
1327   }
1328
1329   public void setFont(Font f)
1330   {
1331     // Sun's JDK does not throw NPEs, instead it leaves the current setting
1332     // unchanged. So do we.
1333     if (f == null)
1334       return;
1335
1336     if (f.getPeer() instanceof GdkFontPeer)
1337       font = f;
1338     else
1339       font = 
1340         ((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
1341         .getFont(f.getName(), f.getAttributes());    
1342   }
1343
1344   public Font getFont()
1345   {
1346     if (font == null)
1347       return new Font("SansSerif", Font.PLAIN, 12);
1348     return font;
1349   }
1350
1351   /////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
1352
1353   public boolean hit(Rectangle rect, Shape s, boolean onStroke)
1354   {
1355     if( onStroke )
1356       {
1357         Shape stroked = stroke.createStrokedShape( s );
1358         return stroked.intersects( (double)rect.x, (double)rect.y, 
1359                                    (double)rect.width, (double)rect.height );
1360       }
1361     return s.intersects( (double)rect.x, (double)rect.y, 
1362                          (double)rect.width, (double)rect.height );
1363   }
1364
1365   public String toString()
1366   {
1367     return  (getClass().getName()
1368              + "[font=" + getFont().toString()
1369              + ",color=" + fg.toString()
1370              + "]");
1371   }
1372
1373   ///////////////////////// PRIVATE METHODS ///////////////////////////////////
1374
1375   /**
1376    * All the drawImage() methods eventually get delegated here if the image
1377    * is not a Cairo surface.
1378    *
1379    * @param bgcolor - if non-null draws the background color before 
1380    * drawing the image.
1381    */
1382   private boolean drawRaster(ColorModel cm, Raster r,
1383                              AffineTransform imageToUser, Color bgcolor)
1384   {
1385     if (r == null)
1386       return false;
1387
1388     SampleModel sm = r.getSampleModel();
1389     DataBuffer db = r.getDataBuffer();
1390
1391     if (db == null || sm == null)
1392       return false;
1393
1394     if (cm == null)
1395       cm = ColorModel.getRGBdefault();
1396
1397     double[] i2u = new double[6];
1398     if (imageToUser != null)
1399       imageToUser.getMatrix(i2u);
1400     else
1401       {
1402         i2u[0] = 1;
1403         i2u[1] = 0;
1404         i2u[2] = 0;
1405         i2u[3] = 1;
1406         i2u[4] = 0;
1407         i2u[5] = 0;
1408       }
1409
1410     int[] pixels = findSimpleIntegerArray(cm, r);
1411
1412     if (pixels == null)
1413       {
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
1417         // data formats.
1418         if (sm instanceof MultiPixelPackedSampleModel)
1419           {
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]);
1423           }
1424         else
1425           {
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));
1429           }
1430       }
1431
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
1434     // correctly.
1435     if (cm.hasAlpha())
1436       {
1437         if (bgcolor != null && cm.hasAlpha())
1438           for (int i = 0; i < pixels.length; i++)
1439             {
1440               if (cm.getAlpha(pixels[i]) == 0)
1441                 pixels[i] = bgcolor.getRGB();
1442             }
1443       }
1444     else
1445       for (int i = 0; i < pixels.length; i++)
1446         pixels[i] |= 0xFF000000;
1447
1448     drawPixels(pixels, r.getWidth(), r.getHeight(), r.getWidth(), i2u);
1449
1450     // Cairo seems to lose the current color which must be restored.
1451     updateColor();
1452     
1453     return true;
1454   }
1455
1456   /**
1457    * Shifts coordinates by 0.5.
1458    */
1459   private double shifted(double coord, boolean doShift)
1460   {
1461     if (doShift)
1462       return Math.floor(coord) + 0.5;
1463     else
1464       return coord;
1465   }
1466
1467   /**
1468    * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
1469    */
1470   private void walkPath(PathIterator p, boolean doShift)
1471   {
1472     double x = 0;
1473     double y = 0;
1474     double[] coords = new double[6];
1475
1476     cairoSetFillRule(p.getWindingRule());
1477     for (; ! p.isDone(); p.next())
1478       {
1479         int seg = p.currentSegment(coords);
1480         switch (seg)
1481           {
1482           case PathIterator.SEG_MOVETO:
1483             x = shifted(coords[0], doShift);
1484             y = shifted(coords[1], doShift);
1485             cairoMoveTo(x, y);
1486             break;
1487           case PathIterator.SEG_LINETO:
1488             x = shifted(coords[0], doShift);
1489             y = shifted(coords[1], doShift);
1490             cairoLineTo(x, y);
1491             break;
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);
1497
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);
1500
1501             x = shifted(coords[2], doShift);
1502             y = shifted(coords[3], doShift);
1503             cairoCurveTo(x1, y1, x2, y2, x, y);
1504             break;
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);
1512             break;
1513           case PathIterator.SEG_CLOSE:
1514             cairoClosePath();
1515             break;
1516           }
1517       }
1518   }
1519
1520   /**
1521    * Used by setRenderingHints()
1522    */
1523   private Map getDefaultHints()
1524   {
1525     HashMap defaultHints = new HashMap();
1526
1527     defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
1528                      RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
1529
1530     defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
1531                      RenderingHints.VALUE_STROKE_DEFAULT);
1532
1533     defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
1534                      RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
1535
1536     defaultHints.put(RenderingHints.KEY_ANTIALIASING,
1537                      RenderingHints.VALUE_ANTIALIAS_OFF);
1538
1539     defaultHints.put(RenderingHints.KEY_RENDERING,
1540                      RenderingHints.VALUE_RENDER_DEFAULT);
1541
1542     return defaultHints;
1543   }
1544
1545   /**
1546    * Used by drawRaster and GdkPixbufDecoder
1547    */
1548   public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
1549   {
1550     if (cm == null || raster == null)
1551       return null;
1552
1553     if (! cm.getColorSpace().isCS_sRGB())
1554       return null;
1555
1556     if (! (cm instanceof DirectColorModel))
1557       return null;
1558
1559     DirectColorModel dcm = (DirectColorModel) cm;
1560
1561     if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
1562         || dcm.getBlueMask() != 0x000000FF)
1563       return null;
1564
1565     if (! (raster instanceof WritableRaster))
1566       return null;
1567
1568     if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
1569       return null;
1570
1571     if (! (raster.getDataBuffer() instanceof DataBufferInt))
1572       return null;
1573
1574     DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
1575
1576     if (db.getNumBanks() != 1)
1577       return null;
1578
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.
1583
1584     return db.getData();
1585   }
1586 }