OSDN Git Service

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