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