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.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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 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:    */
 320:   protected native long init(long pointer);
 321: 
 322:   /**
 323:    * These are declared abstract as there may be context-specific issues.
 324:    */
 325:   public abstract Graphics create();
 326: 
 327:   public abstract GraphicsConfiguration getDeviceConfiguration();
 328: 
 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:    */
 338:   protected abstract Rectangle2D getRealBounds();
 339: 
 340:   ////// Native Methods ////////////////////////////////////////////////////
 341: 
 342:   /**
 343:    * Dispose of allocate native resouces.
 344:    */
 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:    */
 354:   protected native void drawPixels(long pointer, int[] pixels, int w, int h,
 355:                                  int stride, double[] i2u, double alpha,
 356:                                  int interpolation);
 357: 
 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: 
 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:    */
 370:   protected native void cairoSetMatrix(long pointer, double[] m);
 371: 
 372:   /**
 373:    * Scaling method
 374:    */
 375:   protected native void cairoScale(long pointer, double x, double y);
 376: 
 377:   /**
 378:    * Set the compositing operator
 379:    */
 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:    */
 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:    */
 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:    */
 397:   protected native void cairoSetLine(long pointer, double width, int cap,
 398:                                    int join, double miterLimit);
 399: 
 400:   /**
 401:    * Set the dash style
 402:    */
 403:   protected native void cairoSetDash(long pointer, double[] dashes, int ndash,
 404:                                    double offset);
 405: 
 406:   /*
 407:    * Draws a Glyph Vector
 408:    */
 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:    */
 416:   protected native void cairoSetFont(long pointer, GdkFontPeer font);
 417: 
 418:   /**
 419:    * Appends a rectangle to the current path
 420:    */
 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:    */
 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:    */
 433:   protected native void cairoSave(long pointer);
 434:   protected native void cairoRestore(long pointer);
 435: 
 436:   /**
 437:    * New current path
 438:    */
 439:   protected native void cairoNewPath(long pointer);
 440: 
 441:   /**
 442:    * Close current path
 443:    */
 444:   protected native void cairoClosePath(long pointer);
 445: 
 446:   /** moveTo */
 447:   protected native void cairoMoveTo(long pointer, double x, double y);
 448: 
 449:   /** lineTo */
 450:   protected native void cairoLineTo(long pointer, double x, double y);
 451: 
 452:   /** Cubic curve-to */
 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:    */
 460:   protected native void cairoStroke(long pointer);
 461: 
 462:   /**
 463:    * Fill current path
 464:    */
 465:   protected native void cairoFill(long pointer, double alpha);
 466: 
 467:   /**
 468:    * Clip current path
 469:    */
 470:   protected native void cairoClip(long pointer);
 471: 
 472:   /**
 473:    * Clear clip
 474:    */
 475:   protected native void cairoResetClip(long pointer);
 476: 
 477:   /**
 478:    * Set antialias.
 479:    */
 480:   protected native void cairoSetAntialias(long pointer, boolean aa);
 481: 
 482: 
 483:   ///////////////////////// TRANSFORMS ///////////////////////////////////
 484:   /**
 485:    * Set the current transform
 486:    */
 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: 
 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: 
 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: 
 545:   public void rotate(double theta)
 546:   {
 547:     transform(AffineTransform.getRotateInstance(theta));
 548:   }
 549: 
 550:   public void rotate(double theta, double x, double y)
 551:   {
 552:     transform(AffineTransform.getRotateInstance(theta, x, y));
 553:   }
 554: 
 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:    */
 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: 
 592:   public void translate(int x, int y)
 593:   {
 594:     translate((double) x, (double) y);
 595:   }
 596: 
 597:   public void shear(double shearX, double shearY)
 598:   {
 599:     transform(AffineTransform.getShearInstance(shearX, shearY));
 600:   }
 601: 
 602:   ///////////////////////// DRAWING STATE ///////////////////////////////////
 603: 
 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: 
 652:   public Paint getPaint()
 653:   {
 654:     return paint;
 655:   }
 656: 
 657:   public AffineTransform getTransform()
 658:   {
 659:     return (AffineTransform) transform.clone();
 660:   }
 661: 
 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:    */
 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: 
 805:   public Stroke getStroke()
 806:   {
 807:     return stroke;
 808:   }
 809: 
 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:    */
 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: 
 861:   public void setPaintMode()
 862:   {
 863:     setComposite(AlphaComposite.SrcOver);
 864:   }
 865: 
 866:   public void setXORMode(Color c)
 867:   {
 868:     // FIXME: implement
 869:   }
 870: 
 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:    */
 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: 
 894:   public Color getColor()
 895:   {
 896:     return fg;
 897:   }
 898: 
 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: 
 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: 
 927:   public Rectangle getClipBounds()
 928:   {
 929:     if (clip == null)
 930:       return null;
 931:     else
 932:       return clip.getBounds();
 933:   }
 934: 
 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: 
 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: 
 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: 
 983:   public void setBackground(Color c)
 984:   {
 985:     if (c == null)
 986:       c = Color.WHITE;
 987:     bg = c;
 988:   }
 989: 
 990:   public Color getBackground()
 991:   {
 992:     return bg;
 993:   }
 994: 
 995:   /**
 996:    * Return the current composite.
 997:    */
 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:    */
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:    */
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:    */
1056:   protected ColorModel getBufferCM()
1057:   {
1058:     // This may be overridden by some subclasses
1059:     return getNativeCM();
1060:   }
1061: 
1062:   ///////////////////////// DRAWING PRIMITIVES ///////////////////////////////////
1063: 
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: 
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: 
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: 
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: 
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: 
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: 
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: 
1219:   public void drawRect(int x, int y, int width, int height)
1220:   {
1221:     draw(new Rectangle(x, y, width, height));
1222:   }
1223: 
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: 
1232:   public void fillRect(int x, int y, int width, int height)
1233:   {
1234:     fill (new Rectangle(x, y, width, height));
1235:   }
1236: 
1237:   public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1238:   {
1239:     fill(new Polygon(xPoints, yPoints, nPoints));
1240:   }
1241: 
1242:   public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1243:   {
1244:     draw(new Polygon(xPoints, yPoints, nPoints));
1245:   }
1246: 
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: 
1254:   public void drawOval(int x, int y, int width, int height)
1255:   {
1256:     drawArc(x, y, width, height, 0, 360);
1257:   }
1258: 
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: 
1265:   public void fillOval(int x, int y, int width, int height)
1266:   {
1267:     fillArc(x, y, width, height, 0, 360);
1268:   }
1269: 
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:    */
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: 
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: 
1342:   public Object getRenderingHint(RenderingHints.Key hintKey)
1343:   {
1344:     return hints.get(hintKey);
1345:   }
1346: 
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: 
1362:   public void addRenderingHints(Map hints)
1363:   {
1364:     this.hints.putAll(hints);
1365:   }
1366: 
1367:   public RenderingHints getRenderingHints()
1368:   {
1369:     return hints;
1370:   }
1371: 
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:    */
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: 
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: 
1521:   public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1522:   {
1523:     drawRaster(image.getColorModel(), image.getData(), xform, null);
1524:   }
1525: 
1526:   public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1527:   {
1528:     drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1529:   }
1530: 
1531:   public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1532:   {
1533:     return drawImage(img, xform, null, obs);
1534:   }
1535: 
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: 
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: 
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: 
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: 
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: 
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: 
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:    */
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: 
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: 
1710:   public void drawString(String str, int x, int y)
1711:   {
1712:     drawString (str, (float) x, (float) y);
1713:   }
1714: 
1715:   public void drawString(AttributedCharacterIterator ci, int x, int y)
1716:   {
1717:     drawString (ci, (float) x, (float) y);
1718:   }
1719: 
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: 
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:    */
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: 
1782:   public FontMetrics getFontMetrics()
1783:   {
1784:     return getFontMetrics(getFont());
1785:   }
1786: 
1787:   public FontMetrics getFontMetrics(Font f)
1788:   {
1789:     return ((GdkFontPeer) f.getPeer()).getFontMetrics(f);
1790:   }
1791: 
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: 
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: 
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: 
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:    */
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:    */
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:    */
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:    */
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:    */
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:    */
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:    */
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: 
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: 
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: }