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