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