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