Source for gnu.java.awt.java2d.AbstractGraphics2D

   1: /* AbstractGraphics2D.java -- Abstract Graphics2D implementation
   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: package gnu.java.awt.java2d;
  39: 
  40: import java.awt.AWTError;
  41: import java.awt.AlphaComposite;
  42: import java.awt.AWTPermission;
  43: import java.awt.BasicStroke;
  44: import java.awt.Color;
  45: import java.awt.Composite;
  46: import java.awt.CompositeContext;
  47: import java.awt.Font;
  48: import java.awt.FontMetrics;
  49: import java.awt.Graphics;
  50: import java.awt.Graphics2D;
  51: import java.awt.Image;
  52: import java.awt.Paint;
  53: import java.awt.PaintContext;
  54: import java.awt.Point;
  55: import java.awt.Polygon;
  56: import java.awt.Rectangle;
  57: import java.awt.RenderingHints;
  58: import java.awt.Shape;
  59: import java.awt.Stroke;
  60: import java.awt.Toolkit;
  61: import java.awt.RenderingHints.Key;
  62: import java.awt.font.FontRenderContext;
  63: import java.awt.font.GlyphVector;
  64: import java.awt.geom.AffineTransform;
  65: import java.awt.geom.Arc2D;
  66: import java.awt.geom.Area;
  67: import java.awt.geom.Ellipse2D;
  68: import java.awt.geom.GeneralPath;
  69: import java.awt.geom.Line2D;
  70: import java.awt.geom.NoninvertibleTransformException;
  71: import java.awt.geom.RoundRectangle2D;
  72: import java.awt.image.BufferedImage;
  73: import java.awt.image.BufferedImageOp;
  74: import java.awt.image.ColorModel;
  75: import java.awt.image.DataBuffer;
  76: import java.awt.image.ImageObserver;
  77: import java.awt.image.Raster;
  78: import java.awt.image.RenderedImage;
  79: import java.awt.image.SampleModel;
  80: import java.awt.image.WritableRaster;
  81: import java.awt.image.renderable.RenderableImage;
  82: import java.text.AttributedCharacterIterator;
  83: import java.util.HashMap;
  84: import java.util.Map;
  85: 
  86: /**
  87:  * This is a 100% Java implementation of the Java2D rendering pipeline. It is
  88:  * meant as a base class for Graphics2D implementations.
  89:  *
  90:  * <h2>Backend interface</h2>
  91:  * <p>
  92:  * The backend must at the very least provide a Raster which the the rendering
  93:  * pipeline can paint into. This must be implemented in
  94:  * {@link #getDestinationRaster()}. For some backends that might be enough, like
  95:  * when the target surface can be directly access via the raster (like in
  96:  * BufferedImages). Other targets need some way to synchronize the raster with
  97:  * the surface, which can be achieved by implementing the
  98:  * {@link #updateRaster(Raster, int, int, int, int)} method, which always gets
  99:  * called after a chunk of data got painted into the raster.
 100:  * </p>
 101:  * <p>Alternativly the backend can provide a method for filling Shapes by
 102:  * overriding the protected method fillShape(). This can be accomplished
 103:  * by a polygon filling function of the backend. Keep in mind though that
 104:  * Shapes can be quite complex (i.e. non-convex and containing holes, etc)
 105:  * which is not supported by all polygon fillers. Also it must be noted
 106:  * that fillShape() is expected to handle painting and compositing as well as
 107:  * clipping and transformation. If your backend can't support this natively,
 108:  * then you can fallback to the implementation in this class. You'll need
 109:  * to provide a writable Raster then, see above.</p>
 110:  * <p>Another alternative is to implement fillScanline() which only requires
 111:  * the backend to be able to draw horizontal lines in device space,
 112:  * which is usually very cheap.
 113:  * The implementation should still handle painting and compositing,
 114:  * but no more clipping and transformation is required by the backend.</p>
 115:  * <p>The backend is free to provide implementations for the various raw*
 116:  * methods for optimized AWT 1.1 style painting of some primitives. This should
 117:  * accelerate painting of Swing greatly. When doing so, the backend must also
 118:  * keep track of the clip and translation, probably by overriding
 119:  * some clip and translate methods. Don't forget to message super in such a
 120:  * case.</p>
 121:  *
 122:  * <h2>Acceleration options</h2>
 123:  * <p>
 124:  * The fact that it is
 125:  * pure Java makes it a little slow. However, there are several ways of
 126:  * accelerating the rendering pipeline:
 127:  * <ol>
 128:  * <li><em>Optimization hooks for AWT 1.1 - like graphics operations.</em>
 129:  *   The most important methods from the {@link java.awt.Graphics} class
 130:  *   have a corresponding <code>raw*</code> method, which get called when
 131:  *   several optimization conditions are fullfilled. These conditions are
 132:  *   described below. Subclasses can override these methods and delegate
 133:  *   it directly to a native backend.</li>
 134:  * <li><em>Native PaintContexts and CompositeContext.</em> The implementations
 135:  *   for the 3 PaintContexts and AlphaCompositeContext can be accelerated
 136:  *   using native code. These have proved to two of the most performance
 137:  *   critical points in the rendering pipeline and cannot really be done quickly
 138:  *   in plain Java because they involve lots of shuffling around with large
 139:  *   arrays. In fact, you really would want to let the graphics card to the
 140:  *   work, they are made for this.</li>
 141:  * <li>Provide an accelerated implementation for fillShape(). For instance,
 142:  * OpenGL can fill shapes very efficiently. There are some considerations
 143:  * to be made though, see above for details.</li>
 144:  * </ol>
 145:  * </p>
 146:  *
 147:  * @author Roman Kennke (kennke@aicas.com)
 148:  */
 149: public abstract class AbstractGraphics2D
 150:   extends Graphics2D
 151:   implements Cloneable, Pixelizer
 152: {
 153: 
 154:   /**
 155:    * The default font to use on the graphics object.
 156:    */
 157:   private static final Font FONT = new Font("SansSerif", Font.PLAIN, 12);
 158: 
 159:   /**
 160:    * Caches certain shapes to avoid massive creation of such Shapes in
 161:    * the various draw* and fill* methods.
 162:    */
 163:   private static final ThreadLocal<ShapeCache> shapeCache =
 164:     new ThreadLocal<ShapeCache>();
 165: 
 166:   /**
 167:    * The scanline converters by thread.
 168:    */
 169:   private static final ThreadLocal<ScanlineConverter> scanlineConverters =
 170:     new ThreadLocal<ScanlineConverter>();
 171: 
 172:   /**
 173:    * The transformation for this Graphics2D instance
 174:    */
 175:   protected AffineTransform transform;
 176: 
 177:   /**
 178:    * The foreground.
 179:    */
 180:   private Paint paint;
 181: 
 182:   /**
 183:    * The paint context during rendering.
 184:    */
 185:   private PaintContext paintContext;
 186: 
 187:   /**
 188:    * The background.
 189:    */
 190:   private Color background;
 191: 
 192:   /**
 193:    * The current font.
 194:    */
 195:   private Font font;
 196: 
 197:   /**
 198:    * The current composite setting.
 199:    */
 200:   private Composite composite;
 201: 
 202:   /**
 203:    * The current stroke setting.
 204:    */
 205:   private Stroke stroke;
 206: 
 207:   /**
 208:    * The current clip. This clip is in user coordinate space.
 209:    */
 210:   private Shape clip;
 211: 
 212:   /**
 213:    * The rendering hints.
 214:    */
 215:   private RenderingHints renderingHints;
 216: 
 217:   /**
 218:    * The raster of the destination surface. This is where the painting is
 219:    * performed.
 220:    */
 221:   private WritableRaster destinationRaster;
 222: 
 223:   /**
 224:    * Indicates if certain graphics primitives can be rendered in an optimized
 225:    * fashion. This will be the case if the following conditions are met:
 226:    * - The transform may only be a translation, no rotation, shearing or
 227:    *   scaling.
 228:    * - The paint must be a solid color.
 229:    * - The composite must be an AlphaComposite.SrcOver.
 230:    * - The clip must be a Rectangle.
 231:    * - The stroke must be a plain BasicStroke().
 232:    *
 233:    * These conditions represent the standard settings of a new
 234:    * AbstractGraphics2D object and will be the most commonly used setting
 235:    * in Swing rendering and should therefore be optimized as much as possible.
 236:    */
 237:   private boolean isOptimized = true;
 238: 
 239:   private static final BasicStroke STANDARD_STROKE = new BasicStroke();
 240: 
 241:   private static final HashMap STANDARD_HINTS;
 242:   static {
 243:     HashMap hints = new HashMap();
 244:   hints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
 245:             RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
 246:   hints.put(RenderingHints.KEY_ANTIALIASING,
 247:             RenderingHints.VALUE_ANTIALIAS_DEFAULT);
 248:   STANDARD_HINTS = hints;
 249:   }
 250:   /**
 251:    * Creates a new AbstractGraphics2D instance.
 252:    */
 253:   protected AbstractGraphics2D()
 254:   {
 255:     transform = new AffineTransform();
 256:     background = Color.WHITE;
 257:     composite = AlphaComposite.SrcOver;
 258:     stroke = STANDARD_STROKE;
 259:     renderingHints = new RenderingHints(STANDARD_HINTS);
 260:   }
 261: 
 262:   /**
 263:    * Draws the specified shape. The shape is passed through the current stroke
 264:    * and is then forwarded to {@link #fillShape}.
 265:    *
 266:    * @param shape the shape to draw
 267:    */
 268:   public void draw(Shape shape)
 269:   {
 270:     // Stroke the shape.
 271:     Shape strokedShape = stroke.createStrokedShape(shape);
 272:     // Fill the stroked shape.
 273:     fillShape(strokedShape, false);
 274:   }
 275: 
 276: 
 277:   /**
 278:    * Draws the specified image and apply the transform for image space ->
 279:    * user space conversion.
 280:    *
 281:    * This method is implemented to special case RenderableImages and
 282:    * RenderedImages and delegate to
 283:    * {@link #drawRenderableImage(RenderableImage, AffineTransform)} and
 284:    * {@link #drawRenderedImage(RenderedImage, AffineTransform)} accordingly.
 285:    * Other image types are not yet handled.
 286:    *
 287:    * @param image the image to be rendered
 288:    * @param xform the transform from image space to user space
 289:    * @param obs the image observer to be notified
 290:    */
 291:   public boolean drawImage(Image image, AffineTransform xform,
 292:                            ImageObserver obs)
 293:   {
 294:     Rectangle areaOfInterest = new Rectangle(0, 0, image.getWidth(obs),
 295:                                              image.getHeight(obs));
 296:     return drawImageImpl(image, xform, obs, areaOfInterest);
 297:   }
 298: 
 299:   /**
 300:    * Draws the specified image and apply the transform for image space ->
 301:    * user space conversion. This method only draw the part of the image
 302:    * specified by <code>areaOfInterest</code>.
 303:    *
 304:    * This method is implemented to special case RenderableImages and
 305:    * RenderedImages and delegate to
 306:    * {@link #drawRenderableImage(RenderableImage, AffineTransform)} and
 307:    * {@link #drawRenderedImage(RenderedImage, AffineTransform)} accordingly.
 308:    * Other image types are not yet handled.
 309:    *
 310:    * @param image the image to be rendered
 311:    * @param xform the transform from image space to user space
 312:    * @param obs the image observer to be notified
 313:    * @param areaOfInterest the area in image space that is rendered
 314:    */
 315:   private boolean drawImageImpl(Image image, AffineTransform xform,
 316:                              ImageObserver obs, Rectangle areaOfInterest)
 317:   {
 318:     boolean ret;
 319:     if (image == null)
 320:       {
 321:         ret = true;
 322:       }
 323:     else if (image instanceof RenderedImage)
 324:       {
 325:         // FIXME: Handle the ImageObserver.
 326:         drawRenderedImageImpl((RenderedImage) image, xform, areaOfInterest);
 327:         ret = true;
 328:       }
 329:     else if (image instanceof RenderableImage)
 330:       {
 331:         // FIXME: Handle the ImageObserver.
 332:         drawRenderableImageImpl((RenderableImage) image, xform, areaOfInterest);
 333:         ret = true;
 334:       }
 335:     else
 336:       {
 337:         // FIXME: Implement rendering of other Image types.
 338:         ret = false;
 339:       }
 340:     return ret;
 341:   }
 342: 
 343:   /**
 344:    * Renders a BufferedImage and applies the specified BufferedImageOp before
 345:    * to filter the BufferedImage somehow. The resulting BufferedImage is then
 346:    * passed on to {@link #drawRenderedImage(RenderedImage, AffineTransform)}
 347:    * to perform the final rendering.
 348:    *
 349:    * @param image the source buffered image
 350:    * @param op the filter to apply to the buffered image before rendering
 351:    * @param x the x coordinate to render the image to 
 352:    * @param y the y coordinate to render the image to 
 353:    */
 354:   public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
 355:   {
 356:     BufferedImage filtered =
 357:       op.createCompatibleDestImage(image, image.getColorModel());
 358:     AffineTransform t = new AffineTransform();
 359:     t.translate(x, y);
 360:     drawRenderedImage(filtered, t);
 361:   }
 362: 
 363:   /**
 364:    * Renders the specified image to the destination raster. The specified
 365:    * transform is used to convert the image into user space. The transform
 366:    * of this AbstractGraphics2D object is used to transform from user space
 367:    * to device space.
 368:    * 
 369:    * The rendering is performed using the scanline algorithm that performs the
 370:    * rendering of other shapes and a custom Paint implementation, that supplies
 371:    * the pixel values of the rendered image.
 372:    *
 373:    * @param image the image to render to the destination raster
 374:    * @param xform the transform from image space to user space
 375:    */
 376:   public void drawRenderedImage(RenderedImage image, AffineTransform xform)
 377:   {
 378:     Rectangle areaOfInterest = new Rectangle(image.getMinX(),
 379:                                              image.getHeight(),
 380:                                              image.getWidth(),
 381:                                              image.getHeight());
 382:     drawRenderedImageImpl(image, xform, areaOfInterest);
 383:   }
 384: 
 385:   /**
 386:    * Renders the specified image to the destination raster. The specified
 387:    * transform is used to convert the image into user space. The transform
 388:    * of this AbstractGraphics2D object is used to transform from user space
 389:    * to device space. Only the area specified by <code>areaOfInterest</code>
 390:    * is finally rendered to the target.
 391:    * 
 392:    * The rendering is performed using the scanline algorithm that performs the
 393:    * rendering of other shapes and a custom Paint implementation, that supplies
 394:    * the pixel values of the rendered image.
 395:    *
 396:    * @param image the image to render to the destination raster
 397:    * @param xform the transform from image space to user space
 398:    */
 399:   private void drawRenderedImageImpl(RenderedImage image,
 400:                                      AffineTransform xform,
 401:                                      Rectangle areaOfInterest)
 402:   {
 403:     // First we compute the transformation. This is made up of 3 parts:
 404:     // 1. The areaOfInterest -> image space transform.
 405:     // 2. The image space -> user space transform.
 406:     // 3. The user space -> device space transform.
 407:     AffineTransform t = new AffineTransform();
 408:     t.translate(- areaOfInterest.x - image.getMinX(),
 409:                 - areaOfInterest.y - image.getMinY());
 410:     t.concatenate(xform);
 411:     t.concatenate(transform);
 412:     AffineTransform it = null;
 413:     try
 414:       {
 415:         it = t.createInverse();
 416:       }
 417:     catch (NoninvertibleTransformException ex)
 418:       {
 419:         // Ignore -- we return if the transform is not invertible.
 420:       }
 421:     if (it != null)
 422:       {
 423:         // Transform the area of interest into user space.
 424:         GeneralPath aoi = new GeneralPath(areaOfInterest);
 425:         aoi.transform(xform);
 426:         // Render the shape using the standard renderer, but with a temporary
 427:         // ImagePaint.
 428:         ImagePaint p = new ImagePaint(image, it);
 429:         Paint savedPaint = paint;
 430:         try
 431:           {
 432:             paint = p;
 433:             fillShape(aoi, false);
 434:           }
 435:         finally
 436:           {
 437:             paint = savedPaint;
 438:           }
 439:       }
 440:   }
 441: 
 442:   /**
 443:    * Renders a renderable image. This produces a RenderedImage, which is
 444:    * then passed to {@link #drawRenderedImage(RenderedImage, AffineTransform)}
 445:    * to perform the final rendering.
 446:    *
 447:    * @param image the renderable image to be rendered
 448:    * @param xform the transform from image space to user space
 449:    */
 450:   public void drawRenderableImage(RenderableImage image, AffineTransform xform)
 451:   {
 452:     Rectangle areaOfInterest = new Rectangle((int) image.getMinX(),
 453:                                              (int) image.getHeight(),
 454:                                              (int) image.getWidth(),
 455:                                              (int) image.getHeight());
 456:     drawRenderableImageImpl(image, xform, areaOfInterest);
 457:                                                        
 458:   }
 459: 
 460:   /**
 461:    * Renders a renderable image. This produces a RenderedImage, which is
 462:    * then passed to {@link #drawRenderedImage(RenderedImage, AffineTransform)}
 463:    * to perform the final rendering. Only the area of the image specified
 464:    * by <code>areaOfInterest</code> is rendered.
 465:    *
 466:    * @param image the renderable image to be rendered
 467:    * @param xform the transform from image space to user space
 468:    */
 469:   private void drawRenderableImageImpl(RenderableImage image,
 470:                                        AffineTransform xform,
 471:                                        Rectangle areaOfInterest)
 472:   {
 473:     // TODO: Maybe make more clever usage of a RenderContext here.
 474:     RenderedImage rendered = image.createDefaultRendering();
 475:     drawRenderedImageImpl(rendered, xform, areaOfInterest);
 476:   }
 477: 
 478:   /**
 479:    * Draws the specified string at the specified location.
 480:    *
 481:    * @param text the string to draw
 482:    * @param x the x location, relative to the bounding rectangle of the text
 483:    * @param y the y location, relative to the bounding rectangle of the text
 484:    */
 485:   public void drawString(String text, int x, int y)
 486:   {
 487:     if (isOptimized)
 488:       rawDrawString(text, x, y);
 489:     else
 490:       {
 491:         FontRenderContext ctx = getFontRenderContext();
 492:         GlyphVector gv = font.createGlyphVector(ctx, text.toCharArray());
 493:         drawGlyphVector(gv, x, y);
 494:       }
 495:   }
 496: 
 497:   /**
 498:    * Draws the specified string at the specified location.
 499:    *
 500:    * @param text the string to draw
 501:    * @param x the x location, relative to the bounding rectangle of the text
 502:    * @param y the y location, relative to the bounding rectangle of the text
 503:    */
 504:   public void drawString(String text, float x, float y)
 505:   {
 506:     FontRenderContext ctx = getFontRenderContext();
 507:     GlyphVector gv = font.createGlyphVector(ctx, text.toCharArray());
 508:     drawGlyphVector(gv, x, y);
 509:   }
 510: 
 511:   /**
 512:    * Draws the specified string (as AttributedCharacterIterator) at the
 513:    * specified location.
 514:    *
 515:    * @param iterator the string to draw
 516:    * @param x the x location, relative to the bounding rectangle of the text
 517:    * @param y the y location, relative to the bounding rectangle of the text
 518:    */
 519:   public void drawString(AttributedCharacterIterator iterator, int x, int y)
 520:   {
 521:     FontRenderContext ctx = getFontRenderContext();
 522:     GlyphVector gv = font.createGlyphVector(ctx, iterator);
 523:     drawGlyphVector(gv, x, y);
 524:   }
 525: 
 526:   /**
 527:    * Draws the specified string (as AttributedCharacterIterator) at the
 528:    * specified location.
 529:    *
 530:    * @param iterator the string to draw
 531:    * @param x the x location, relative to the bounding rectangle of the text
 532:    * @param y the y location, relative to the bounding rectangle of the text
 533:    */
 534:   public void drawString(AttributedCharacterIterator iterator, float x, float y)
 535:   {
 536:     FontRenderContext ctx = getFontRenderContext();
 537:     GlyphVector gv = font.createGlyphVector(ctx, iterator);
 538:     drawGlyphVector(gv, x, y);
 539:   }
 540: 
 541:   /**
 542:    * Fills the specified shape with the current foreground.
 543:    *
 544:    * @param shape the shape to fill
 545:    */
 546:   public void fill(Shape shape)
 547:   {
 548:     fillShape(shape, false);
 549:   }
 550: 
 551:   public boolean hit(Rectangle rect, Shape text, boolean onStroke)
 552:   {
 553:     // FIXME: Implement this.
 554:     throw new UnsupportedOperationException("Not yet implemented");
 555:   }
 556: 
 557:   /**
 558:    * Sets the composite.
 559:    *
 560:    * @param comp the composite to set
 561:    */
 562:   public void setComposite(Composite comp)
 563:   {
 564:     if (! (comp instanceof AlphaComposite))
 565:       {
 566:         // FIXME: this check is only required "if this Graphics2D
 567:         // context is drawing to a Component on the display screen".
 568:         SecurityManager sm = System.getSecurityManager();
 569:         if (sm != null)
 570:           sm.checkPermission(new AWTPermission("readDisplayPixels"));
 571:       }
 572: 
 573:     composite = comp;
 574:     if (! (comp.equals(AlphaComposite.SrcOver)))
 575:       isOptimized = false;
 576:     else
 577:       updateOptimization();
 578:   }
 579: 
 580:   /**
 581:    * Sets the current foreground.
 582:    *
 583:    * @param p the foreground to set.
 584:    */
 585:   public void setPaint(Paint p)
 586:   {
 587:     if (p != null)
 588:       {
 589:         paint = p;
 590: 
 591:         if (! (paint instanceof Color))
 592:           isOptimized = false;
 593:         else
 594:           {
 595:             updateOptimization();
 596:           }
 597:       }
 598:   }
 599: 
 600:   /**
 601:    * Sets the stroke for this graphics object.
 602:    *
 603:    * @param s the stroke to set
 604:    */
 605:   public void setStroke(Stroke s)
 606:   {
 607:     stroke = s;
 608:     if (! stroke.equals(new BasicStroke()))
 609:       isOptimized = false;
 610:     else
 611:       updateOptimization();
 612:   }
 613: 
 614:   /**
 615:    * Sets the specified rendering hint.
 616:    *
 617:    * @param hintKey the key of the rendering hint
 618:    * @param hintValue the value
 619:    */
 620:   public void setRenderingHint(Key hintKey, Object hintValue)
 621:   {
 622:     renderingHints.put(hintKey, hintValue);
 623:   }
 624: 
 625:   /**
 626:    * Returns the rendering hint for the specified key.
 627:    *
 628:    * @param hintKey the rendering hint key
 629:    *
 630:    * @return the rendering hint for the specified key
 631:    */
 632:   public Object getRenderingHint(Key hintKey)
 633:   {
 634:     return renderingHints.get(hintKey);
 635:   }
 636: 
 637:   /**
 638:    * Sets the specified rendering hints.
 639:    *
 640:    * @param hints the rendering hints to set
 641:    */
 642:   public void setRenderingHints(Map hints)
 643:   {
 644:     renderingHints.clear();
 645:     renderingHints.putAll(hints);
 646:   }
 647: 
 648:   /**
 649:    * Adds the specified rendering hints.
 650:    *
 651:    * @param hints the rendering hints to add
 652:    */
 653:   public void addRenderingHints(Map hints)
 654:   {
 655:     renderingHints.putAll(hints);
 656:   }
 657: 
 658:   /**
 659:    * Returns the current rendering hints.
 660:    *
 661:    * @return the current rendering hints
 662:    */
 663:   public RenderingHints getRenderingHints()
 664:   {
 665:     return (RenderingHints) renderingHints.clone();
 666:   }
 667: 
 668:   /**
 669:    * Translates the coordinate system by (x, y).
 670:    *
 671:    * @param x the translation X coordinate
 672:    * @param y the translation Y coordinate 
 673:    */
 674:   public void translate(int x, int y)
 675:   {
 676:     transform.translate(x, y);
 677: 
 678:     // Update the clip. We special-case rectangular clips here, because they
 679:     // are so common (e.g. in Swing).
 680:     if (clip != null)
 681:       {
 682:         if (clip instanceof Rectangle)
 683:           {
 684:             Rectangle r = (Rectangle) clip;
 685:             r.x -= x;
 686:             r.y -= y;
 687:             setClip(r);
 688:           }
 689:         else
 690:           {
 691:             AffineTransform clipTransform = new AffineTransform();
 692:             clipTransform.translate(-x, -y);
 693:             updateClip(clipTransform);
 694:           }
 695:       }
 696:   }
 697: 
 698:   /**
 699:    * Translates the coordinate system by (tx, ty).
 700:    *
 701:    * @param tx the translation X coordinate
 702:    * @param ty the translation Y coordinate 
 703:    */
 704:   public void translate(double tx, double ty)
 705:   {
 706:     transform.translate(tx, ty);
 707: 
 708:     // Update the clip. We special-case rectangular clips here, because they
 709:     // are so common (e.g. in Swing).
 710:     if (clip != null)
 711:       {
 712:         if (clip instanceof Rectangle)
 713:           {
 714:             Rectangle r = (Rectangle) clip;
 715:             r.x -= tx;
 716:             r.y -= ty;
 717:           }
 718:         else
 719:           {
 720:             AffineTransform clipTransform = new AffineTransform();
 721:             clipTransform.translate(-tx, -ty);
 722:             updateClip(clipTransform);
 723:           }
 724:       }
 725:   }
 726: 
 727:   /**
 728:    * Rotates the coordinate system by <code>theta</code> degrees.
 729:    *
 730:    * @param theta the angle be which to rotate the coordinate system
 731:    */
 732:   public void rotate(double theta)
 733:   {
 734:     transform.rotate(theta);
 735:     if (clip != null)
 736:       {
 737:         AffineTransform clipTransform = new AffineTransform();
 738:         clipTransform.rotate(-theta);
 739:         updateClip(clipTransform);
 740:       }
 741:     updateOptimization();
 742:   }
 743: 
 744:   /**
 745:    * Rotates the coordinate system by <code>theta</code> around the point
 746:    * (x, y).
 747:    *
 748:    * @param theta the angle by which to rotate the coordinate system
 749:    * @param x the point around which to rotate, X coordinate
 750:    * @param y the point around which to rotate, Y coordinate
 751:    */
 752:   public void rotate(double theta, double x, double y)
 753:   {
 754:     transform.rotate(theta, x, y);
 755:     if (clip != null)
 756:       {
 757:         AffineTransform clipTransform = new AffineTransform();
 758:         clipTransform.rotate(-theta, x, y);
 759:         updateClip(clipTransform);
 760:       }
 761:     updateOptimization();
 762:   }
 763: 
 764:   /**
 765:    * Scales the coordinate system by the factors <code>scaleX</code> and
 766:    * <code>scaleY</code>.
 767:    *
 768:    * @param scaleX the factor by which to scale the X axis
 769:    * @param scaleY the factor by which to scale the Y axis
 770:    */
 771:   public void scale(double scaleX, double scaleY)
 772:   {
 773:     transform.scale(scaleX, scaleY);
 774:     if (clip != null)
 775:       {
 776:         AffineTransform clipTransform = new AffineTransform();
 777:         clipTransform.scale(1 / scaleX, 1 / scaleY);
 778:         updateClip(clipTransform);
 779:       }
 780:     updateOptimization();
 781:   }
 782: 
 783:   /**
 784:    * Shears the coordinate system by <code>shearX</code> and
 785:    * <code>shearY</code>.
 786:    *
 787:    * @param shearX the X shearing
 788:    * @param shearY the Y shearing
 789:    */
 790:   public void shear(double shearX, double shearY)
 791:   {
 792:     transform.shear(shearX, shearY);
 793:     if (clip != null)
 794:       {
 795:         AffineTransform clipTransform = new AffineTransform();
 796:         clipTransform.shear(-shearX, -shearY);
 797:         updateClip(clipTransform);
 798:       }
 799:     updateOptimization();
 800:   }
 801: 
 802:   /**
 803:    * Transforms the coordinate system using the specified transform
 804:    * <code>t</code>.
 805:    *
 806:    * @param t the transform
 807:    */
 808:   public void transform(AffineTransform t)
 809:   {
 810:     transform.concatenate(t);
 811:     try
 812:       {
 813:         AffineTransform clipTransform = t.createInverse();
 814:         updateClip(clipTransform);
 815:       }
 816:     catch (NoninvertibleTransformException ex)
 817:       {
 818:         // TODO: How can we deal properly with this?
 819:         ex.printStackTrace();
 820:       }
 821:     updateOptimization();
 822:   }
 823: 
 824:   /**
 825:    * Sets the transformation for this Graphics object.
 826:    *
 827:    * @param t the transformation to set
 828:    */
 829:   public void setTransform(AffineTransform t)
 830:   {
 831:     // Transform clip into target space using the old transform.
 832:     updateClip(transform);
 833:     transform.setTransform(t);
 834:     // Transform the clip back into user space using the inverse new transform.
 835:     try
 836:       {
 837:         updateClip(transform.createInverse());
 838:       }
 839:     catch (NoninvertibleTransformException ex)
 840:       {
 841:         // TODO: How can we deal properly with this?
 842:         ex.printStackTrace();
 843:       }
 844:     updateOptimization();
 845:   }
 846: 
 847:   /**
 848:    * Returns the transformation of this coordinate system.
 849:    *
 850:    * @return the transformation of this coordinate system
 851:    */
 852:   public AffineTransform getTransform()
 853:   {
 854:     return (AffineTransform) transform.clone();
 855:   }
 856: 
 857:   /**
 858:    * Returns the current foreground.
 859:    *
 860:    * @return the current foreground
 861:    */
 862:   public Paint getPaint()
 863:   {
 864:     return paint;
 865:   }
 866: 
 867: 
 868:   /**
 869:    * Returns the current composite.
 870:    *
 871:    * @return the current composite
 872:    */
 873:   public Composite getComposite()
 874:   {
 875:     return composite;
 876:   }
 877: 
 878:   /**
 879:    * Sets the current background.
 880:    *
 881:    * @param color the background to set.
 882:    */
 883:   public void setBackground(Color color)
 884:   {
 885:     background = color;
 886:   }
 887: 
 888:   /**
 889:    * Returns the current background.
 890:    *
 891:    * @return the current background
 892:    */
 893:   public Color getBackground()
 894:   {
 895:     return background;
 896:   }
 897: 
 898:   /**
 899:    * Returns the current stroke.
 900:    *
 901:    * @return the current stroke
 902:    */
 903:   public Stroke getStroke()
 904:   {
 905:     return stroke;
 906:   }
 907: 
 908:   /**
 909:    * Intersects the clip of this graphics object with the specified clip.
 910:    *
 911:    * @param s the clip with which the current clip should be intersected
 912:    */
 913:   public void clip(Shape s)
 914:   {
 915:     // Initialize clip if not already present.
 916:     if (clip == null)
 917:       setClip(s);
 918: 
 919:     // This is so common, let's optimize this. 
 920:     else if (clip instanceof Rectangle && s instanceof Rectangle)
 921:       {
 922:         Rectangle clipRect = (Rectangle) clip;
 923:         Rectangle r = (Rectangle) s;
 924:         computeIntersection(r.x, r.y, r.width, r.height, clipRect);
 925:         // Call setClip so that subclasses get notified.
 926:         setClip(clipRect);
 927:       }
 928:    else
 929:      {
 930:        Area current;
 931:        if (clip instanceof Area)
 932:          current = (Area) clip;
 933:        else
 934:          current = new Area(clip);
 935: 
 936:        Area intersect;
 937:        if (s instanceof Area)
 938:          intersect = (Area) s;
 939:        else
 940:          intersect = new Area(s);
 941: 
 942:        current.intersect(intersect);
 943:        clip = current;
 944:        isOptimized = false;
 945:        // Call setClip so that subclasses get notified.
 946:        setClip(clip);
 947:      }
 948:   }
 949: 
 950:   public FontRenderContext getFontRenderContext()
 951:   {
 952:     return new FontRenderContext(transform, false, true);
 953:   }
 954: 
 955:   /**
 956:    * Draws the specified glyph vector at the specified location.
 957:    *
 958:    * @param gv the glyph vector to draw
 959:    * @param x the location, x coordinate
 960:    * @param y the location, y coordinate
 961:    */
 962:   public void drawGlyphVector(GlyphVector gv, float x, float y)
 963:   {
 964:     translate(x, y);
 965:     fillShape(gv.getOutline(), true);
 966:     translate(-x, -y);
 967:   }
 968: 
 969:   /**
 970:    * Creates a copy of this graphics object.
 971:    *
 972:    * @return a copy of this graphics object
 973:    */
 974:   public Graphics create()
 975:   {
 976:     AbstractGraphics2D copy = (AbstractGraphics2D) clone();
 977:     return copy;
 978:   }
 979: 
 980:   /**
 981:    * Creates and returns a copy of this Graphics object. This should
 982:    * be overridden by subclasses if additional state must be handled when
 983:    * cloning. This is called by {@link #create()}.
 984:    *
 985:    * @return a copy of this Graphics object
 986:    */
 987:   protected Object clone()
 988:   {
 989:     try
 990:       {
 991:         AbstractGraphics2D copy = (AbstractGraphics2D) super.clone();
 992:         // Copy the clip. If it's a Rectangle, preserve that for optimization.
 993:         if (clip instanceof Rectangle)
 994:           copy.clip = new Rectangle((Rectangle) clip);
 995:         else
 996:           copy.clip = new GeneralPath(clip);
 997: 
 998:     copy.renderingHints = new RenderingHints(null);
 999:     copy.renderingHints.putAll(renderingHints);
1000:         copy.transform = new AffineTransform(transform);
1001:         // The remaining state is inmmutable and doesn't need to be copied.
1002:         return copy;
1003:       }
1004:     catch (CloneNotSupportedException ex)
1005:       {
1006:         AWTError err = new AWTError("Unexpected exception while cloning");
1007:         err.initCause(ex);
1008:         throw err;
1009:       }
1010:   }
1011: 
1012:   /**
1013:    * Returns the current foreground.
1014:    */
1015:   public Color getColor()
1016:   {
1017:     Color c = null;
1018:     if (paint instanceof Color)
1019:       c = (Color) paint;
1020:     return c;
1021:   }
1022: 
1023:   /**
1024:    * Sets the current foreground.
1025:    *
1026:    * @param color the foreground to set
1027:    */
1028:   public void setColor(Color color)
1029:   {
1030:     setPaint(color);
1031:   }
1032: 
1033:   public void setPaintMode()
1034:   {
1035:     // FIXME: Implement this.
1036:     throw new UnsupportedOperationException("Not yet implemented");
1037:   }
1038: 
1039:   public void setXORMode(Color color)
1040:   {
1041:     // FIXME: Implement this.
1042:     throw new UnsupportedOperationException("Not yet implemented");
1043:   }
1044: 
1045:   /**
1046:    * Returns the current font.
1047:    *
1048:    * @return the current font
1049:    */
1050:   public Font getFont()
1051:   {
1052:     return font;
1053:   }
1054: 
1055:   /**
1056:    * Sets the font on this graphics object. When <code>f == null</code>, the
1057:    * current setting is not changed.
1058:    *
1059:    * @param f the font to set
1060:    */
1061:   public void setFont(Font f)
1062:   {
1063:     if (f != null)
1064:       font = f;
1065:   }
1066: 
1067:   /**
1068:    * Returns the font metrics for the specified font.
1069:    *
1070:    * @param font the font for which to fetch the font metrics
1071:    *
1072:    * @return the font metrics for the specified font
1073:    */
1074:   public FontMetrics getFontMetrics(Font font)
1075:   {
1076:     return Toolkit.getDefaultToolkit().getFontMetrics(font);
1077:   }
1078: 
1079:   /**
1080:    * Returns the bounds of the current clip.
1081:    *
1082:    * @return the bounds of the current clip
1083:    */
1084:   public Rectangle getClipBounds()
1085:   {
1086:     Rectangle b = null;
1087:     if (clip != null)
1088:       b = clip.getBounds();
1089:     return b;
1090:   }
1091: 
1092:   /**
1093:    * Intersects the current clipping region with the specified rectangle.
1094:    *
1095:    * @param x the x coordinate of the rectangle
1096:    * @param y the y coordinate of the rectangle
1097:    * @param width the width of the rectangle
1098:    * @param height the height of the rectangle
1099:    */
1100:   public void clipRect(int x, int y, int width, int height)
1101:   {
1102:     clip(new Rectangle(x, y, width, height));
1103:   }
1104: 
1105:   /**
1106:    * Sets the clip to the specified rectangle.
1107:    *
1108:    * @param x the x coordinate of the clip rectangle
1109:    * @param y the y coordinate of the clip rectangle
1110:    * @param width the width of the clip rectangle
1111:    * @param height the height of the clip rectangle
1112:    */
1113:   public void setClip(int x, int y, int width, int height)
1114:   {
1115:     setClip(new Rectangle(x, y, width, height));
1116:   }
1117: 
1118:   /**
1119:    * Returns the current clip.
1120:    *
1121:    * @return the current clip
1122:    */
1123:   public Shape getClip()
1124:   {
1125:     return clip;
1126:   }
1127: 
1128:   /**
1129:    * Sets the current clipping area to <code>clip</code>.
1130:    *
1131:    * @param c the clip to set
1132:    */
1133:   public void setClip(Shape c)
1134:   {
1135:     clip = c;
1136:     if (! (clip instanceof Rectangle))
1137:       isOptimized = false;
1138:     else
1139:       updateOptimization();
1140:   }
1141: 
1142:   public void copyArea(int x, int y, int width, int height, int dx, int dy)
1143:   {
1144:     if (isOptimized)
1145:       rawCopyArea(x, y, width, height, dx, dy);
1146:     else
1147:       copyAreaImpl(x, y, width, height, dx, dy);
1148:   }
1149: 
1150:   /**
1151:    * Draws a line from (x1, y1) to (x2, y2).
1152:    *
1153:    * This implementation transforms the coordinates and forwards the call to
1154:    * {@link #rawDrawLine}.
1155:    */
1156:   public void drawLine(int x1, int y1, int x2, int y2)
1157:   {
1158:     if (isOptimized)
1159:       {
1160:         int tx = (int) transform.getTranslateX();
1161:         int ty = (int) transform.getTranslateY();
1162:         rawDrawLine(x1 + tx, y1 + ty, x2 + tx, y2 + ty);
1163:       }
1164:     else
1165:       {
1166:         ShapeCache sc = getShapeCache();
1167:         if (sc.line == null)
1168:           sc.line = new Line2D.Float();
1169:         sc.line.setLine(x1, y1, x2, y2);
1170:         draw(sc.line);
1171:       }
1172:   }
1173: 
1174:   public void drawRect(int x, int y, int w, int h)
1175:   {
1176:     if (isOptimized)
1177:       {
1178:         rawDrawRect(x, y, w, h);
1179:       }
1180:     else
1181:       {
1182:         ShapeCache sc = getShapeCache();
1183:         if (sc.rect == null)
1184:           sc.rect = new Rectangle();
1185:         sc.rect.setBounds(x, y, w, h);
1186:         draw(sc.rect);
1187:       }
1188:   }
1189: 
1190:   /**
1191:    * Fills a rectangle with the current paint.
1192:    *
1193:    * @param x the upper left corner, X coordinate
1194:    * @param y the upper left corner, Y coordinate
1195:    * @param width the width of the rectangle
1196:    * @param height the height of the rectangle
1197:    */
1198:   public void fillRect(int x, int y, int width, int height)
1199:   {
1200:     if (isOptimized)
1201:       {
1202:         rawFillRect(x + (int) transform.getTranslateX(),
1203:                     y + (int) transform.getTranslateY(), width, height);
1204:       }
1205:     else
1206:       {
1207:         ShapeCache sc = getShapeCache();
1208:         if (sc.rect == null)
1209:           sc.rect = new Rectangle();
1210:         sc.rect.setBounds(x, y, width, height);
1211:         fill(sc.rect);
1212:       }
1213:   }
1214: 
1215:   /**
1216:    * Fills a rectangle with the current background color.
1217:    *
1218:    * This implementation temporarily sets the foreground color to the 
1219:    * background and forwards the call to {@link #fillRect(int, int, int, int)}.
1220:    *
1221:    * @param x the upper left corner, X coordinate
1222:    * @param y the upper left corner, Y coordinate
1223:    * @param width the width of the rectangle
1224:    * @param height the height of the rectangle
1225:    */
1226:   public void clearRect(int x, int y, int width, int height)
1227:   {
1228:     if (isOptimized)
1229:       rawClearRect(x, y, width, height);
1230:     else
1231:       {
1232:         Paint savedForeground = getPaint();
1233:         setPaint(getBackground());
1234:         fillRect(x, y, width, height);
1235:         setPaint(savedForeground);
1236:       }
1237:   }
1238: 
1239:   /**
1240:    * Draws a rounded rectangle.
1241:    *
1242:    * @param x the x coordinate of the rectangle
1243:    * @param y the y coordinate of the rectangle
1244:    * @param width the width of the rectangle
1245:    * @param height the height of the rectangle
1246:    * @param arcWidth the width of the arcs
1247:    * @param arcHeight the height of the arcs
1248:    */
1249:   public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
1250:                             int arcHeight)
1251:   {
1252:     ShapeCache sc = getShapeCache();
1253:     if (sc.roundRect == null)
1254:       sc.roundRect = new RoundRectangle2D.Float();
1255:     sc.roundRect.setRoundRect(x, y, width, height, arcWidth, arcHeight);
1256:     draw(sc.roundRect);
1257:   }
1258: 
1259:   /**
1260:    * Fills a rounded rectangle.
1261:    *
1262:    * @param x the x coordinate of the rectangle
1263:    * @param y the y coordinate of the rectangle
1264:    * @param width the width of the rectangle
1265:    * @param height the height of the rectangle
1266:    * @param arcWidth the width of the arcs
1267:    * @param arcHeight the height of the arcs
1268:    */
1269:   public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
1270:                             int arcHeight)
1271:   {
1272:     ShapeCache sc = getShapeCache();
1273:     if (sc.roundRect == null)
1274:       sc.roundRect = new RoundRectangle2D.Float();
1275:     sc.roundRect.setRoundRect(x, y, width, height, arcWidth, arcHeight);
1276:     fill(sc.roundRect);
1277:   }
1278: 
1279:   /**
1280:    * Draws the outline of an oval.
1281:    *
1282:    * @param x the upper left corner of the bounding rectangle of the ellipse
1283:    * @param y the upper left corner of the bounding rectangle of the ellipse
1284:    * @param width the width of the ellipse
1285:    * @param height the height of the ellipse
1286:    */
1287:   public void drawOval(int x, int y, int width, int height)
1288:   {
1289:     ShapeCache sc = getShapeCache();
1290:     if (sc.ellipse == null)
1291:       sc.ellipse = new Ellipse2D.Float();
1292:     sc.ellipse.setFrame(x, y, width, height);
1293:     draw(sc.ellipse);
1294:   }
1295: 
1296:   /**
1297:    * Fills an oval.
1298:    *
1299:    * @param x the upper left corner of the bounding rectangle of the ellipse
1300:    * @param y the upper left corner of the bounding rectangle of the ellipse
1301:    * @param width the width of the ellipse
1302:    * @param height the height of the ellipse
1303:    */
1304:   public void fillOval(int x, int y, int width, int height)
1305:   {
1306:     ShapeCache sc = getShapeCache();
1307:     if (sc.ellipse == null)
1308:       sc.ellipse = new Ellipse2D.Float();
1309:     sc.ellipse.setFrame(x, y, width, height);
1310:     fill(sc.ellipse);
1311:   }
1312: 
1313:   /**
1314:    * Draws an arc.
1315:    */
1316:   public void drawArc(int x, int y, int width, int height, int arcStart,
1317:                       int arcAngle)
1318:   {
1319:     ShapeCache sc = getShapeCache();
1320:     if (sc.arc == null)
1321:       sc.arc = new Arc2D.Float();
1322:     sc.arc.setArc(x, y, width, height, arcStart, arcAngle, Arc2D.OPEN);
1323:     draw(sc.arc);
1324:   }
1325: 
1326:   /**
1327:    * Fills an arc.
1328:    */
1329:   public void fillArc(int x, int y, int width, int height, int arcStart,
1330:                       int arcAngle)
1331:   {
1332:     ShapeCache sc = getShapeCache();
1333:     if (sc.arc == null)
1334:       sc.arc = new Arc2D.Float();
1335:     sc.arc.setArc(x, y, width, height, arcStart, arcAngle, Arc2D.PIE);
1336:     draw(sc.arc);
1337:   }
1338: 
1339:   public void drawPolyline(int[] xPoints, int[] yPoints, int npoints)
1340:   {
1341:     ShapeCache sc = getShapeCache();
1342:     if (sc.polyline == null)
1343:       sc.polyline = new GeneralPath();
1344:     GeneralPath p = sc.polyline;
1345:     p.reset();
1346:     if (npoints > 0)
1347:       p.moveTo(xPoints[0], yPoints[0]);
1348:     for (int i = 1; i < npoints; i++)
1349:       p.lineTo(xPoints[i], yPoints[i]);
1350:     fill(p);
1351:   }
1352: 
1353:   /**
1354:    * Draws the outline of a polygon.
1355:    */
1356:   public void drawPolygon(int[] xPoints, int[] yPoints, int npoints)
1357:   {
1358:     ShapeCache sc = getShapeCache();
1359:     if (sc.polygon == null)
1360:       sc.polygon = new Polygon();
1361:     sc.polygon.reset();
1362:     sc.polygon.xpoints = xPoints;
1363:     sc.polygon.ypoints = yPoints;
1364:     sc.polygon.npoints = npoints;
1365:     draw(sc.polygon);
1366:   }
1367: 
1368:   /**
1369:    * Fills the outline of a polygon.
1370:    */
1371:   public void fillPolygon(int[] xPoints, int[] yPoints, int npoints)
1372:   {
1373:     ShapeCache sc = getShapeCache();
1374:     if (sc.polygon == null)
1375:       sc.polygon = new Polygon();
1376:     sc.polygon.reset();
1377:     sc.polygon.xpoints = xPoints;
1378:     sc.polygon.ypoints = yPoints;
1379:     sc.polygon.npoints = npoints;
1380:     fill(sc.polygon);
1381:   }
1382: 
1383:   /**
1384:    * Draws the specified image at the specified location. This forwards
1385:    * to {@link #drawImage(Image, AffineTransform, ImageObserver)}.
1386:    *
1387:    * @param image the image to render
1388:    * @param x the x location to render to
1389:    * @param y the y location to render to
1390:    * @param observer the image observer to receive notification
1391:    */
1392:   public boolean drawImage(Image image, int x, int y, ImageObserver observer)
1393:   {
1394:     boolean ret;
1395:     if (isOptimized)
1396:       {
1397:         ret = rawDrawImage(image, x + (int) transform.getTranslateX(),
1398:                            y + (int) transform.getTranslateY(), observer);
1399:       }
1400:     else
1401:       {
1402:         AffineTransform t = new AffineTransform();
1403:         t.translate(x, y);
1404:         ret = drawImage(image, t, observer);
1405:       }
1406:     return ret;
1407:   }
1408: 
1409:   /**
1410:    * Draws the specified image at the specified location. The image
1411:    * is scaled to the specified width and height. This forwards
1412:    * to {@link #drawImage(Image, AffineTransform, ImageObserver)}.
1413:    *
1414:    * @param image the image to render
1415:    * @param x the x location to render to
1416:    * @param y the y location to render to
1417:    * @param width the target width of the image
1418:    * @param height the target height of the image
1419:    * @param observer the image observer to receive notification
1420:    */
1421:   public boolean drawImage(Image image, int x, int y, int width, int height,
1422:                            ImageObserver observer)
1423:   {
1424:     AffineTransform t = new AffineTransform();
1425:     t.translate(x, y);
1426:     double scaleX = (double) width / (double) image.getWidth(observer);
1427:     double scaleY =  (double) height / (double) image.getHeight(observer);
1428:     t.scale(scaleX, scaleY);
1429:     return drawImage(image, t, observer);
1430:   }
1431: 
1432:   /**
1433:    * Draws the specified image at the specified location. This forwards
1434:    * to {@link #drawImage(Image, AffineTransform, ImageObserver)}.
1435:    *
1436:    * @param image the image to render
1437:    * @param x the x location to render to
1438:    * @param y the y location to render to
1439:    * @param bgcolor the background color to use for transparent pixels
1440:    * @param observer the image observer to receive notification
1441:    */
1442:   public boolean drawImage(Image image, int x, int y, Color bgcolor,
1443:                            ImageObserver observer)
1444:   {
1445:     AffineTransform t = new AffineTransform();
1446:     t.translate(x, y);
1447:     // TODO: Somehow implement the background option.
1448:     return drawImage(image, t, observer);
1449:   }
1450: 
1451:   /**
1452:    * Draws the specified image at the specified location. The image
1453:    * is scaled to the specified width and height. This forwards
1454:    * to {@link #drawImage(Image, AffineTransform, ImageObserver)}.
1455:    *
1456:    * @param image the image to render
1457:    * @param x the x location to render to
1458:    * @param y the y location to render to
1459:    * @param width the target width of the image
1460:    * @param height the target height of the image
1461:    * @param bgcolor the background color to use for transparent pixels
1462:    * @param observer the image observer to receive notification
1463:    */
1464:   public boolean drawImage(Image image, int x, int y, int width, int height,
1465:                            Color bgcolor, ImageObserver observer)
1466:   {
1467:     AffineTransform t = new AffineTransform();
1468:     t.translate(x, y);
1469:     double scaleX = (double) image.getWidth(observer) / (double) width;
1470:     double scaleY = (double) image.getHeight(observer) / (double) height;
1471:     t.scale(scaleX, scaleY);
1472:     // TODO: Somehow implement the background option.
1473:     return drawImage(image, t, observer);
1474:   }
1475: 
1476:   /**
1477:    * Draws an image fragment to a rectangular area of the target.
1478:    *
1479:    * @param image the image to render
1480:    * @param dx1 the first corner of the destination rectangle
1481:    * @param dy1 the first corner of the destination rectangle
1482:    * @param dx2 the second corner of the destination rectangle
1483:    * @param dy2 the second corner of the destination rectangle
1484:    * @param sx1 the first corner of the source rectangle
1485:    * @param sy1 the first corner of the source rectangle
1486:    * @param sx2 the second corner of the source rectangle
1487:    * @param sy2 the second corner of the source rectangle
1488:    * @param observer the image observer to be notified
1489:    */
1490:   public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
1491:                            int sx1, int sy1, int sx2, int sy2,
1492:                            ImageObserver observer)
1493:   {
1494:     int sx = Math.min(sx1, sx1);
1495:     int sy = Math.min(sy1, sy2);
1496:     int sw = Math.abs(sx1 - sx2);
1497:     int sh = Math.abs(sy1 - sy2);
1498:     int dx = Math.min(dx1, dx1);
1499:     int dy = Math.min(dy1, dy2);
1500:     int dw = Math.abs(dx1 - dx2);
1501:     int dh = Math.abs(dy1 - dy2);
1502:     
1503:     AffineTransform t = new AffineTransform();
1504:     t.translate(sx - dx, sy - dy);
1505:     double scaleX = (double) sw / (double) dw;
1506:     double scaleY = (double) sh / (double) dh;
1507:     t.scale(scaleX, scaleY);
1508:     Rectangle areaOfInterest = new Rectangle(sx, sy, sw, sh);
1509:     return drawImageImpl(image, t, observer, areaOfInterest);
1510:   }
1511: 
1512:   /**
1513:    * Draws an image fragment to a rectangular area of the target.
1514:    *
1515:    * @param image the image to render
1516:    * @param dx1 the first corner of the destination rectangle
1517:    * @param dy1 the first corner of the destination rectangle
1518:    * @param dx2 the second corner of the destination rectangle
1519:    * @param dy2 the second corner of the destination rectangle
1520:    * @param sx1 the first corner of the source rectangle
1521:    * @param sy1 the first corner of the source rectangle
1522:    * @param sx2 the second corner of the source rectangle
1523:    * @param sy2 the second corner of the source rectangle
1524:    * @param bgcolor the background color to use for transparent pixels
1525:    * @param observer the image observer to be notified
1526:    */
1527:   public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
1528:                            int sx1, int sy1, int sx2, int sy2, Color bgcolor,
1529:                            ImageObserver observer)
1530:   {
1531:     // FIXME: Do something with bgcolor.
1532:     return drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
1533:   }
1534: 
1535:   /**
1536:    * Disposes this graphics object.
1537:    */
1538:   public void dispose()
1539:   {
1540:     // Nothing special to do here.
1541:   }
1542: 
1543:   /**
1544:    * Fills the specified shape. Override this if your backend can efficiently
1545:    * fill shapes. This is possible on many systems via a polygon fill
1546:    * method or something similar. But keep in mind that Shapes can be quite
1547:    * complex (non-convex, with holes etc), which is not necessarily supported
1548:    * by all polygon fillers. Also note that you must perform clipping
1549:    * before filling the shape.
1550:    *
1551:    * @param s the shape to fill
1552:    * @param isFont <code>true</code> if the shape is a font outline
1553:    */
1554:   protected void fillShape(Shape s, boolean isFont)
1555:   {
1556:     // Determine if we need to antialias stuff.
1557:     boolean antialias = false;
1558:     if (isFont)
1559:       {
1560:         Object v = renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING);
1561:         // We default to antialiasing for text rendering.
1562:         antialias = (v == RenderingHints.VALUE_TEXT_ANTIALIAS_ON
1563:                      || v == RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
1564:       }
1565:     else
1566:       {
1567:         Object v = renderingHints.get(RenderingHints.KEY_ANTIALIASING);
1568:         antialias = (v == RenderingHints.VALUE_ANTIALIAS_ON);
1569:       }
1570:     ScanlineConverter sc = getScanlineConverter();
1571:     int resolution = 0;
1572:     if (antialias)
1573:       {
1574:         // Adjust resolution according to rendering hints.
1575:         resolution = 2;
1576:       }
1577:     sc.renderShape(this, s, clip, transform, resolution, renderingHints);
1578:   }
1579: 
1580:   /**
1581:    * Returns the color model of this Graphics object.
1582:    *
1583:    * @return the color model of this Graphics object
1584:    */
1585:   protected abstract ColorModel getColorModel();
1586: 
1587:   /**
1588:    * Returns the bounds of the target.
1589:    *
1590:    * @return the bounds of the target
1591:    */
1592:   protected Rectangle getDeviceBounds()
1593:   {
1594:     return destinationRaster.getBounds();
1595:   }
1596: 
1597:   /**
1598:    * Draws a line in optimization mode. The implementation should respect the
1599:    * clip and translation. It can assume that the clip is a rectangle and that
1600:    * the transform is only a translating transform.
1601:    *
1602:    * @param x0 the starting point, X coordinate
1603:    * @param y0 the starting point, Y coordinate
1604:    * @param x1 the end point, X coordinate 
1605:    * @param y1 the end point, Y coordinate
1606:    */
1607:   protected void rawDrawLine(int x0, int y0, int x1, int y1)
1608:   {
1609:     ShapeCache sc = getShapeCache();
1610:     if (sc.line == null)
1611:       sc.line = new Line2D.Float();
1612:     sc.line.setLine(x0, y0, x1, y1);
1613:     draw(sc.line);
1614:   }
1615: 
1616:   protected void rawDrawRect(int x, int y, int w, int h)
1617:   {
1618:     ShapeCache sc = getShapeCache();
1619:     if (sc.rect == null)
1620:       sc.rect = new Rectangle();
1621:     sc.rect.setBounds(x, y, w, h);
1622:     draw(sc.rect);
1623:   }
1624: 
1625:   /**
1626:    * Draws a string in optimization mode. The implementation should respect the
1627:    * clip and translation. It can assume that the clip is a rectangle and that
1628:    * the transform is only a translating transform.
1629:    *
1630:    * @param text the string to be drawn
1631:    * @param x the start of the baseline, X coordinate
1632:    * @param y the start of the baseline, Y coordinate
1633:    */
1634:   protected void rawDrawString(String text, int x, int y)
1635:   {
1636:     FontRenderContext ctx = getFontRenderContext();
1637:     GlyphVector gv = font.createGlyphVector(ctx, text.toCharArray());
1638:     drawGlyphVector(gv, x, y);
1639:   }
1640: 
1641:   /**
1642:    * Clears a rectangle in optimization mode. The implementation should respect the
1643:    * clip and translation. It can assume that the clip is a rectangle and that
1644:    * the transform is only a translating transform.
1645:    *
1646:    * @param x the upper left corner, X coordinate
1647:    * @param y the upper left corner, Y coordinate
1648:    * @param w the width
1649:    * @param h the height
1650:    */
1651:   protected void rawClearRect(int x, int y, int w, int h)
1652:   {
1653:     Paint savedForeground = getPaint();
1654:     setPaint(getBackground());
1655:     rawFillRect(x, y, w, h);
1656:     setPaint(savedForeground);
1657:   }
1658: 
1659:   /**
1660:    * Fills a rectangle in optimization mode. The implementation should respect
1661:    * the clip but can assume that it is a rectangle.
1662:    *
1663:    * @param x the upper left corner, X coordinate
1664:    * @param y the upper left corner, Y coordinate
1665:    * @param w the width
1666:    * @param h the height
1667:    */
1668:   protected void rawFillRect(int x, int y, int w, int h)
1669:   {
1670:     ShapeCache sc = getShapeCache();
1671:     if (sc.rect == null)
1672:       sc.rect = new Rectangle();
1673:     sc.rect.setBounds(x, y, w, h);
1674:     fill(sc.rect);
1675:   }
1676: 
1677:   /**
1678:    * Draws an image in optimization mode. The implementation should respect
1679:    * the clip but can assume that it is a rectangle.
1680:    *
1681:    * @param image the image to be painted
1682:    * @param x the location, X coordinate
1683:    * @param y the location, Y coordinate
1684:    * @param obs the image observer to be notified
1685:    *
1686:    * @return <code>true</code> when the image is painted completely,
1687:    *         <code>false</code> if it is still rendered
1688:    */
1689:   protected boolean rawDrawImage(Image image, int x, int y, ImageObserver obs)
1690:   {
1691:     AffineTransform t = new AffineTransform();
1692:     t.translate(x, y);
1693:     return drawImage(image, t, obs);
1694:   }
1695: 
1696:   /**
1697:    * Copies a rectangular region to another location.
1698:    *
1699:    * @param x the upper left corner, X coordinate
1700:    * @param y the upper left corner, Y coordinate
1701:    * @param w the width
1702:    * @param h the height
1703:    * @param dx
1704:    * @param dy
1705:    */
1706:   protected void rawCopyArea(int x, int y, int w, int h, int dx, int dy)
1707:   {
1708:     copyAreaImpl(x, y, w, h, dx, dy);
1709:   }
1710: 
1711:   // Private implementation methods.
1712: 
1713:   /**
1714:    * Copies a rectangular area of the target raster to a different location.
1715:    */
1716:   private void copyAreaImpl(int x, int y, int w, int h, int dx, int dy)
1717:   {
1718:     // FIXME: Implement this properly.
1719:     throw new UnsupportedOperationException("Not implemented yet.");
1720:   }
1721: 
1722:   /**
1723:    * Paints a scanline between x0 and x1. Override this when your backend
1724:    * can efficiently draw/fill horizontal lines.
1725:    *
1726:    * @param x0 the left offset
1727:    * @param x1 the right offset
1728:    * @param y the scanline
1729:    */
1730:   public void renderScanline(int y, ScanlineCoverage c)
1731:   {
1732:     PaintContext pCtx = paintContext;
1733:     int x0 = c.getMinX();
1734:     int x1 = c.getMaxX();
1735:     Raster paintRaster = pCtx.getRaster(x0, y, x1 - x0, 1);
1736: 
1737:     // Do the anti aliasing thing.
1738:     float coverageAlpha = 0;
1739:     float maxCoverage = c.getMaxCoverage();
1740:     ColorModel cm = pCtx.getColorModel();
1741:     DataBuffer db = paintRaster.getDataBuffer();
1742:     Point loc = new Point(paintRaster.getMinX(), paintRaster.getMinY());
1743:     SampleModel sm = paintRaster.getSampleModel();
1744:     WritableRaster writeRaster = Raster.createWritableRaster(sm, db, loc);
1745:     WritableRaster alphaRaster = cm.getAlphaRaster(writeRaster);
1746:     int pixel;
1747:     ScanlineCoverage.Iterator iter = c.iterate();
1748:     while (iter.hasNext())
1749:       {
1750:         ScanlineCoverage.Range range = iter.next();
1751:         coverageAlpha = range.getCoverage() / maxCoverage;
1752:         if (coverageAlpha < 1.0)
1753:           {
1754:             for (int x = range.getXPos(); x < range.getXPosEnd(); x++)
1755:               {
1756:                 pixel = alphaRaster.getSample(x, y, 0);
1757:                 pixel = (int) (pixel * coverageAlpha);
1758:                 alphaRaster.setSample(x, y, 0, pixel);
1759:               }
1760:           }
1761:       }
1762:     ColorModel paintColorModel = pCtx.getColorModel();
1763:     CompositeContext cCtx = composite.createContext(paintColorModel,
1764:                                                     getColorModel(),
1765:                                                     renderingHints);
1766:     WritableRaster targetChild = destinationRaster.createWritableTranslatedChild(-x0,- y);
1767:     cCtx.compose(paintRaster, targetChild, targetChild);
1768:     updateRaster(destinationRaster, x0, y, x1 - x0, 1);
1769:     cCtx.dispose();
1770:   }
1771: 
1772: 
1773:   /**
1774:    * Initializes this graphics object. This must be called by subclasses in
1775:    * order to correctly initialize the state of this object.
1776:    */
1777:   protected void init()
1778:   {
1779:     setPaint(Color.BLACK);
1780:     setFont(FONT);
1781:     isOptimized = true;
1782:   }
1783: 
1784:   /**
1785:    * Returns a WritableRaster that is used by this class to perform the
1786:    * rendering in. It is not necessary that the target surface immediately
1787:    * reflects changes in the raster. Updates to the raster are notified via
1788:    * {@link #updateRaster}.
1789:    *
1790:    * @return the destination raster
1791:    */
1792:   protected WritableRaster getDestinationRaster()
1793:   {
1794:     // TODO: Ideally we would fetch the xdrawable's surface pixels for
1795:     // initialization of the raster.
1796:     Rectangle db = getDeviceBounds();
1797:     if (destinationRaster == null)
1798:       {
1799:         int[] bandMasks = new int[]{ 0xFF0000, 0xFF00, 0xFF };
1800:         destinationRaster = Raster.createPackedRaster(DataBuffer.TYPE_INT,
1801:                                                       db.width, db.height,
1802:                                                       bandMasks, null);
1803:         // Initialize raster with white.
1804:         int x0 = destinationRaster.getMinX();
1805:         int x1 = destinationRaster.getWidth() + x0;
1806:         int y0 = destinationRaster.getMinY();
1807:         int y1 = destinationRaster.getHeight() + y0;
1808:         int numBands = destinationRaster.getNumBands();
1809:         for (int y = y0; y < y1; y++)
1810:           {
1811:             for (int x = x0; x < x1; x++)
1812:               {
1813:                 for (int b = 0; b < numBands; b++)
1814:                   destinationRaster.setSample(x, y, b, 255);
1815:               }
1816:           }
1817:       }
1818:     return destinationRaster;
1819:   }
1820: 
1821:   /**
1822:    * Notifies the backend that the raster has changed in the specified
1823:    * rectangular area. The raster that is provided in this method is always
1824:    * the same as the one returned in {@link #getDestinationRaster}.
1825:    * Backends that reflect changes to this raster directly don't need to do
1826:    * anything here.
1827:    *
1828:    * @param raster the updated raster, identical to the raster returned
1829:    *        by {@link #getDestinationRaster()}
1830:    * @param x the upper left corner of the updated region, X coordinate
1831:    * @param y the upper lef corner of the updated region, Y coordinate
1832:    * @param w the width of the updated region
1833:    * @param h the height of the updated region
1834:    */
1835:   protected void updateRaster(Raster raster, int x, int y, int w, int h)
1836:   {
1837:     // Nothing to do here. Backends that need to update their surface
1838:     // to reflect the change should override this method.
1839:   }
1840: 
1841:   // Some helper methods.
1842: 
1843:   /**
1844:    * Helper method to check and update the optimization conditions.
1845:    */
1846:   private void updateOptimization()
1847:   {
1848:     int transformType = transform.getType();
1849:     boolean optimizedTransform = false;
1850:     if (transformType == AffineTransform.TYPE_IDENTITY
1851:         || transformType == AffineTransform.TYPE_TRANSLATION)
1852:       optimizedTransform = true;
1853: 
1854:     boolean optimizedClip = (clip == null || clip instanceof Rectangle);
1855:     isOptimized = optimizedClip
1856:                   && optimizedTransform && paint instanceof Color
1857:                   && composite == AlphaComposite.SrcOver
1858:                   && stroke.equals(new BasicStroke());
1859:   }
1860: 
1861:   /**
1862:    * Calculates the intersection of two rectangles. The result is stored
1863:    * in <code>rect</code>. This is basically the same
1864:    * like {@link Rectangle#intersection(Rectangle)}, only that it does not
1865:    * create new Rectangle instances. The tradeoff is that you loose any data in
1866:    * <code>rect</code>.
1867:    *
1868:    * @param x upper-left x coodinate of first rectangle
1869:    * @param y upper-left y coodinate of first rectangle
1870:    * @param w width of first rectangle
1871:    * @param h height of first rectangle
1872:    * @param rect a Rectangle object of the second rectangle
1873:    *
1874:    * @throws NullPointerException if rect is null
1875:    *
1876:    * @return a rectangle corresponding to the intersection of the
1877:    *         two rectangles. An empty rectangle is returned if the rectangles
1878:    *         do not overlap
1879:    */
1880:   private static Rectangle computeIntersection(int x, int y, int w, int h,
1881:                                                Rectangle rect)
1882:   {
1883:     int x2 = rect.x;
1884:     int y2 = rect.y;
1885:     int w2 = rect.width;
1886:     int h2 = rect.height;
1887: 
1888:     int dx = (x > x2) ? x : x2;
1889:     int dy = (y > y2) ? y : y2;
1890:     int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx);
1891:     int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy);
1892: 
1893:     if (dw >= 0 && dh >= 0)
1894:       rect.setBounds(dx, dy, dw, dh);
1895:     else
1896:       rect.setBounds(0, 0, 0, 0);
1897: 
1898:     return rect;
1899:   }
1900: 
1901:   /**
1902:    * Helper method to transform the clip. This is called by the various
1903:    * transformation-manipulation methods to update the clip (which is in
1904:    * userspace) accordingly.
1905:    *
1906:    * The transform usually is the inverse transform that was applied to the
1907:    * graphics object.
1908:    *
1909:    * @param t the transform to apply to the clip
1910:    */
1911:   private void updateClip(AffineTransform t)
1912:   {
1913:     if (! (clip instanceof GeneralPath))
1914:       clip = new GeneralPath(clip);
1915: 
1916:     GeneralPath p = (GeneralPath) clip;
1917:     p.transform(t);
1918:   }
1919: 
1920:   /**
1921:    * Returns the ShapeCache for the calling thread.
1922:    *
1923:    * @return the ShapeCache for the calling thread
1924:    */
1925:   private ShapeCache getShapeCache()
1926:   {
1927:     ShapeCache sc = shapeCache.get();
1928:     if (sc == null)
1929:       {
1930:         sc = new ShapeCache();
1931:         shapeCache.set(sc);
1932:       }
1933:     return sc;
1934:   }
1935: 
1936:   /**
1937:    * Returns the scanline converter for this thread.
1938:    *
1939:    * @return the scanline converter for this thread
1940:    */
1941:   private ScanlineConverter getScanlineConverter()
1942:   {
1943:     ScanlineConverter sc = scanlineConverters.get();
1944:     if (sc == null)
1945:       {
1946:         sc = new ScanlineConverter();
1947:         scanlineConverters.set(sc);
1948:       }
1949:     return sc;
1950:   }
1951: 
1952: }