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 
43 import gnu.java.awt.ClasspathToolkit;
44 
45 import java.awt.AWTPermission;
46 import java.awt.AlphaComposite;
47 import java.awt.BasicStroke;
48 import java.awt.Color;
49 import java.awt.Composite;
50 import java.awt.CompositeContext;
51 import java.awt.Font;
52 import java.awt.FontMetrics;
53 import java.awt.GradientPaint;
54 import java.awt.Graphics;
55 import java.awt.Graphics2D;
56 import java.awt.GraphicsConfiguration;
57 import java.awt.Image;
58 import java.awt.Paint;
59 import java.awt.PaintContext;
60 import java.awt.Point;
61 import java.awt.Polygon;
62 import java.awt.Rectangle;
63 import java.awt.RenderingHints;
64 import java.awt.Shape;
65 import java.awt.Stroke;
66 import java.awt.TexturePaint;
67 import java.awt.Toolkit;
68 import java.awt.font.FontRenderContext;
69 import java.awt.font.GlyphVector;
70 import java.awt.font.TextLayout;
71 import java.awt.geom.AffineTransform;
72 import java.awt.geom.Arc2D;
73 import java.awt.geom.Area;
74 import java.awt.geom.Ellipse2D;
75 import java.awt.geom.GeneralPath;
76 import java.awt.geom.Line2D;
77 import java.awt.geom.NoninvertibleTransformException;
78 import java.awt.geom.PathIterator;
79 import java.awt.geom.Point2D;
80 import java.awt.geom.Rectangle2D;
81 import java.awt.geom.RoundRectangle2D;
82 import java.awt.image.AffineTransformOp;
83 import java.awt.image.BufferedImage;
84 import java.awt.image.BufferedImageOp;
85 import java.awt.image.ColorModel;
86 import java.awt.image.DataBuffer;
87 import java.awt.image.DataBufferInt;
88 import java.awt.image.DirectColorModel;
89 import java.awt.image.ImageObserver;
90 import java.awt.image.ImageProducer;
91 import java.awt.image.ImagingOpException;
92 import java.awt.image.MultiPixelPackedSampleModel;
93 import java.awt.image.Raster;
94 import java.awt.image.RenderedImage;
95 import java.awt.image.SampleModel;
96 import java.awt.image.WritableRaster;
97 import java.awt.image.renderable.RenderContext;
98 import java.awt.image.renderable.RenderableImage;
99 import java.text.AttributedCharacterIterator;
100 import java.util.HashMap;
101 import java.util.Map;
102 
103 /**
104  * This is an abstract implementation of Graphics2D on Cairo.
105  *
106  * It should be subclassed for different Cairo contexts.
107  *
108  * Note for subclassers: Apart from the constructor (see comments below),
109  * The following abstract methods must be implemented:
110  *
111  * Graphics create()
112  * GraphicsConfiguration getDeviceConfiguration()
113  * copyArea(int x, int y, int width, int height, int dx, int dy)
114  *
115  * Also, dispose() must be overloaded to free any native datastructures
116  * used by subclass and in addition call super.dispose() to free the
117  * native cairographics2d structure and cairo_t.
118  *
119  * @author Sven de Marothy
120  */
121 public abstract class CairoGraphics2D extends Graphics2D
122 {
123   static
124   {
125     if (true) // GCJ LOCAL
126       {
127         System.loadLibrary("gtkpeer");
128       }
129   }
130 
131   /**
132    * Important: This is a pointer to the native cairographics2d structure
133    *
134    * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
135    */
136   long nativePointer;
137 
138   // Drawing state variables
139   /**
140    * The current paint
141    */
142   Paint paint;
143   boolean customPaint;
144 
145   /**
146    * The current stroke
147    */
148   Stroke stroke;
149 
150   /*
151    * Current foreground and background color.
152    */
153   Color fg, bg;
154 
155   /**
156    * Current clip shape.
157    */
158   Shape clip;
159 
160   /**
161    * Current transform.
162    */
163   AffineTransform transform;
164 
165   /**
166    * Current font.
167    */
168   Font font;
169 
170   /**
171    * The current compositing context, if any.
172    */
173   Composite comp;
174   CompositeContext compCtx;
175 
176   /**
177    * Rendering hint map.
178    */
179   private RenderingHints hints;
180 
181   /**
182    * Status of the anti-alias flag in cairo.
183    */
184   private boolean antialias = false;
185   private boolean ignoreAA = false;
186 
187   /**
188    * Some operations (drawing rather than filling) require that their
189    * coords be shifted to land on 0.5-pixel boundaries, in order to land on
190    * "middle of pixel" coordinates and light up complete pixels.
191    */
192   protected boolean shiftDrawCalls = false;
193 
194   /**
195    * Keep track if the first clip to be set, which is restored on setClip(null);
196    */
197   private boolean firstClip = true;
198   private Shape originalClip;
199 
200   /**
201    * Stroke used for 3DRects
202    */
203   private static BasicStroke draw3DRectStroke = new BasicStroke();
204 
205   static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
206   static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF,
207                                                   0xFF000000);
208 
209   /**
210    * Native constants for interpolation methods.
211    * Note, this corresponds to an enum in native/jni/gtk-peer/cairographics2d.h
212    */
213   public static final int INTERPOLATION_NEAREST         = 0,
214                           INTERPOLATION_BILINEAR        = 1,
215                           INTERPOLATION_BICUBIC         = 5,
216                           ALPHA_INTERPOLATION_SPEED     = 2,
217                           ALPHA_INTERPOLATION_QUALITY   = 3,
218                           ALPHA_INTERPOLATION_DEFAULT   = 4;
219   // TODO: Does ALPHA_INTERPOLATION really correspond to CAIRO_FILTER_FAST/BEST/GOOD?
220 
221   /**
222    * Constructor does nothing.
223    */
CairoGraphics2D()224   public CairoGraphics2D()
225   {
226   }
227 
228   /**
229    * Sets up the default values and allocates the native cairographics2d structure
230    * @param cairo_t_pointer a native pointer to a cairo_t of the context.
231    */
setup(long cairo_t_pointer)232   public void setup(long cairo_t_pointer)
233   {
234     nativePointer = init(cairo_t_pointer);
235     setRenderingHints(new RenderingHints(getDefaultHints()));
236     setFont(new Font("SansSerif", Font.PLAIN, 12));
237     setColor(Color.black);
238     setBackground(Color.white);
239     setPaint(Color.black);
240     setStroke(new BasicStroke());
241     setTransform(new AffineTransform());
242     cairoSetAntialias(nativePointer, antialias);
243   }
244 
245   /**
246    * Same as above, but copies the state of another CairoGraphics2D.
247    */
copy(CairoGraphics2D g, long cairo_t_pointer)248   public void copy(CairoGraphics2D g, long cairo_t_pointer)
249   {
250     nativePointer = init(cairo_t_pointer);
251     paint = g.paint;
252     stroke = g.stroke;
253     setRenderingHints(g.hints);
254 
255     Color foreground;
256 
257     if (g.fg.getAlpha() != -1)
258       foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
259                              g.fg.getAlpha());
260     else
261       foreground = new Color(g.fg.getRGB());
262 
263     if (g.bg != null)
264       {
265         if (g.bg.getAlpha() != -1)
266           bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
267                          g.bg.getAlpha());
268         else
269           bg = new Color(g.bg.getRGB());
270       }
271 
272     firstClip = g.firstClip;
273     originalClip = g.originalClip;
274     clip = g.getClip();
275 
276     if (g.transform == null)
277       transform = null;
278     else
279       transform = new AffineTransform(g.transform);
280 
281     setFont(g.font);
282     setColor(foreground);
283     setBackground(bg);
284     setPaint(paint);
285     setStroke(stroke);
286     setTransformImpl(transform);
287     setClip(clip);
288     setComposite(comp);
289 
290     antialias = !g.antialias;
291     setAntialias(g.antialias);
292   }
293 
294   /**
295    * Generic destructor - call the native dispose() method.
296    */
finalize()297   public void finalize()
298   {
299     dispose();
300   }
301 
302   /**
303    * Disposes the native cairographics2d structure, including the
304    * cairo_t and any gradient stuff, if allocated.
305    * Subclasses should of course overload and call this if
306    * they have additional native structures.
307    */
dispose()308   public void dispose()
309   {
310     disposeNative(nativePointer);
311     nativePointer = 0;
312     if (compCtx != null)
313       compCtx.dispose();
314   }
315 
316   /**
317    * Allocate the cairographics2d structure and set the cairo_t pointer in it.
318    * @param pointer - a cairo_t pointer, casted to a long.
319    */
init(long pointer)320   protected native long init(long pointer);
321 
322   /**
323    * These are declared abstract as there may be context-specific issues.
324    */
create()325   public abstract Graphics create();
326 
getDeviceConfiguration()327   public abstract GraphicsConfiguration getDeviceConfiguration();
328 
copyAreaImpl(int x, int y, int width, int height, int dx, int dy)329   protected abstract void copyAreaImpl(int x, int y, int width, int height,
330                                        int dx, int dy);
331 
332 
333   /**
334    * Find the bounds of this graphics context, in device space.
335    *
336    * @return the bounds in device-space
337    */
getRealBounds()338   protected abstract Rectangle2D getRealBounds();
339 
340   ////// Native Methods ////////////////////////////////////////////////////
341 
342   /**
343    * Dispose of allocate native resouces.
344    */
disposeNative(long pointer)345   public native void disposeNative(long pointer);
346 
347   /**
348    * Draw pixels as an RGBA int matrix
349    * @param w - width
350    * @param h - height
351    * @param stride - stride of the array width
352    * @param i2u - affine transform array
353    */
drawPixels(long pointer, int[] pixels, int w, int h, int stride, double[] i2u, double alpha, int interpolation)354   protected native void drawPixels(long pointer, int[] pixels, int w, int h,
355                                  int stride, double[] i2u, double alpha,
356                                  int interpolation);
357 
setGradient(long pointer, double x1, double y1, double x2, double y2, int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2, boolean cyclic)358   protected native void setGradient(long pointer, double x1, double y1,
359                                   double x2, double y2,
360                                   int r1, int g1, int b1, int a1, int r2,
361                                   int g2, int b2, int a2, boolean cyclic);
362 
setPaintPixels(long pointer, int[] pixels, int w, int h, int stride, boolean repeat, int x, int y)363   protected native void setPaintPixels(long pointer, int[] pixels, int w,
364                                      int h, int stride, boolean repeat,
365                                      int x, int y);
366 
367   /**
368    * Set the current transform matrix
369    */
cairoSetMatrix(long pointer, double[] m)370   protected native void cairoSetMatrix(long pointer, double[] m);
371 
372   /**
373    * Scaling method
374    */
cairoScale(long pointer, double x, double y)375   protected native void cairoScale(long pointer, double x, double y);
376 
377   /**
378    * Set the compositing operator
379    */
cairoSetOperator(long pointer, int cairoOperator)380   protected native void cairoSetOperator(long pointer, int cairoOperator);
381 
382   /**
383    * Sets the current color in RGBA as a 0.0-1.0 double
384    */
cairoSetRGBAColor(long pointer, double red, double green, double blue, double alpha)385   protected native void cairoSetRGBAColor(long pointer, double red, double green,
386                                         double blue, double alpha);
387 
388   /**
389    * Sets the current winding rule in Cairo
390    */
cairoSetFillRule(long pointer, int cairoFillRule)391   protected native void cairoSetFillRule(long pointer, int cairoFillRule);
392 
393   /**
394    * Set the line style, cap, join and miter limit.
395    * Cap and join parameters are in the BasicStroke enumerations.
396    */
cairoSetLine(long pointer, double width, int cap, int join, double miterLimit)397   protected native void cairoSetLine(long pointer, double width, int cap,
398                                    int join, double miterLimit);
399 
400   /**
401    * Set the dash style
402    */
cairoSetDash(long pointer, double[] dashes, int ndash, double offset)403   protected native void cairoSetDash(long pointer, double[] dashes, int ndash,
404                                    double offset);
405 
406   /*
407    * Draws a Glyph Vector
408    */
cairoDrawGlyphVector(long pointer, GdkFontPeer font, float x, float y, int n, int[] codes, float[] positions, long[] fontset)409   protected native void cairoDrawGlyphVector(long pointer, GdkFontPeer font,
410                                    float x, float y, int n,
411                                    int[] codes, float[] positions, long[] fontset);
412 
413   /**
414    * Set the font in cairo.
415    */
cairoSetFont(long pointer, GdkFontPeer font)416   protected native void cairoSetFont(long pointer, GdkFontPeer font);
417 
418   /**
419    * Appends a rectangle to the current path
420    */
cairoRectangle(long pointer, double x, double y, double width, double height)421   protected native void cairoRectangle(long pointer, double x, double y,
422                                      double width, double height);
423 
424   /**
425    * Appends an arc to the current path
426    */
cairoArc(long pointer, double x, double y, double radius, double angle1, double angle2)427   protected native void cairoArc(long pointer, double x, double y,
428                                double radius, double angle1, double angle2);
429 
430   /**
431    * Save / restore a cairo path
432    */
cairoSave(long pointer)433   protected native void cairoSave(long pointer);
cairoRestore(long pointer)434   protected native void cairoRestore(long pointer);
435 
436   /**
437    * New current path
438    */
cairoNewPath(long pointer)439   protected native void cairoNewPath(long pointer);
440 
441   /**
442    * Close current path
443    */
cairoClosePath(long pointer)444   protected native void cairoClosePath(long pointer);
445 
446   /** moveTo */
cairoMoveTo(long pointer, double x, double y)447   protected native void cairoMoveTo(long pointer, double x, double y);
448 
449   /** lineTo */
cairoLineTo(long pointer, double x, double y)450   protected native void cairoLineTo(long pointer, double x, double y);
451 
452   /** Cubic curve-to */
cairoCurveTo(long pointer, double x1, double y1, double x2, double y2, double x3, double y3)453   protected native void cairoCurveTo(long pointer, double x1, double y1,
454                                    double x2, double y2,
455                                    double x3, double y3);
456 
457   /**
458    * Stroke current path
459    */
cairoStroke(long pointer)460   protected native void cairoStroke(long pointer);
461 
462   /**
463    * Fill current path
464    */
cairoFill(long pointer, double alpha)465   protected native void cairoFill(long pointer, double alpha);
466 
467   /**
468    * Clip current path
469    */
cairoClip(long pointer)470   protected native void cairoClip(long pointer);
471 
472   /**
473    * Clear clip
474    */
cairoResetClip(long pointer)475   protected native void cairoResetClip(long pointer);
476 
477   /**
478    * Set antialias.
479    */
cairoSetAntialias(long pointer, boolean aa)480   protected native void cairoSetAntialias(long pointer, boolean aa);
481 
482 
483   ///////////////////////// TRANSFORMS ///////////////////////////////////
484   /**
485    * Set the current transform
486    */
setTransform(AffineTransform tx)487   public void setTransform(AffineTransform tx)
488   {
489     // Transform clip into target space using the old transform.
490     updateClip(transform);
491 
492     // Update the native transform.
493     setTransformImpl(tx);
494 
495     // Transform the clip back into user space using the inverse new transform.
496     try
497       {
498         updateClip(transform.createInverse());
499       }
500     catch (NoninvertibleTransformException ex)
501       {
502         // TODO: How can we deal properly with this?
503         ex.printStackTrace();
504       }
505 
506     if (clip != null)
507       setClip(clip);
508   }
509 
setTransformImpl(AffineTransform tx)510   private void setTransformImpl(AffineTransform tx)
511   {
512     transform = tx;
513     if (transform != null)
514       {
515         double[] m = new double[6];
516         transform.getMatrix(m);
517         cairoSetMatrix(nativePointer, m);
518       }
519   }
520 
transform(AffineTransform tx)521   public void transform(AffineTransform tx)
522   {
523     if (transform == null)
524       transform = new AffineTransform(tx);
525     else
526       transform.concatenate(tx);
527 
528     if (clip != null)
529       {
530         try
531           {
532             AffineTransform clipTransform = tx.createInverse();
533             updateClip(clipTransform);
534           }
535         catch (NoninvertibleTransformException ex)
536           {
537             // TODO: How can we deal properly with this?
538             ex.printStackTrace();
539           }
540       }
541 
542     setTransformImpl(transform);
543   }
544 
rotate(double theta)545   public void rotate(double theta)
546   {
547     transform(AffineTransform.getRotateInstance(theta));
548   }
549 
rotate(double theta, double x, double y)550   public void rotate(double theta, double x, double y)
551   {
552     transform(AffineTransform.getRotateInstance(theta, x, y));
553   }
554 
scale(double sx, double sy)555   public void scale(double sx, double sy)
556   {
557     transform(AffineTransform.getScaleInstance(sx, sy));
558   }
559 
560   /**
561    * Translate the system of the co-ordinates. As translation is a frequent
562    * operation, it is done in an optimised way, unlike scaling and rotating.
563    */
translate(double tx, double ty)564   public void translate(double tx, double ty)
565   {
566     if (transform != null)
567       transform.translate(tx, ty);
568     else
569       transform = AffineTransform.getTranslateInstance(tx, ty);
570 
571     if (clip != null)
572       {
573         // FIXME: this should actuall try to transform the shape
574         // rather than degrade to bounds.
575         if (clip instanceof Rectangle2D)
576           {
577             Rectangle2D r = (Rectangle2D) clip;
578             r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(),
579                       r.getHeight());
580           }
581         else
582           {
583             AffineTransform clipTransform =
584               AffineTransform.getTranslateInstance(-tx, -ty);
585             updateClip(clipTransform);
586           }
587       }
588 
589     setTransformImpl(transform);
590   }
591 
translate(int x, int y)592   public void translate(int x, int y)
593   {
594     translate((double) x, (double) y);
595   }
596 
shear(double shearX, double shearY)597   public void shear(double shearX, double shearY)
598   {
599     transform(AffineTransform.getShearInstance(shearX, shearY));
600   }
601 
602   ///////////////////////// DRAWING STATE ///////////////////////////////////
603 
clip(Shape s)604   public void clip(Shape s)
605   {
606     // Do not touch clip when s == null.
607     if (s == null)
608       {
609         // The spec says this should clear the clip. The reference
610         // implementation throws a NullPointerException instead. I think,
611         // in this case we should conform to the specs, as it shouldn't
612         // affect compatibility.
613         setClip(null);
614         return;
615       }
616 
617     // If the current clip is still null, initialize it.
618     if (clip == null)
619       {
620         clip = getRealBounds();
621       }
622 
623     // This is so common, let's optimize this.
624     if (clip instanceof Rectangle2D && s instanceof Rectangle2D)
625       {
626         Rectangle2D clipRect = (Rectangle2D) clip;
627         Rectangle2D r = (Rectangle2D) s;
628         Rectangle2D.intersect(clipRect, r, clipRect);
629         setClip(clipRect);
630       }
631    else
632      {
633        Area current;
634        if (clip instanceof Area)
635          current = (Area) clip;
636        else
637          current = new Area(clip);
638 
639        Area intersect;
640        if (s instanceof Area)
641          intersect = (Area) s;
642        else
643          intersect = new Area(s);
644 
645        current.intersect(intersect);
646        clip = current;
647        // Call setClip so that the native side gets notified.
648        setClip(clip);
649      }
650   }
651 
getPaint()652   public Paint getPaint()
653   {
654     return paint;
655   }
656 
getTransform()657   public AffineTransform getTransform()
658   {
659     return (AffineTransform) transform.clone();
660   }
661 
setPaint(Paint p)662   public void setPaint(Paint p)
663   {
664     if (p == null)
665       return;
666 
667     paint = p;
668     if (paint instanceof Color)
669       {
670         setColor((Color) paint);
671         customPaint = false;
672       }
673 
674     else if (paint instanceof TexturePaint)
675       {
676         TexturePaint tp = (TexturePaint) paint;
677         BufferedImage img = tp.getImage();
678 
679         // map the image to the anchor rectangle
680         int width = (int) tp.getAnchorRect().getWidth();
681         int height = (int) tp.getAnchorRect().getHeight();
682 
683         double scaleX = width / (double) img.getWidth();
684         double scaleY = height / (double) img.getHeight();
685 
686         AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
687         AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
688         BufferedImage texture = op.filter(img, null);
689         int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
690         setPaintPixels(nativePointer, pixels, width, height, width, true, 0, 0);
691         customPaint = false;
692       }
693 
694     else if (paint instanceof GradientPaint)
695       {
696         GradientPaint gp = (GradientPaint) paint;
697         Point2D p1 = gp.getPoint1();
698         Point2D p2 = gp.getPoint2();
699         Color c1 = gp.getColor1();
700         Color c2 = gp.getColor2();
701         setGradient(nativePointer, p1.getX(), p1.getY(), p2.getX(), p2.getY(),
702                     c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(),
703                     c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(),
704                     gp.isCyclic());
705         customPaint = false;
706       }
707     else
708       {
709         customPaint = true;
710       }
711   }
712 
713   /**
714    * Sets a custom paint
715    *
716    * @param bounds the bounding box, in user space
717    */
setCustomPaint(Rectangle bounds)718   protected void setCustomPaint(Rectangle bounds)
719   {
720     if (paint instanceof Color || paint instanceof TexturePaint
721         || paint instanceof GradientPaint)
722       return;
723 
724     int userX = bounds.x;
725     int userY = bounds.y;
726     int userWidth = bounds.width;
727     int userHeight = bounds.height;
728 
729     // Find bounds in device space
730     Rectangle2D bounds2D = getTransformedBounds(bounds, transform);
731     int deviceX = (int)bounds2D.getX();
732     int deviceY = (int)bounds2D.getY();
733     int deviceWidth = (int)Math.ceil(bounds2D.getWidth());
734     int deviceHeight = (int)Math.ceil(bounds2D.getHeight());
735 
736     // Get raster of the paint background
737     PaintContext pc = paint.createContext(CairoSurface.cairoColorModel,
738                                           new Rectangle(deviceX, deviceY,
739                                                         deviceWidth,
740                                                         deviceHeight),
741                                           bounds,
742                                           transform, hints);
743 
744     Raster raster = pc.getRaster(deviceX, deviceY, deviceWidth,
745                                  deviceHeight);
746 
747     // Clear the transform matrix in Cairo, since the raster returned by the
748     // PaintContext is already in device-space
749     AffineTransform oldTx = new AffineTransform(transform);
750     setTransformImpl(new AffineTransform());
751 
752     // Set pixels in cairo, aligning the top-left of the background image
753     // to the top-left corner in device space
754     if (pc.getColorModel().equals(CairoSurface.cairoColorModel)
755         && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
756       {
757         // Use a fast copy if the paint context can uses a Cairo-compatible
758         // color model
759         setPaintPixels(nativePointer,
760                        (int[])raster.getDataElements(0, 0, deviceWidth,
761                                                      deviceHeight, null),
762                        deviceWidth, deviceHeight, deviceWidth, false,
763                        deviceX, deviceY);
764       }
765 
766     else if (pc.getColorModel().equals(CairoSurface.cairoCM_opaque)
767             && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
768       {
769         // We can also optimize if the context uses a similar color model
770         // but without an alpha channel; we just add the alpha
771         int[] pixels = (int[])raster.getDataElements(0, 0, deviceWidth,
772                                                      deviceHeight, null);
773 
774         for (int i = 0; i < pixels.length; i++)
775           pixels[i] = 0xff000000 | (pixels[i] & 0x00ffffff);
776 
777         setPaintPixels(nativePointer, pixels, deviceWidth, deviceHeight,
778                        deviceWidth, false, deviceX, deviceY);
779       }
780 
781     else
782       {
783         // Fall back on wrapping the raster in a BufferedImage, and
784         // use BufferedImage.getRGB() to do color-model conversion
785         WritableRaster wr = Raster.createWritableRaster(raster.getSampleModel(),
786                                                         new Point(raster.getMinX(),
787                                                                   raster.getMinY()));
788         wr.setRect(raster);
789 
790         BufferedImage img2 = new BufferedImage(pc.getColorModel(), wr,
791                                                pc.getColorModel().isAlphaPremultiplied(),
792                                                null);
793 
794         setPaintPixels(nativePointer,
795                        img2.getRGB(0, 0, deviceWidth, deviceHeight, null, 0,
796                                    deviceWidth),
797                        deviceWidth, deviceHeight, deviceWidth, false,
798                        deviceX, deviceY);
799       }
800 
801     // Restore transform
802     setTransformImpl(oldTx);
803   }
804 
getStroke()805   public Stroke getStroke()
806   {
807     return stroke;
808   }
809 
setStroke(Stroke st)810   public void setStroke(Stroke st)
811   {
812     stroke = st;
813     if (stroke instanceof BasicStroke)
814       {
815         BasicStroke bs = (BasicStroke) stroke;
816         cairoSetLine(nativePointer, bs.getLineWidth(), bs.getEndCap(),
817                      bs.getLineJoin(), bs.getMiterLimit());
818 
819         float[] dashes = bs.getDashArray();
820         if (dashes != null)
821           {
822             double[] double_dashes = new double[dashes.length];
823             for (int i = 0; i < dashes.length; i++)
824               double_dashes[i] = dashes[i];
825 
826             cairoSetDash(nativePointer, double_dashes, double_dashes.length,
827                          (double) bs.getDashPhase());
828           }
829         else
830           cairoSetDash(nativePointer, new double[0], 0, 0.0);
831       }
832   }
833 
834   /**
835    * Utility method to find the bounds of a shape, including the stroke width.
836    *
837    * @param s the shape
838    * @return the bounds of the shape, including stroke width
839    */
findStrokedBounds(Shape s)840   protected Rectangle findStrokedBounds(Shape s)
841   {
842     Rectangle r = s.getBounds();
843 
844     if (stroke instanceof BasicStroke)
845       {
846         int strokeWidth = (int)Math.ceil(((BasicStroke)stroke).getLineWidth());
847         r.x -= strokeWidth / 2;
848         r.y -= strokeWidth / 2;
849         r.height += strokeWidth;
850         r.width += strokeWidth;
851       }
852     else
853       {
854         Shape s2 = stroke.createStrokedShape(s);
855         r = s2.getBounds();
856       }
857 
858     return r;
859   }
860 
setPaintMode()861   public void setPaintMode()
862   {
863     setComposite(AlphaComposite.SrcOver);
864   }
865 
setXORMode(Color c)866   public void setXORMode(Color c)
867   {
868     // FIXME: implement
869   }
870 
setColor(Color c)871   public void setColor(Color c)
872   {
873     if (c == null)
874       c = Color.BLACK;
875 
876     fg = c;
877     paint = c;
878     updateColor();
879   }
880 
881   /**
882    * Set the current fg value as the cairo color.
883    */
updateColor()884   void updateColor()
885   {
886     if (fg == null)
887       fg = Color.BLACK;
888 
889     cairoSetRGBAColor(nativePointer, fg.getRed() / 255.0,
890                       fg.getGreen() / 255.0,fg.getBlue() / 255.0,
891                       fg.getAlpha() / 255.0);
892   }
893 
getColor()894   public Color getColor()
895   {
896     return fg;
897   }
898 
clipRect(int x, int y, int width, int height)899   public void clipRect(int x, int y, int width, int height)
900   {
901     if (clip == null)
902       setClip(new Rectangle(x, y, width, height));
903     else if (clip instanceof Rectangle)
904       {
905         computeIntersection(x, y, width, height, (Rectangle) clip);
906         setClip(clip);
907       }
908     else
909       clip(new Rectangle(x, y, width, height));
910   }
911 
getClip()912   public Shape getClip()
913   {
914     if (clip == null)
915       return null;
916     else if (clip instanceof Rectangle2D)
917       return clip.getBounds2D(); //getClipInDevSpace();
918     else
919       {
920         GeneralPath p = new GeneralPath();
921         PathIterator pi = clip.getPathIterator(null);
922         p.append(pi, false);
923         return p;
924       }
925   }
926 
getClipBounds()927   public Rectangle getClipBounds()
928   {
929     if (clip == null)
930       return null;
931     else
932       return clip.getBounds();
933   }
934 
getClipInDevSpace()935   protected Rectangle2D getClipInDevSpace()
936   {
937     Rectangle2D uclip = clip.getBounds2D();
938     if (transform == null)
939       return uclip;
940     else
941       return getTransformedBounds(clip.getBounds2D(), transform);
942   }
943 
setClip(int x, int y, int width, int height)944   public void setClip(int x, int y, int width, int height)
945   {
946     if( width < 0 || height < 0 )
947       return;
948 
949     setClip(new Rectangle2D.Double(x, y, width, height));
950   }
951 
setClip(Shape s)952   public void setClip(Shape s)
953   {
954     // The first time the clip is set, save it as the original clip
955     // to reset to on s == null. We can rely on this being non-null
956     // because the constructor in subclasses is expected to set the
957     // initial clip properly.
958     if( firstClip )
959       {
960         originalClip = s;
961         firstClip = false;
962       }
963 
964     clip = s;
965     cairoResetClip(nativePointer);
966 
967     if (clip != null)
968       {
969         cairoNewPath(nativePointer);
970         if (clip instanceof Rectangle2D)
971           {
972             Rectangle2D r = (Rectangle2D) clip;
973             cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(),
974                            r.getHeight());
975           }
976         else
977           walkPath(clip.getPathIterator(null), false);
978 
979         cairoClip(nativePointer);
980       }
981   }
982 
setBackground(Color c)983   public void setBackground(Color c)
984   {
985     if (c == null)
986       c = Color.WHITE;
987     bg = c;
988   }
989 
getBackground()990   public Color getBackground()
991   {
992     return bg;
993   }
994 
995   /**
996    * Return the current composite.
997    */
getComposite()998   public Composite getComposite()
999   {
1000     if (comp == null)
1001       return AlphaComposite.SrcOver;
1002     else
1003       return comp;
1004   }
1005 
1006   /**
1007    * Sets the current composite context.
1008    */
setComposite(Composite comp)1009   public void setComposite(Composite comp)
1010   {
1011     if (this.comp == comp)
1012       return;
1013 
1014     this.comp = comp;
1015     if (compCtx != null)
1016       compCtx.dispose();
1017     compCtx = null;
1018 
1019     if (comp instanceof AlphaComposite)
1020       {
1021         AlphaComposite a = (AlphaComposite) comp;
1022         cairoSetOperator(nativePointer, a.getRule());
1023       }
1024 
1025     else
1026       {
1027         cairoSetOperator(nativePointer, AlphaComposite.SRC_OVER);
1028 
1029         if (comp != null)
1030           {
1031             // FIXME: this check is only required "if this Graphics2D
1032             // context is drawing to a Component on the display screen".
1033             SecurityManager sm = System.getSecurityManager();
1034             if (sm != null)
1035               sm.checkPermission(new AWTPermission("readDisplayPixels"));
1036 
1037             compCtx = comp.createContext(getBufferCM(), getNativeCM(), hints);
1038           }
1039       }
1040   }
1041 
1042   /**
1043    * Returns the Colour Model describing the native, raw image data for this
1044    * specific peer.
1045    *
1046    * @return ColorModel the ColorModel of native data in this peer
1047    */
getNativeCM()1048   protected abstract ColorModel getNativeCM();
1049 
1050   /**
1051    * Returns the Color Model describing the buffer that this peer uses
1052    * for custom composites.
1053    *
1054    * @return ColorModel the ColorModel of the composite buffer in this peer.
1055    */
getBufferCM()1056   protected ColorModel getBufferCM()
1057   {
1058     // This may be overridden by some subclasses
1059     return getNativeCM();
1060   }
1061 
1062   ///////////////////////// DRAWING PRIMITIVES ///////////////////////////////////
1063 
draw(Shape s)1064   public void draw(Shape s)
1065   {
1066     if ((stroke != null && ! (stroke instanceof BasicStroke))
1067         || (comp instanceof AlphaComposite && ((AlphaComposite) comp).getAlpha() != 1.0))
1068       {
1069         // Cairo doesn't support stroking with alpha, so we create the stroked
1070         // shape and fill with alpha instead
1071         fill(stroke.createStrokedShape(s));
1072         return;
1073       }
1074 
1075     if (customPaint)
1076       {
1077         Rectangle r = findStrokedBounds(s);
1078         setCustomPaint(r);
1079       }
1080 
1081     setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING)
1082                        .equals(RenderingHints.VALUE_ANTIALIAS_OFF));
1083     createPath(s, true);
1084     cairoStroke(nativePointer);
1085   }
1086 
fill(Shape s)1087   public void fill(Shape s)
1088   {
1089     createPath(s, false);
1090 
1091     if (customPaint)
1092       setCustomPaint(s.getBounds());
1093 
1094     setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING)
1095                        .equals(RenderingHints.VALUE_ANTIALIAS_OFF));
1096     double alpha = 1.0;
1097     if (comp instanceof AlphaComposite)
1098       alpha = ((AlphaComposite) comp).getAlpha();
1099     cairoFill(nativePointer, alpha);
1100   }
1101 
createPath(Shape s, boolean isDraw)1102   private void createPath(Shape s, boolean isDraw)
1103   {
1104     cairoNewPath(nativePointer);
1105 
1106     // Optimize rectangles, since there is a direct Cairo function
1107     if (s instanceof Rectangle2D)
1108       {
1109         Rectangle2D r = (Rectangle2D) s;
1110 
1111         // Pixels need to be shifted in draw operations to ensure that they
1112         // light up entire pixels, but we also need to make sure the rectangle
1113         // does not get distorted by this shifting operation
1114         double x = shiftX(r.getX(),shiftDrawCalls && isDraw);
1115         double y = shiftY(r.getY(), shiftDrawCalls && isDraw);
1116         double w = Math.round(r.getWidth());
1117         double h = Math.round(r.getHeight());
1118         cairoRectangle(nativePointer, x, y, w, h);
1119       }
1120 
1121     // Lines are easy too
1122     else if (s instanceof Line2D)
1123       {
1124         Line2D l = (Line2D) s;
1125         cairoMoveTo(nativePointer, shiftX(l.getX1(), shiftDrawCalls && isDraw),
1126                   shiftY(l.getY1(), shiftDrawCalls && isDraw));
1127         cairoLineTo(nativePointer, shiftX(l.getX2(), shiftDrawCalls && isDraw),
1128                   shiftY(l.getY2(), shiftDrawCalls && isDraw));
1129       }
1130 
1131     // We can optimize ellipses too; however we don't bother optimizing arcs:
1132     // the iterator is fast enough (an ellipse requires 5 steps using the
1133     // iterator, while most arcs are only 2-3)
1134     else if (s instanceof Ellipse2D)
1135       {
1136         Ellipse2D e = (Ellipse2D) s;
1137 
1138         double radius = Math.min(e.getHeight(), e.getWidth()) / 2;
1139 
1140         // Cairo only draws circular shapes, but we can use a stretch to make
1141         // them into ellipses
1142         double xscale = 1, yscale = 1;
1143         if (e.getHeight() != e.getWidth())
1144           {
1145             cairoSave(nativePointer);
1146 
1147             if (e.getHeight() < e.getWidth())
1148               xscale = e.getWidth() / (radius * 2);
1149             else
1150               yscale = e.getHeight() / (radius * 2);
1151 
1152             if (xscale != 1 || yscale != 1)
1153               cairoScale(nativePointer, xscale, yscale);
1154           }
1155 
1156         cairoArc(nativePointer,
1157                  shiftX(e.getCenterX() / xscale, shiftDrawCalls && isDraw),
1158                  shiftY(e.getCenterY() / yscale, shiftDrawCalls && isDraw),
1159                  radius, 0, Math.PI * 2);
1160 
1161         if (xscale != 1 || yscale != 1)
1162           cairoRestore(nativePointer);
1163       }
1164 
1165     // All other shapes are broken down and drawn in steps using the
1166     // PathIterator
1167     else
1168       walkPath(s.getPathIterator(null), shiftDrawCalls && isDraw);
1169   }
1170 
1171   /**
1172    * Note that the rest of the drawing methods go via fill() or draw() for the drawing,
1173    * although subclasses may with to overload these methods where context-specific
1174    * optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int)
1175    */
1176 
clearRect(int x, int y, int width, int height)1177   public void clearRect(int x, int y, int width, int height)
1178   {
1179     if (bg != null)
1180       cairoSetRGBAColor(nativePointer, bg.getRed() / 255.0,
1181                         bg.getGreen() / 255.0, bg.getBlue() / 255.0,
1182                         bg.getAlpha() / 255.0);
1183 
1184     Composite oldcomp = comp;
1185     setComposite(AlphaComposite.Src);
1186     fillRect(x, y, width, height);
1187 
1188     setComposite(oldcomp);
1189     updateColor();
1190   }
1191 
draw3DRect(int x, int y, int width, int height, boolean raised)1192   public void draw3DRect(int x, int y, int width, int height, boolean raised)
1193   {
1194     Stroke tmp = stroke;
1195     setStroke(draw3DRectStroke);
1196     super.draw3DRect(x, y, width, height, raised);
1197     setStroke(tmp);
1198   }
1199 
drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)1200   public void drawArc(int x, int y, int width, int height, int startAngle,
1201                       int arcAngle)
1202   {
1203     draw(new Arc2D.Double((double) x, (double) y, (double) width,
1204                           (double) height, (double) startAngle,
1205                           (double) arcAngle, Arc2D.OPEN));
1206   }
1207 
drawLine(int x1, int y1, int x2, int y2)1208   public void drawLine(int x1, int y1, int x2, int y2)
1209   {
1210     // The coordinates being pairwise identical means one wants
1211     // to draw a single pixel. This is emulated by drawing
1212     // a one pixel sized rectangle.
1213     if (x1 == x2 && y1 == y2)
1214       fill(new Rectangle(x1, y1, 1, 1));
1215     else
1216       draw(new Line2D.Double(x1, y1, x2, y2));
1217   }
1218 
drawRect(int x, int y, int width, int height)1219   public void drawRect(int x, int y, int width, int height)
1220   {
1221     draw(new Rectangle(x, y, width, height));
1222   }
1223 
fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)1224   public void fillArc(int x, int y, int width, int height, int startAngle,
1225                       int arcAngle)
1226   {
1227     fill(new Arc2D.Double((double) x, (double) y, (double) width,
1228                           (double) height, (double) startAngle,
1229                           (double) arcAngle, Arc2D.PIE));
1230   }
1231 
fillRect(int x, int y, int width, int height)1232   public void fillRect(int x, int y, int width, int height)
1233   {
1234     fill (new Rectangle(x, y, width, height));
1235   }
1236 
fillPolygon(int[] xPoints, int[] yPoints, int nPoints)1237   public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1238   {
1239     fill(new Polygon(xPoints, yPoints, nPoints));
1240   }
1241 
drawPolygon(int[] xPoints, int[] yPoints, int nPoints)1242   public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1243   {
1244     draw(new Polygon(xPoints, yPoints, nPoints));
1245   }
1246 
drawPolyline(int[] xPoints, int[] yPoints, int nPoints)1247   public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1248   {
1249     for (int i = 1; i < nPoints; i++)
1250       draw(new Line2D.Double(xPoints[i - 1], yPoints[i - 1],
1251                              xPoints[i], yPoints[i]));
1252   }
1253 
drawOval(int x, int y, int width, int height)1254   public void drawOval(int x, int y, int width, int height)
1255   {
1256     drawArc(x, y, width, height, 0, 360);
1257   }
1258 
drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)1259   public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
1260                             int arcHeight)
1261   {
1262     draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
1263   }
1264 
fillOval(int x, int y, int width, int height)1265   public void fillOval(int x, int y, int width, int height)
1266   {
1267     fillArc(x, y, width, height, 0, 360);
1268   }
1269 
fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)1270   public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
1271                             int arcHeight)
1272   {
1273     fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
1274   }
1275 
1276   /**
1277    * CopyArea - performs clipping to the native surface as a convenience
1278    * (requires getRealBounds). Then calls copyAreaImpl.
1279    */
copyArea(int ox, int oy, int owidth, int oheight, int odx, int ody)1280   public void copyArea(int ox, int oy, int owidth, int oheight,
1281                        int odx, int ody)
1282   {
1283     // FIXME: does this handle a rotation transform properly?
1284     // (the width/height might not be correct)
1285     Point2D pos = transform.transform(new Point2D.Double(ox, oy),
1286                                       (Point2D) null);
1287     Point2D dim = transform.transform(new Point2D.Double(ox + owidth,
1288                                                          oy + oheight),
1289                                       (Point2D) null);
1290     Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody),
1291                                      (Point2D) null);
1292     int x = (int)pos.getX();
1293     int y = (int)pos.getY();
1294     int width = (int)(dim.getX() - pos.getX());
1295     int height = (int)(dim.getY() - pos.getY());
1296     int dx = (int)(p2.getX() - pos.getX());
1297     int dy = (int)(p2.getY() - pos.getY());
1298 
1299     Rectangle2D r = getRealBounds();
1300 
1301     if( width <= 0 || height <= 0 )
1302       return;
1303     // Return if outside the surface
1304     if( x + dx > r.getWidth() || y + dy > r.getHeight() )
1305       return;
1306 
1307     if( x + dx + width < r.getX() || y + dy + height < r.getY() )
1308       return;
1309 
1310     // Clip edges if necessary
1311     if( x + dx < r.getX() ) // left
1312       {
1313         width = x + dx + width;
1314         x = (int)r.getX() - dx;
1315       }
1316 
1317     if( y + dy < r.getY() ) // top
1318       {
1319         height = y + dy + height;
1320         y = (int)r.getY() - dy;
1321       }
1322 
1323     if( x + dx + width >= r.getWidth() ) // right
1324       width = (int)r.getWidth() - dx - x;
1325 
1326     if( y + dy + height >= r.getHeight() ) // bottom
1327       height = (int)r.getHeight() - dy - y;
1328 
1329     copyAreaImpl(x, y, width, height, dx, dy);
1330   }
1331 
1332   ///////////////////////// RENDERING HINTS ///////////////////////////////////
1333 
setRenderingHint(RenderingHints.Key hintKey, Object hintValue)1334   public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
1335   {
1336     hints.put(hintKey, hintValue);
1337 
1338     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1339       || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1340   }
1341 
getRenderingHint(RenderingHints.Key hintKey)1342   public Object getRenderingHint(RenderingHints.Key hintKey)
1343   {
1344     return hints.get(hintKey);
1345   }
1346 
setRenderingHints(Map<?,?> hints)1347   public void setRenderingHints(Map<?,?> hints)
1348   {
1349     this.hints = new RenderingHints(getDefaultHints());
1350     this.hints.putAll(hints);
1351 
1352     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1353       || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1354 
1355     if (compCtx != null)
1356       {
1357         compCtx.dispose();
1358         compCtx = comp.createContext(getNativeCM(), getNativeCM(), this.hints);
1359       }
1360   }
1361 
addRenderingHints(Map hints)1362   public void addRenderingHints(Map hints)
1363   {
1364     this.hints.putAll(hints);
1365   }
1366 
getRenderingHints()1367   public RenderingHints getRenderingHints()
1368   {
1369     return hints;
1370   }
1371 
getInterpolation()1372   private int getInterpolation()
1373   {
1374     if (this.hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1375       return INTERPOLATION_NEAREST;
1376 
1377     else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1378       return INTERPOLATION_BILINEAR;
1379 
1380     else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC))
1381       return INTERPOLATION_BICUBIC;
1382 
1383     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1384       return ALPHA_INTERPOLATION_SPEED;
1385 
1386     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1387       return ALPHA_INTERPOLATION_QUALITY;
1388 
1389     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1390       return ALPHA_INTERPOLATION_DEFAULT;
1391 
1392     // Do bilinear interpolation as default
1393     return INTERPOLATION_BILINEAR;
1394   }
1395 
1396   /**
1397    * Set antialias if needed.  If the ignoreAA flag is set, this method will
1398    * return without doing anything.
1399    *
1400    * @param needAA RenderingHints.VALUE_ANTIALIAS_ON or RenderingHints.VALUE_ANTIALIAS_OFF
1401    */
setAntialias(boolean needAA)1402   private void setAntialias(boolean needAA)
1403   {
1404     if (ignoreAA)
1405       return;
1406 
1407     if (needAA != antialias)
1408       {
1409         antialias = !antialias;
1410         cairoSetAntialias(nativePointer, antialias);
1411       }
1412   }
1413 
1414   ///////////////////////// IMAGE. METHODS ///////////////////////////////////
1415 
drawImage(Image img, AffineTransform xform, Color bgcolor, ImageObserver obs)1416   protected boolean drawImage(Image img, AffineTransform xform,
1417                             Color bgcolor, ImageObserver obs)
1418   {
1419     if (img == null)
1420       return false;
1421 
1422     if (xform == null)
1423       xform = new AffineTransform();
1424 
1425     // In this case, xform is an AffineTransform that transforms bounding
1426     // box of the specified image from image space to user space. However
1427     // when we pass this transform to cairo, cairo will use this transform
1428     // to map "user coordinates" to "pixel" coordinates, which is the
1429     // other way around. Therefore to get the "user -> pixel" transform
1430     // that cairo wants from "image -> user" transform that we currently
1431     // have, we will need to invert the transformation matrix.
1432     AffineTransform invertedXform;
1433 
1434     try
1435       {
1436         invertedXform = xform.createInverse();
1437       }
1438     catch (NoninvertibleTransformException e)
1439       {
1440         throw new ImagingOpException("Unable to invert transform "
1441                                      + xform.toString());
1442       }
1443 
1444     // Unrecognized image - convert to a BufferedImage
1445     // Note - this can get us in trouble when the gdk lock is re-acquired.
1446     // for example by VolatileImage. See ComponentGraphics for how we work
1447     // around this.
1448     img = AsyncImage.realImage(img, obs);
1449     if( !(img instanceof BufferedImage) )
1450       {
1451         ImageProducer source = img.getSource();
1452         if (source == null)
1453           return false;
1454         img = Toolkit.getDefaultToolkit().createImage(source);
1455       }
1456 
1457     BufferedImage b = (BufferedImage) img;
1458     Raster raster;
1459     double[] i2u = new double[6];
1460     int width = b.getWidth();
1461     int height = b.getHeight();
1462 
1463     // If this BufferedImage has a BufferedImageGraphics object,
1464     // use the cached CairoSurface that BIG is drawing onto
1465 
1466     if( BufferedImageGraphics.bufferedImages.get( b ) != null )
1467       raster = BufferedImageGraphics.bufferedImages.get( b );
1468     else
1469       raster = b.getRaster();
1470 
1471     invertedXform.getMatrix(i2u);
1472 
1473     double alpha = 1.0;
1474     if (comp instanceof AlphaComposite)
1475       alpha = ((AlphaComposite) comp).getAlpha();
1476 
1477     if(raster instanceof CairoSurface
1478         && ((CairoSurface)raster).sharedBuffer == true)
1479       {
1480         drawCairoSurface((CairoSurface)raster, xform, alpha, getInterpolation());
1481         updateColor();
1482         return true;
1483       }
1484 
1485     if( bgcolor != null )
1486       {
1487         Color oldColor = bg;
1488         setBackground(bgcolor);
1489 
1490         Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
1491         bounds = getTransformedBounds(bounds, xform);
1492 
1493         clearRect((int)bounds.getX(), (int)bounds.getY(),
1494                   (int)bounds.getWidth(), (int)bounds.getHeight());
1495 
1496         setBackground(oldColor);
1497       }
1498 
1499     int[] pixels = b.getRGB(0, 0, width, height, null, 0, width);
1500     // FIXME: The above method returns data in the standard ARGB colorspace,
1501     // meaning data should NOT be alpha pre-multiplied; however Cairo expects
1502     // data to be premultiplied.
1503 
1504     cairoSave(nativePointer);
1505     Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
1506     bounds = getTransformedBounds(bounds, xform);
1507     cairoRectangle(nativePointer, bounds.getX(), bounds.getY(),
1508                    bounds.getWidth(), bounds.getHeight());
1509     cairoClip(nativePointer);
1510 
1511     drawPixels(nativePointer, pixels, width, height, width, i2u, alpha,
1512                getInterpolation());
1513 
1514     cairoRestore(nativePointer);
1515 
1516     // Cairo seems to lose the current color which must be restored.
1517     updateColor();
1518     return true;
1519   }
1520 
drawRenderedImage(RenderedImage image, AffineTransform xform)1521   public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1522   {
1523     drawRaster(image.getColorModel(), image.getData(), xform, null);
1524   }
1525 
drawRenderableImage(RenderableImage image, AffineTransform xform)1526   public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1527   {
1528     drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1529   }
1530 
drawImage(Image img, AffineTransform xform, ImageObserver obs)1531   public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1532   {
1533     return drawImage(img, xform, null, obs);
1534   }
1535 
drawImage(BufferedImage image, BufferedImageOp op, int x, int y)1536   public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
1537   {
1538     Image filtered = image;
1539     if (op != null)
1540       filtered = op.filter(image, null);
1541     drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null);
1542   }
1543 
drawImage(Image img, int x, int y, ImageObserver observer)1544   public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1545   {
1546     return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null,
1547                      observer);
1548   }
1549 
drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer)1550   public boolean drawImage(Image img, int x, int y, Color bgcolor,
1551                            ImageObserver observer)
1552   {
1553     return drawImage(img, x, y, img.getWidth(observer),
1554                      img.getHeight(observer), bgcolor, observer);
1555   }
1556 
drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer)1557   public boolean drawImage(Image img, int x, int y, int width, int height,
1558                            Color bgcolor, ImageObserver observer)
1559   {
1560     double scaleX = width / (double) img.getWidth(observer);
1561     double scaleY = height / (double) img.getHeight(observer);
1562     if( scaleX == 0 || scaleY == 0 )
1563       return true;
1564 
1565     return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y),
1566                      bgcolor, observer);
1567   }
1568 
drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)1569   public boolean drawImage(Image img, int x, int y, int width, int height,
1570                            ImageObserver observer)
1571   {
1572     return drawImage(img, x, y, width, height, null, observer);
1573   }
1574 
drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer)1575   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1576                            int sx1, int sy1, int sx2, int sy2, Color bgcolor,
1577                            ImageObserver observer)
1578   {
1579     if (img == null)
1580       return false;
1581 
1582     int sourceWidth = sx2 - sx1;
1583     int sourceHeight = sy2 - sy1;
1584 
1585     int destWidth = dx2 - dx1;
1586     int destHeight = dy2 - dy1;
1587 
1588     if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 ||
1589        sourceHeight == 0)
1590       return true;
1591 
1592     double scaleX = destWidth / (double) sourceWidth;
1593     double scaleY = destHeight / (double) sourceHeight;
1594 
1595     // FIXME: Avoid using an AT if possible here - it's at least twice as slow.
1596 
1597     Shape oldClip = getClip();
1598     int cx, cy, cw, ch;
1599     if( dx1 < dx2 )
1600       { cx = dx1; cw = dx2 - dx1; }
1601     else
1602       { cx = dx2; cw = dx1 - dx2; }
1603     if( dy1 < dy2 )
1604       { cy = dy1; ch = dy2 - dy1; }
1605     else
1606       { cy = dy2; ch = dy1 - dy2; }
1607 
1608     clipRect( cx, cy, cw, ch );
1609 
1610     AffineTransform tx = new AffineTransform();
1611     tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY );
1612     tx.scale( scaleX, scaleY );
1613 
1614     boolean retval = drawImage(img, tx, bgcolor, observer);
1615     setClip( oldClip );
1616     return retval;
1617   }
1618 
drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)1619   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1620                            int sx1, int sy1, int sx2, int sy2,
1621                            ImageObserver observer)
1622   {
1623     return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
1624   }
1625 
1626   /**
1627    * Optimized method for drawing a CairoSurface onto this graphics context.
1628    *
1629    * @param surface The surface to draw.
1630    * @param tx The transformation matrix (cannot be null).
1631    * @param alpha The alpha value to paint with ( 0 <= alpha <= 1).
1632    * @param interpolation The interpolation type.
1633    */
drawCairoSurface(CairoSurface surface, AffineTransform tx, double alpha, int interpolation)1634   protected void drawCairoSurface(CairoSurface surface, AffineTransform tx,
1635                                   double alpha, int interpolation)
1636   {
1637     // Find offset required if this surface is a sub-raster, and append offset
1638     // to transformation.
1639     if (surface.getSampleModelTranslateX() != 0
1640         || surface.getSampleModelTranslateY() != 0)
1641       {
1642         Point2D origin = new Point2D.Double(0, 0);
1643         Point2D offset = new Point2D.Double(surface.getSampleModelTranslateX(),
1644                                             surface.getSampleModelTranslateY());
1645 
1646         tx.transform(origin, origin);
1647         tx.transform(offset, offset);
1648 
1649         tx.translate(offset.getX() - origin.getX(),
1650                      offset.getY() - origin.getY());
1651       }
1652 
1653     // Find dimensions of this surface relative to the root parent surface
1654     Rectangle bounds = new Rectangle(-surface.getSampleModelTranslateX(),
1655                                      -surface.getSampleModelTranslateY(),
1656                                      surface.width, surface.height);
1657 
1658     // Clip to the translated image
1659     //   We use direct cairo methods to avoid the overhead of maintaining a
1660     //   java copy of the clip, since we will be reverting it immediately
1661     //   after drawing
1662     Shape newBounds = tx.createTransformedShape(bounds);
1663     cairoSave(nativePointer);
1664     walkPath(newBounds.getPathIterator(null), false);
1665     cairoClip(nativePointer);
1666 
1667     // Draw the surface
1668     try
1669     {
1670       double[] i2u = new double[6];
1671       tx.createInverse().getMatrix(i2u);
1672       surface.nativeDrawSurface(surface.surfacePointer, nativePointer, i2u,
1673                                 alpha, interpolation);
1674     }
1675     catch (NoninvertibleTransformException ex)
1676     {
1677       // This should never happen(?), so we don't need to do anything here.
1678       ;
1679     }
1680 
1681     // Restore clip
1682     cairoRestore(nativePointer);
1683   }
1684 
1685 
1686   ///////////////////////// TEXT METHODS ////////////////////////////////////
1687 
drawString(String str, float x, float y)1688   public void drawString(String str, float x, float y)
1689   {
1690     if (str == null || str.length() == 0)
1691       return;
1692     GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
1693     TextLayout tl = (TextLayout) fontPeer.textLayoutCache.get(str);
1694     if (tl == null)
1695       {
1696         tl = new TextLayout( str, getFont(), getFontRenderContext() );
1697         fontPeer.textLayoutCache.put(str, tl);
1698       }
1699 
1700     // Set antialias to text_antialiasing, and set the ignoreAA flag so that
1701     // the setting doesn't get overridden in a draw() or fill() call.
1702     setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING)
1703                        .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
1704     ignoreAA = true;
1705 
1706     tl.draw(this, x, y);
1707     ignoreAA = false;
1708   }
1709 
drawString(String str, int x, int y)1710   public void drawString(String str, int x, int y)
1711   {
1712     drawString (str, (float) x, (float) y);
1713   }
1714 
drawString(AttributedCharacterIterator ci, int x, int y)1715   public void drawString(AttributedCharacterIterator ci, int x, int y)
1716   {
1717     drawString (ci, (float) x, (float) y);
1718   }
1719 
drawGlyphVector(GlyphVector gv, float x, float y)1720   public void drawGlyphVector(GlyphVector gv, float x, float y)
1721   {
1722     double alpha = 1.0;
1723 
1724     if( gv.getNumGlyphs() <= 0 )
1725       return;
1726 
1727     if (customPaint)
1728       setCustomPaint(gv.getOutline().getBounds());
1729 
1730     if (comp instanceof AlphaComposite)
1731       alpha = ((AlphaComposite) comp).getAlpha();
1732 
1733     setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING)
1734                        .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
1735     ignoreAA = true;
1736 
1737     if (gv instanceof FreetypeGlyphVector && alpha == 1.0
1738         && !((FreetypeGlyphVector)gv).hasTransforms())
1739       {
1740         int n = gv.getNumGlyphs ();
1741         int[] codes = gv.getGlyphCodes (0, n, null);
1742         long[] fontset = ((FreetypeGlyphVector)gv).getGlyphFonts (0, n, null);
1743         float[] positions = gv.getGlyphPositions (0, n, null);
1744 
1745         setFont (gv.getFont ());
1746         GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
1747         synchronized (fontPeer)
1748           {
1749             cairoDrawGlyphVector(nativePointer, fontPeer,
1750                                  x, y, n, codes, positions, fontset);
1751           }
1752       }
1753     else
1754       {
1755         translate(x, y);
1756         fill(gv.getOutline());
1757         translate(-x, -y);
1758       }
1759 
1760     ignoreAA = false;
1761   }
1762 
drawString(AttributedCharacterIterator ci, float x, float y)1763   public void drawString(AttributedCharacterIterator ci, float x, float y)
1764   {
1765     GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci);
1766     drawGlyphVector(gv, x, y);
1767   }
1768 
1769   /**
1770    * Should perhaps be contexct dependent, but this is left for now as an
1771    * overloadable default implementation.
1772    */
getFontRenderContext()1773   public FontRenderContext getFontRenderContext()
1774   {
1775     return new FontRenderContext(transform, true, true);
1776   }
1777 
1778   // Until such time as pango is happy to talk directly to cairo, we
1779   // actually need to redirect some calls from the GtkFontPeer and
1780   // GtkFontMetrics into the drawing kit and ask cairo ourselves.
1781 
getFontMetrics()1782   public FontMetrics getFontMetrics()
1783   {
1784     return getFontMetrics(getFont());
1785   }
1786 
getFontMetrics(Font f)1787   public FontMetrics getFontMetrics(Font f)
1788   {
1789     return ((GdkFontPeer) f.getPeer()).getFontMetrics(f);
1790   }
1791 
setFont(Font f)1792   public void setFont(Font f)
1793   {
1794     // Sun's JDK does not throw NPEs, instead it leaves the current setting
1795     // unchanged. So do we.
1796     if (f == null)
1797       return;
1798 
1799     if (f.getPeer() instanceof GdkFontPeer)
1800       font = f;
1801     else
1802       font =
1803         ((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
1804         .getFont(f.getName(), f.getAttributes());
1805 
1806     GdkFontPeer fontpeer = (GdkFontPeer) getFont().getPeer();
1807     synchronized (fontpeer)
1808       {
1809         cairoSetFont(nativePointer, fontpeer);
1810       }
1811   }
1812 
getFont()1813   public Font getFont()
1814   {
1815     if (font == null)
1816       return new Font("SansSerif", Font.PLAIN, 12);
1817     return font;
1818   }
1819 
1820   /////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
1821 
hit(Rectangle rect, Shape s, boolean onStroke)1822   public boolean hit(Rectangle rect, Shape s, boolean onStroke)
1823   {
1824     if( onStroke )
1825       {
1826         Shape stroked = stroke.createStrokedShape( s );
1827         return stroked.intersects( (double)rect.x, (double)rect.y,
1828                                    (double)rect.width, (double)rect.height );
1829       }
1830     return s.intersects( (double)rect.x, (double)rect.y,
1831                          (double)rect.width, (double)rect.height );
1832   }
1833 
toString()1834   public String toString()
1835   {
1836     return  (getClass().getName()
1837              + "[font=" + getFont().toString()
1838              + ",color=" + fg.toString()
1839              + "]");
1840   }
1841 
1842   ///////////////////////// PRIVATE METHODS ///////////////////////////////////
1843 
1844   /**
1845    * All the drawImage() methods eventually get delegated here if the image
1846    * is not a Cairo surface.
1847    *
1848    * @param bgcolor - if non-null draws the background color before
1849    * drawing the image.
1850    */
drawRaster(ColorModel cm, Raster r, AffineTransform imageToUser, Color bgcolor)1851   private boolean drawRaster(ColorModel cm, Raster r,
1852                              AffineTransform imageToUser, Color bgcolor)
1853   {
1854     if (r == null)
1855       return false;
1856 
1857     SampleModel sm = r.getSampleModel();
1858     DataBuffer db = r.getDataBuffer();
1859 
1860     if (db == null || sm == null)
1861       return false;
1862 
1863     if (cm == null)
1864       cm = ColorModel.getRGBdefault();
1865 
1866     double[] i2u = new double[6];
1867     if (imageToUser != null)
1868       imageToUser.getMatrix(i2u);
1869     else
1870       {
1871         i2u[0] = 1;
1872         i2u[1] = 0;
1873         i2u[2] = 0;
1874         i2u[3] = 1;
1875         i2u[4] = 0;
1876         i2u[5] = 0;
1877       }
1878 
1879     int[] pixels = findSimpleIntegerArray(cm, r);
1880 
1881     if (pixels == null)
1882       {
1883         // FIXME: I don't think this code will work correctly with a non-RGB
1884         // MultiPixelPackedSampleModel. Although this entire method should
1885         // probably be rewritten to better utilize Cairo's different supported
1886         // data formats.
1887         if (sm instanceof MultiPixelPackedSampleModel)
1888           {
1889             pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
1890             for (int i = 0; i < pixels.length; i++)
1891               pixels[i] = cm.getRGB(pixels[i]);
1892           }
1893         else
1894           {
1895             pixels = new int[r.getWidth() * r.getHeight()];
1896             for (int i = 0; i < pixels.length; i++)
1897               pixels[i] = cm.getRGB(db.getElem(i));
1898           }
1899       }
1900 
1901     // Change all transparent pixels in the image to the specified bgcolor,
1902     // or (if there's no alpha) fill in an alpha channel so that it paints
1903     // correctly.
1904     if (cm.hasAlpha())
1905       {
1906         if (bgcolor != null && cm.hasAlpha())
1907           for (int i = 0; i < pixels.length; i++)
1908             {
1909               if (cm.getAlpha(pixels[i]) == 0)
1910                 pixels[i] = bgcolor.getRGB();
1911             }
1912       }
1913     else
1914       for (int i = 0; i < pixels.length; i++)
1915         pixels[i] |= 0xFF000000;
1916 
1917     double alpha = 1.0;
1918     if (comp instanceof AlphaComposite)
1919       alpha = ((AlphaComposite) comp).getAlpha();
1920 
1921     drawPixels(nativePointer, pixels, r.getWidth(), r.getHeight(),
1922                r.getWidth(), i2u, alpha, getInterpolation());
1923 
1924     // Cairo seems to lose the current color which must be restored.
1925     updateColor();
1926 
1927     return true;
1928   }
1929 
1930   /**
1931    * Shifts an x-coordinate by 0.5 in device space.
1932    */
shiftX(double coord, boolean doShift)1933   private double shiftX(double coord, boolean doShift)
1934   {
1935     if (doShift)
1936       {
1937         double shift = 0.5;
1938         if (!transform.isIdentity())
1939           shift /= transform.getScaleX();
1940         return (coord + shift);
1941       }
1942     else
1943       return coord;
1944   }
1945 
1946   /**
1947    * Shifts a y-coordinate by 0.5 in device space.
1948    */
shiftY(double coord, boolean doShift)1949   private double shiftY(double coord, boolean doShift)
1950   {
1951     if (doShift)
1952       {
1953         double shift = 0.5;
1954         if (!transform.isIdentity())
1955           shift /= transform.getScaleY();
1956         return (coord + shift);
1957       }
1958     else
1959       return coord;
1960   }
1961 
1962   /**
1963    * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
1964    */
walkPath(PathIterator p, boolean doShift)1965   private void walkPath(PathIterator p, boolean doShift)
1966   {
1967     double x = 0;
1968     double y = 0;
1969     double[] coords = new double[6];
1970 
1971     cairoSetFillRule(nativePointer, p.getWindingRule());
1972     for (; ! p.isDone(); p.next())
1973       {
1974         int seg = p.currentSegment(coords);
1975         switch (seg)
1976           {
1977           case PathIterator.SEG_MOVETO:
1978             x = shiftX(coords[0], doShift);
1979             y = shiftY(coords[1], doShift);
1980             cairoMoveTo(nativePointer, x, y);
1981             break;
1982           case PathIterator.SEG_LINETO:
1983             x = shiftX(coords[0], doShift);
1984             y = shiftY(coords[1], doShift);
1985             cairoLineTo(nativePointer, x, y);
1986             break;
1987           case PathIterator.SEG_QUADTO:
1988             // splitting a quadratic bezier into a cubic:
1989             // see: http://pfaedit.sourceforge.net/bezier.html
1990             double x1 = x + (2.0 / 3.0) * (shiftX(coords[0], doShift) - x);
1991             double y1 = y + (2.0 / 3.0) * (shiftY(coords[1], doShift) - y);
1992 
1993             double x2 = x1 + (1.0 / 3.0) * (shiftX(coords[2], doShift) - x);
1994             double y2 = y1 + (1.0 / 3.0) * (shiftY(coords[3], doShift) - y);
1995 
1996             x = shiftX(coords[2], doShift);
1997             y = shiftY(coords[3], doShift);
1998             cairoCurveTo(nativePointer, x1, y1, x2, y2, x, y);
1999             break;
2000           case PathIterator.SEG_CUBICTO:
2001             x = shiftX(coords[4], doShift);
2002             y = shiftY(coords[5], doShift);
2003             cairoCurveTo(nativePointer, shiftX(coords[0], doShift),
2004                          shiftY(coords[1], doShift),
2005                          shiftX(coords[2], doShift),
2006                          shiftY(coords[3], doShift), x, y);
2007             break;
2008           case PathIterator.SEG_CLOSE:
2009             cairoClosePath(nativePointer);
2010             break;
2011           }
2012       }
2013   }
2014 
2015   /**
2016    * Used by setRenderingHints()
2017    */
getDefaultHints()2018   private Map<RenderingHints.Key, Object> getDefaultHints()
2019   {
2020     HashMap<RenderingHints.Key, Object> defaultHints =
2021       new HashMap<RenderingHints.Key, Object>();
2022 
2023     defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
2024                      RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
2025 
2026     defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
2027                      RenderingHints.VALUE_STROKE_DEFAULT);
2028 
2029     defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
2030                      RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
2031 
2032     defaultHints.put(RenderingHints.KEY_ANTIALIASING,
2033                      RenderingHints.VALUE_ANTIALIAS_OFF);
2034 
2035     defaultHints.put(RenderingHints.KEY_RENDERING,
2036                      RenderingHints.VALUE_RENDER_DEFAULT);
2037 
2038     return defaultHints;
2039   }
2040 
2041   /**
2042    * Used by drawRaster and GdkPixbufDecoder
2043    */
findSimpleIntegerArray(ColorModel cm, Raster raster)2044   public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
2045   {
2046     if (cm == null || raster == null)
2047       return null;
2048 
2049     if (! cm.getColorSpace().isCS_sRGB())
2050       return null;
2051 
2052     if (! (cm instanceof DirectColorModel))
2053       return null;
2054 
2055     DirectColorModel dcm = (DirectColorModel) cm;
2056 
2057     if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
2058         || dcm.getBlueMask() != 0x000000FF)
2059       return null;
2060 
2061     if (! (raster instanceof WritableRaster))
2062       return null;
2063 
2064     if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
2065       return null;
2066 
2067     if (! (raster.getDataBuffer() instanceof DataBufferInt))
2068       return null;
2069 
2070     DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
2071 
2072     if (db.getNumBanks() != 1)
2073       return null;
2074 
2075     // Finally, we have determined that this is a single bank, [A]RGB-int
2076     // buffer in sRGB space. It's worth checking all this, because it means
2077     // that cairo can paint directly into the data buffer, which is very
2078     // fast compared to all the normal copying and converting.
2079 
2080     return db.getData();
2081   }
2082 
2083   /**
2084    * Helper method to transform the clip. This is called by the various
2085    * transformation-manipulation methods to update the clip (which is in
2086    * userspace) accordingly.
2087    *
2088    * The transform usually is the inverse transform that was applied to the
2089    * graphics object.
2090    *
2091    * @param t the transform to apply to the clip
2092    */
updateClip(AffineTransform t)2093   private void updateClip(AffineTransform t)
2094   {
2095     if (clip == null)
2096       return;
2097 
2098     // If the clip is a rectangle, and the transformation preserves the shape
2099     // (translate/stretch only), then keep the clip as a rectangle
2100     double[] matrix = new double[4];
2101     t.getMatrix(matrix);
2102     if (clip instanceof Rectangle2D && matrix[1] == 0 && matrix[2] == 0)
2103       {
2104         Rectangle2D rect = (Rectangle2D)clip;
2105         double[] origin = new double[] {rect.getX(), rect.getY()};
2106         double[] dimensions = new double[] {rect.getWidth(), rect.getHeight()};
2107         t.transform(origin, 0, origin, 0, 1);
2108         t.deltaTransform(dimensions, 0, dimensions, 0, 1);
2109         rect.setRect(origin[0], origin[1], dimensions[0], dimensions[1]);
2110       }
2111     else
2112       {
2113         if (! (clip instanceof GeneralPath))
2114           clip = new GeneralPath(clip);
2115 
2116         GeneralPath p = (GeneralPath) clip;
2117         p.transform(t);
2118       }
2119   }
2120 
computeIntersection(int x, int y, int w, int h, Rectangle rect)2121   private static Rectangle computeIntersection(int x, int y, int w, int h,
2122                                                Rectangle rect)
2123   {
2124     int x2 = rect.x;
2125     int y2 = rect.y;
2126     int w2 = rect.width;
2127     int h2 = rect.height;
2128 
2129     int dx = (x > x2) ? x : x2;
2130     int dy = (y > y2) ? y : y2;
2131     int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx);
2132     int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy);
2133 
2134     if (dw >= 0 && dh >= 0)
2135       rect.setBounds(dx, dy, dw, dh);
2136     else
2137       rect.setBounds(0, 0, 0, 0);
2138 
2139     return rect;
2140   }
2141 
getTransformedBounds(Rectangle2D bounds, AffineTransform tx)2142   static Rectangle2D getTransformedBounds(Rectangle2D bounds, AffineTransform tx)
2143   {
2144     double x1 = bounds.getX();
2145     double x2 = bounds.getX() + bounds.getWidth();
2146     double x3 = x1;
2147     double x4 = x2;
2148     double y1 = bounds.getY();
2149     double y2 = y1;
2150     double y3 = bounds.getY() + bounds.getHeight();
2151     double y4 = y3;
2152 
2153     double[] points = new double[] {x1, y1, x2, y2, x3, y3, x4, y4};
2154     tx.transform(points, 0, points, 0, 4);
2155 
2156     double minX = points[0];
2157     double maxX = minX;
2158     double minY = points[1];
2159     double maxY = minY;
2160     for (int i = 0; i < 8; i++)
2161       {
2162         if (points[i] < minX)
2163           minX = points[i];
2164         if (points[i] > maxX)
2165           maxX = points[i];
2166         i++;
2167 
2168         if (points[i] < minY)
2169           minY = points[i];
2170         if (points[i] > maxY)
2171           maxY = points[i];
2172       }
2173 
2174     return new Rectangle2D.Double(minX, minY, (maxX - minX), (maxY - minY));
2175   }
2176 }
2177