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>(); hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT)292 hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, 293 RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_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 */ AbstractGraphics2D()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 */ draw(Shape shape)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 */ drawImage(Image image, AffineTransform xform, ImageObserver obs)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 */ drawImageImpl(Image image, AffineTransform xform, ImageObserver obs, Rectangle areaOfInterest)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 */ drawImage(BufferedImage image, BufferedImageOp op, int x, int y)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 */ drawRenderedImage(RenderedImage image, AffineTransform xform)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 */ drawRenderedImageImpl(RenderedImage image, AffineTransform xform, Rectangle areaOfInterest)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 */ drawRenderableImage(RenderableImage image, AffineTransform xform)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 */ drawRenderableImageImpl(RenderableImage image, AffineTransform xform, Rectangle areaOfInterest)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 */ drawString(String text, int x, int y)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 */ drawString(String text, float x, float y)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 */ drawString(AttributedCharacterIterator iterator, int x, int y)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 */ drawString(AttributedCharacterIterator iterator, float x, float y)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 */ fill(Shape shape)607 public void fill(Shape shape) 608 { 609 fillShape(shape, false); 610 } 611 hit(Rectangle rect, Shape text, boolean onStroke)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 */ setComposite(Composite comp)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 */ setPaint(Paint p)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 */ setStroke(Stroke s)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 */ setRenderingHint(Key hintKey, Object hintValue)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 */ getRenderingHint(Key hintKey)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 */ setRenderingHints(Map hints)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 */ addRenderingHints(Map hints)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 */ getRenderingHints()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 */ translate(int x, int y)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 */ translate(double tx, double ty)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 */ rotate(double theta)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 */ rotate(double theta, double x, double y)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 */ scale(double scaleX, double scaleY)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 */ shear(double shearX, double shearY)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 */ transform(AffineTransform t)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 */ setTransform(AffineTransform t)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 */ getTransform()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 */ getPaint()938 public Paint getPaint() 939 { 940 return paint; 941 } 942 943 944 /** 945 * Returns the current composite. 946 * 947 * @return the current composite 948 */ getComposite()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 */ setBackground(Color color)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 */ getBackground()969 public Color getBackground() 970 { 971 return background; 972 } 973 974 /** 975 * Returns the current stroke. 976 * 977 * @return the current stroke 978 */ getStroke()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 */ clip(Shape s)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 getFontRenderContext()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 */ drawGlyphVector(GlyphVector gv, float x, float y)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 */ create()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 */ clone()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 */ getColor()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 */ setColor(Color color)1109 public void setColor(Color color) 1110 { 1111 this.setPaint(color); 1112 } 1113 setPaintMode()1114 public void setPaintMode() 1115 { 1116 // FIXME: Implement this. 1117 throw new UnsupportedOperationException("Not yet implemented"); 1118 } 1119 setXORMode(Color color)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 */ getFont()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 */ setFont(Font f)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 */ getFontMetrics(Font font)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 */ getClipBounds()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 */ clipRect(int x, int y, int width, int height)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 */ setClip(int x, int y, int width, int height)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 */ getClip()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 */ setClip(Shape c)1214 public void setClip(Shape c) 1215 { 1216 clip = c; 1217 if (! (clip instanceof Rectangle)) 1218 isOptimized = false; 1219 else 1220 updateOptimization(); 1221 } 1222 copyArea(int x, int y, int width, int height, int dx, int dy)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 */ drawLine(int x1, int y1, int x2, int y2)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 drawRect(int x, int y, int w, int h)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 */ fillRect(int x, int y, int width, int height)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 */ clearRect(int x, int y, int width, int height)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 */ drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)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 */ fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)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 */ drawOval(int x, int y, int width, int height)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 */ fillOval(int x, int y, int width, int height)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 */ drawArc(int x, int y, int width, int height, int arcStart, int arcAngle)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 */ fillArc(int x, int y, int width, int height, int arcStart, int arcAngle)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 drawPolyline(int[] xPoints, int[] yPoints, int npoints)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 */ drawPolygon(int[] xPoints, int[] yPoints, int npoints)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 */ fillPolygon(int[] xPoints, int[] yPoints, int npoints)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 */ drawImage(Image image, int x, int y, ImageObserver observer)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 */ drawImage(Image image, int x, int y, int width, int height, ImageObserver observer)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 */ drawImage(Image image, int x, int y, Color bgcolor, ImageObserver observer)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 */ drawImage(Image image, int x, int y, int width, int height, Color bgcolor, ImageObserver observer)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 */ drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)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 */ drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer)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 */ dispose()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 */ fillShape(Shape s, boolean isFont)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 */ getColorModel()1680 protected abstract ColorModel getColorModel(); 1681 1682 /** 1683 * Returns the bounds of the target. 1684 * 1685 * @return the bounds of the target 1686 */ getDeviceBounds()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 */ rawDrawLine(int x0, int y0, int x1, int y1)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 rawDrawRect(int x, int y, int w, int h)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 */ rawClearRect(int x, int y, int w, int h)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 */ rawFillRect(int x, int y, int w, int h)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 */ rawDrawImage(Image image, int x, int y, ImageObserver obs)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 */ rawCopyArea(int x, int y, int w, int h, int dx, int dy)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 */ copyAreaImpl(int x, int y, int w, int h, int dx, int dy)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 */ renderScanline(int y, ScanlineCoverage c)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 */ init()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 */ getDestinationRaster()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 */ updateRaster(Raster raster, int x, int y, int w, int h)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 */ updateOptimization()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 */ computeIntersection(int x, int y, int w, int h, Rectangle rect)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 */ updateClip(AffineTransform t)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 */ getScanlineConverter()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 */ freeScanlineConverter(ScanlineConverter sc)2026 private void freeScanlineConverter(ScanlineConverter sc) 2027 { 2028 synchronized (scanlineConverters) 2029 { 2030 scanlineConverters.addLast(sc); 2031 } 2032 } 2033 getPaintContext()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 */ prepareImage(Image image, int w, int h)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 } 2095