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