Source for gnu.java.awt.peer.gtk.CairoGraphics2D

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