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