# HG changeset patch
# User flar
# Date 1292361929 28800
# Node ID 4d6f9aaa2600eec36725ab7cd47cb4b652755723
# Parent  0eeac8ca33e329aab7a9ad0fcfb3a321cd1abd06
6766342: Improve performance of Ductus rasterizer
Reviewed-by: jgodinez, prr

diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 make/sun/awt/mapfile-vers
--- openjdk.orig/jdk/make/sun/awt/mapfile-vers	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/make/sun/awt/mapfile-vers	Tue Dec 14 13:25:29 2010 -0800
@@ -118,6 +118,8 @@
 		Java_sun_java2d_loops_GraphicsPrimitiveMgr_registerNativeLoops;
 		Java_sun_java2d_loops_MaskBlit_MaskBlit;
 		Java_sun_java2d_loops_MaskFill_MaskFill;
+		Java_sun_java2d_loops_MaskFill_FillAAPgram;
+		Java_sun_java2d_loops_MaskFill_DrawAAPgram;
 		Java_sun_java2d_loops_TransformHelper_Transform;
 		Java_sun_java2d_pipe_Region_initIDs;
 		Java_sun_java2d_pipe_SpanClipRenderer_initIDs;
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 make/sun/awt/mapfile-vers-linux
--- openjdk.orig/jdk/make/sun/awt/mapfile-vers-linux	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/make/sun/awt/mapfile-vers-linux	Tue Dec 14 13:25:29 2010 -0800
@@ -115,6 +115,8 @@
 		Java_sun_java2d_loops_GraphicsPrimitiveMgr_registerNativeLoops;
 		Java_sun_java2d_loops_MaskBlit_MaskBlit;
 		Java_sun_java2d_loops_MaskFill_MaskFill;
+		Java_sun_java2d_loops_MaskFill_FillAAPgram;
+		Java_sun_java2d_loops_MaskFill_DrawAAPgram;
                 Java_sun_java2d_pipe_BufferedRenderPipe_fillSpans;
 		Java_sun_java2d_pipe_SpanClipRenderer_initIDs;
 		sun_awt_image_GifImageDecoder_initIDs;
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/classes/sun/dc/DuctusRenderingEngine.java
--- openjdk.orig/jdk/src/share/classes/sun/dc/DuctusRenderingEngine.java	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/classes/sun/dc/DuctusRenderingEngine.java	Tue Dec 14 13:25:29 2010 -0800
@@ -635,6 +635,88 @@
         return r;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public AATileGenerator getAATileGenerator(double x, double y,
+                                              double dx1, double dy1,
+                                              double dx2, double dy2,
+                                              double lw1, double lw2,
+                                              Region clip,
+                                              int bbox[])
+    {
+        // REMIND: Deal with large coordinates!
+        double ldx1, ldy1, ldx2, ldy2;
+        boolean innerpgram = (lw1 > 0 && lw2 > 0);
+
+        if (innerpgram) {
+            ldx1 = dx1 * lw1;
+            ldy1 = dy1 * lw1;
+            ldx2 = dx2 * lw2;
+            ldy2 = dy2 * lw2;
+            x -= (ldx1 + ldx2) / 2.0;
+            y -= (ldy1 + ldy2) / 2.0;
+            dx1 += ldx1;
+            dy1 += ldy1;
+            dx2 += ldx2;
+            dy2 += ldy2;
+            if (lw1 > 1 && lw2 > 1) {
+                // Inner parallelogram was entirely consumed by stroke...
+                innerpgram = false;
+            }
+        } else {
+            ldx1 = ldy1 = ldx2 = ldy2 = 0;
+        }
+
+        Rasterizer r = getRasterizer();
+
+        r.setUsage(Rasterizer.EOFILL);
+
+        r.beginPath();
+        r.beginSubpath((float) x, (float) y);
+        r.appendLine((float) (x+dx1), (float) (y+dy1));
+        r.appendLine((float) (x+dx1+dx2), (float) (y+dy1+dy2));
+        r.appendLine((float) (x+dx2), (float) (y+dy2));
+        r.closedSubpath();
+        if (innerpgram) {
+            x += ldx1 + ldx2;
+            y += ldy1 + ldy2;
+            dx1 -= 2.0 * ldx1;
+            dy1 -= 2.0 * ldy1;
+            dx2 -= 2.0 * ldx2;
+            dy2 -= 2.0 * ldy2;
+            r.beginSubpath((float) x, (float) y);
+            r.appendLine((float) (x+dx1), (float) (y+dy1));
+            r.appendLine((float) (x+dx1+dx2), (float) (y+dy1+dy2));
+            r.appendLine((float) (x+dx2), (float) (y+dy2));
+            r.closedSubpath();
+        }
+
+        try {
+            r.endPath();
+            r.getAlphaBox(bbox);
+            clip.clipBoxToBounds(bbox);
+            if (bbox[0] >= bbox[2] || bbox[1] >= bbox[3]) {
+                dropRasterizer(r);
+                return null;
+            }
+            r.setOutputArea(bbox[0], bbox[1],
+                            bbox[2] - bbox[0],
+                            bbox[3] - bbox[1]);
+        } catch (PRException e) {
+            /*
+             * This exeption is thrown from the native part of the Ductus
+             * (only in case of a debug build) to indicate that some
+             * segments of the path have very large coordinates.
+             * See 4485298 for more info.
+             */
+            System.err.println("DuctusRenderingEngine.getAATileGenerator: "+e);
+        }
+
+        return r;
+    }
+
     private void feedConsumer(PathConsumer consumer, PathIterator pi) {
         try {
             consumer.beginPath();
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/classes/sun/java2d/SurfaceData.java
--- openjdk.orig/jdk/src/share/classes/sun/java2d/SurfaceData.java	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/classes/sun/java2d/SurfaceData.java	Tue Dec 14 13:25:29 2010 -0800
@@ -367,16 +367,17 @@
     public static final TextPipe aaTextRenderer;
     public static final TextPipe lcdTextRenderer;
 
-    protected static final CompositePipe colorPipe;
+    protected static final AlphaColorPipe colorPipe;
     protected static final PixelToShapeConverter colorViaShape;
     protected static final PixelToParallelogramConverter colorViaPgram;
     protected static final TextPipe colorText;
     protected static final CompositePipe clipColorPipe;
     protected static final TextPipe clipColorText;
     protected static final AAShapePipe AAColorShape;
-    protected static final PixelToShapeConverter AAColorViaShape;
+    protected static final PixelToParallelogramConverter AAColorViaShape;
+    protected static final PixelToParallelogramConverter AAColorViaPgram;
     protected static final AAShapePipe AAClipColorShape;
-    protected static final PixelToShapeConverter AAClipColorViaShape;
+    protected static final PixelToParallelogramConverter AAClipColorViaShape;
 
     protected static final CompositePipe paintPipe;
     protected static final SpanShapeRenderer paintShape;
@@ -385,9 +386,9 @@
     protected static final CompositePipe clipPaintPipe;
     protected static final TextPipe clipPaintText;
     protected static final AAShapePipe AAPaintShape;
-    protected static final PixelToShapeConverter AAPaintViaShape;
+    protected static final PixelToParallelogramConverter AAPaintViaShape;
     protected static final AAShapePipe AAClipPaintShape;
-    protected static final PixelToShapeConverter AAClipPaintViaShape;
+    protected static final PixelToParallelogramConverter AAClipPaintViaShape;
 
     protected static final CompositePipe compPipe;
     protected static final SpanShapeRenderer compShape;
@@ -396,9 +397,9 @@
     protected static final CompositePipe clipCompPipe;
     protected static final TextPipe clipCompText;
     protected static final AAShapePipe AACompShape;
-    protected static final PixelToShapeConverter AACompViaShape;
+    protected static final PixelToParallelogramConverter AACompViaShape;
     protected static final AAShapePipe AAClipCompShape;
-    protected static final PixelToShapeConverter AAClipCompViaShape;
+    protected static final PixelToParallelogramConverter AAClipCompViaShape;
 
     protected static final DrawImagePipe imagepipe;
 
@@ -427,6 +428,22 @@
         }
     }
 
+    private static PixelToParallelogramConverter
+        makeConverter(AAShapePipe renderer,
+                      ParallelogramPipe pgrampipe)
+    {
+        return new PixelToParallelogramConverter(renderer,
+                                                 pgrampipe,
+                                                 1.0/8.0, 0.499,
+                                                 false);
+    }
+
+    private static PixelToParallelogramConverter
+        makeConverter(AAShapePipe renderer)
+    {
+        return makeConverter(renderer, renderer);
+    }
+
     static {
         colorPrimitives = new LoopPipe();
 
@@ -445,9 +462,10 @@
         clipColorPipe = new SpanClipRenderer(colorPipe);
         clipColorText = new TextRenderer(clipColorPipe);
         AAColorShape = new AAShapePipe(colorPipe);
-        AAColorViaShape = new PixelToShapeConverter(AAColorShape);
+        AAColorViaShape = makeConverter(AAColorShape);
+        AAColorViaPgram = makeConverter(AAColorShape, colorPipe);
         AAClipColorShape = new AAShapePipe(clipColorPipe);
-        AAClipColorViaShape = new PixelToShapeConverter(AAClipColorShape);
+        AAClipColorViaShape = makeConverter(AAClipColorShape);
 
         paintPipe = new AlphaPaintPipe();
         paintShape = new SpanShapeRenderer.Composite(paintPipe);
@@ -456,9 +474,9 @@
         clipPaintPipe = new SpanClipRenderer(paintPipe);
         clipPaintText = new TextRenderer(clipPaintPipe);
         AAPaintShape = new AAShapePipe(paintPipe);
-        AAPaintViaShape = new PixelToShapeConverter(AAPaintShape);
+        AAPaintViaShape = makeConverter(AAPaintShape);
         AAClipPaintShape = new AAShapePipe(clipPaintPipe);
-        AAClipPaintViaShape = new PixelToShapeConverter(AAClipPaintShape);
+        AAClipPaintViaShape = makeConverter(AAClipPaintShape);
 
         compPipe = new GeneralCompositePipe();
         compShape = new SpanShapeRenderer.Composite(compPipe);
@@ -467,9 +485,9 @@
         clipCompPipe = new SpanClipRenderer(compPipe);
         clipCompText = new TextRenderer(clipCompPipe);
         AACompShape = new AAShapePipe(compPipe);
-        AACompViaShape = new PixelToShapeConverter(AACompShape);
+        AACompViaShape = makeConverter(AACompShape);
         AAClipCompShape = new AAShapePipe(clipCompPipe);
-        AAClipCompViaShape = new PixelToShapeConverter(AAClipCompShape);
+        AAClipCompViaShape = makeConverter(AAClipCompShape);
 
         imagepipe = new DrawImage();
     }
@@ -591,12 +609,12 @@
                 if (sg2d.clipState == sg2d.CLIP_SHAPE) {
                     sg2d.drawpipe = AAClipCompViaShape;
                     sg2d.fillpipe = AAClipCompViaShape;
-                    sg2d.shapepipe = AAClipCompShape;
+                    sg2d.shapepipe = AAClipCompViaShape;
                     sg2d.textpipe = clipCompText;
                 } else {
                     sg2d.drawpipe = AACompViaShape;
                     sg2d.fillpipe = AACompViaShape;
-                    sg2d.shapepipe = AACompShape;
+                    sg2d.shapepipe = AACompViaShape;
                     sg2d.textpipe = compText;
                 }
             } else {
@@ -616,12 +634,16 @@
                 if (sg2d.clipState == sg2d.CLIP_SHAPE) {
                     sg2d.drawpipe = AAClipColorViaShape;
                     sg2d.fillpipe = AAClipColorViaShape;
-                    sg2d.shapepipe = AAClipColorShape;
+                    sg2d.shapepipe = AAClipColorViaShape;
                     sg2d.textpipe = clipColorText;
                 } else {
-                    sg2d.drawpipe = AAColorViaShape;
-                    sg2d.fillpipe = AAColorViaShape;
-                    sg2d.shapepipe = AAColorShape;
+                    PixelToParallelogramConverter converter =
+                        (sg2d.alphafill.canDoParallelograms()
+                         ? AAColorViaPgram
+                         : AAColorViaShape);
+                    sg2d.drawpipe = converter;
+                    sg2d.fillpipe = converter;
+                    sg2d.shapepipe = converter;
                     if (sg2d.paintState > sg2d.PAINT_OPAQUECOLOR ||
                         sg2d.compositeState > sg2d.COMP_ISCOPY)
                     {
@@ -634,12 +656,12 @@
                 if (sg2d.clipState == sg2d.CLIP_SHAPE) {
                     sg2d.drawpipe = AAClipPaintViaShape;
                     sg2d.fillpipe = AAClipPaintViaShape;
-                    sg2d.shapepipe = AAClipPaintShape;
+                    sg2d.shapepipe = AAClipPaintViaShape;
                     sg2d.textpipe = clipPaintText;
                 } else {
                     sg2d.drawpipe = AAPaintViaShape;
                     sg2d.fillpipe = AAPaintViaShape;
-                    sg2d.shapepipe = AAPaintShape;
+                    sg2d.shapepipe = AAPaintViaShape;
                     sg2d.textpipe = paintText;
                 }
             }
@@ -793,6 +815,18 @@
         }
     }
 
+    private static CompositeType getFillCompositeType(SunGraphics2D sg2d) {
+        CompositeType compType = sg2d.imageComp;
+        if (sg2d.compositeState == sg2d.COMP_ISCOPY) {
+            if (compType == CompositeType.SrcOverNoEa) {
+                compType = CompositeType.OpaqueSrcOverNoEa;
+            } else {
+                compType = CompositeType.SrcNoEa;
+            }
+        }
+        return compType;
+    }
+
     /**
      * Returns a MaskFill object that can be used on this destination
      * with the source (paint) and composite types determined by the given
@@ -802,9 +836,10 @@
      * surface) before returning a specific MaskFill object.
      */
     protected MaskFill getMaskFill(SunGraphics2D sg2d) {
-        return MaskFill.getFromCache(getPaintSurfaceType(sg2d),
-                                     sg2d.imageComp,
-                                     getSurfaceType());
+        SurfaceType src = getPaintSurfaceType(sg2d);
+        CompositeType comp = getFillCompositeType(sg2d);
+        SurfaceType dst = getSurfaceType();
+        return MaskFill.getFromCache(src, comp, dst);
     }
 
     private static RenderCache loopcache = new RenderCache(30);
@@ -816,9 +851,7 @@
      */
     public RenderLoops getRenderLoops(SunGraphics2D sg2d) {
         SurfaceType src = getPaintSurfaceType(sg2d);
-        CompositeType comp = (sg2d.compositeState == sg2d.COMP_ISCOPY
-                              ? CompositeType.SrcNoEa
-                              : sg2d.imageComp);
+        CompositeType comp = getFillCompositeType(sg2d);
         SurfaceType dst = sg2d.getSurfaceData().getSurfaceType();
 
         Object o = loopcache.get(src, comp, dst);
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/classes/sun/java2d/loops/CompositeType.java
--- openjdk.orig/jdk/src/share/classes/sun/java2d/loops/CompositeType.java	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/classes/sun/java2d/loops/CompositeType.java	Tue Dec 14 13:25:29 2010 -0800
@@ -27,6 +27,7 @@
 
 import java.awt.image.BufferedImage;
 import java.awt.AlphaComposite;
+import java.util.HashMap;
 
 /**
  * A CompositeType object provides a chained description of a type of
@@ -51,6 +52,11 @@
  * the indicated algorithm if all of the more specific searches fail.
  */
 public final class CompositeType {
+
+    private static int unusedUID = 1;
+    private static final HashMap<String,Integer> compositeUIDMap =
+        new HashMap<String,Integer>(100);
+
     /*
      * CONSTANTS USED BY ALL PRIMITIVES TO DESCRIBE THE COMPOSITING
      * ALGORITHMS THEY CAN PERFORM
@@ -153,6 +159,22 @@
         SrcOverNoEa   = SrcOver.deriveSubType(DESC_SRC_OVER_NO_EA);
 
     /*
+     * A special CompositeType for the case where we are filling in
+     * SrcOverNoEa mode with an opaque color.  In that case then the
+     * best loop for us to use would be a SrcNoEa loop, but what if
+     * there is no such loop?  In that case then we would end up
+     * backing off to a Src loop (which should still be fine) or an
+     * AnyAlpha loop which would be slower than a SrcOver loop in
+     * most cases.
+     * The fix is to use the following chain which looks for loops
+     * in the following order:
+     *    SrcNoEa, Src, SrcOverNoEa, SrcOver, AnyAlpha
+     */
+    public static final CompositeType
+        OpaqueSrcOverNoEa = SrcOverNoEa.deriveSubType(DESC_SRC)
+                                       .deriveSubType(DESC_SRC_NO_EA);
+
+    /*
      * END OF CompositeType OBJECTS FOR THE VARIOUS CONSTANTS
      */
 
@@ -210,7 +232,6 @@
         }
     }
 
-    private static int unusedUID = 1;
     private int uniqueID;
     private String desc;
     private CompositeType next;
@@ -218,14 +239,20 @@
     private CompositeType(CompositeType parent, String desc) {
         next = parent;
         this.desc = desc;
-        this.uniqueID = makeUniqueID();
+        this.uniqueID = makeUniqueID(desc);
     }
 
-    private synchronized static final int makeUniqueID() {
-        if (unusedUID > 255) {
-            throw new InternalError("composite type id overflow");
+    public synchronized static final int makeUniqueID(String desc) {
+        Integer i = compositeUIDMap.get(desc);
+
+        if (i == null) {
+            if (unusedUID > 255) {
+                throw new InternalError("composite type id overflow");
+            }
+            i = unusedUID++;
+            compositeUIDMap.put(desc, i);
         }
-        return unusedUID++;
+        return i;
     }
 
     public int getUniqueID() {
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/classes/sun/java2d/loops/MaskFill.java
--- openjdk.orig/jdk/src/share/classes/sun/java2d/loops/MaskFill.java	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/classes/sun/java2d/loops/MaskFill.java	Tue Dec 14 13:25:29 2010 -0800
@@ -50,6 +50,10 @@
 public class MaskFill extends GraphicsPrimitive
 {
     public static final String methodSignature = "MaskFill(...)".toString();
+    public static final String fillPgramSignature =
+        "FillAAPgram(...)".toString();
+    public static final String drawPgramSignature =
+        "DrawAAPgram(...)".toString();
 
     public static final int primTypeID = makePrimTypeID();
 
@@ -92,6 +96,14 @@
         return fill;
     }
 
+    protected MaskFill(String alternateSignature,
+                       SurfaceType srctype,
+                       CompositeType comptype,
+                       SurfaceType dsttype)
+    {
+        super(alternateSignature, primTypeID, srctype, comptype, dsttype);
+    }
+
     protected MaskFill(SurfaceType srctype,
                        CompositeType comptype,
                        SurfaceType dsttype)
@@ -115,6 +127,23 @@
                                 int x, int y, int w, int h,
                                 byte[] mask, int maskoff, int maskscan);
 
+    public native void FillAAPgram(SunGraphics2D sg2d, SurfaceData sData,
+                                   Composite comp,
+                                   double x, double y,
+                                   double dx1, double dy1,
+                                   double dx2, double dy2);
+
+    public native void DrawAAPgram(SunGraphics2D sg2d, SurfaceData sData,
+                                   Composite comp,
+                                   double x, double y,
+                                   double dx1, double dy1,
+                                   double dx2, double dy2,
+                                   double lw1, double lw2);
+
+    public boolean canDoParallelograms() {
+        return (getNativePrim() != 0);
+    }
+
     static {
         GraphicsPrimitiveMgr.registerGeneral(new MaskFill(null, null, null));
     }
@@ -182,12 +211,22 @@
 
     private static class TraceMaskFill extends MaskFill {
         MaskFill target;
+        MaskFill fillPgramTarget;
+        MaskFill drawPgramTarget;
 
         public TraceMaskFill(MaskFill target) {
             super(target.getSourceType(),
                   target.getCompositeType(),
                   target.getDestType());
             this.target = target;
+            this.fillPgramTarget = new MaskFill(fillPgramSignature,
+                                                target.getSourceType(),
+                                                target.getCompositeType(),
+                                                target.getDestType());
+            this.drawPgramTarget = new MaskFill(drawPgramSignature,
+                                                target.getSourceType(),
+                                                target.getCompositeType(),
+                                                target.getDestType());
         }
 
         public GraphicsPrimitive traceWrap() {
@@ -203,5 +242,32 @@
             target.MaskFill(sg2d, sData, comp, x, y, w, h,
                             mask, maskoff, maskscan);
         }
+
+        public void FillAAPgram(SunGraphics2D sg2d, SurfaceData sData,
+                                Composite comp,
+                                double x, double y,
+                                double dx1, double dy1,
+                                double dx2, double dy2)
+        {
+            tracePrimitive(fillPgramTarget);
+            target.FillAAPgram(sg2d, sData, comp,
+                               x, y, dx1, dy1, dx2, dy2);
+        }
+
+        public void DrawAAPgram(SunGraphics2D sg2d, SurfaceData sData,
+                                Composite comp,
+                                double x, double y,
+                                double dx1, double dy1,
+                                double dx2, double dy2,
+                                double lw1, double lw2)
+        {
+            tracePrimitive(drawPgramTarget);
+            target.DrawAAPgram(sg2d, sData, comp,
+                               x, y, dx1, dy1, dx2, dy2, lw1, lw2);
+        }
+
+        public boolean canDoParallelograms() {
+            return target.canDoParallelograms();
+        }
     }
 }
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/classes/sun/java2d/pipe/AAShapePipe.java
--- openjdk.orig/jdk/src/share/classes/sun/java2d/pipe/AAShapePipe.java	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/classes/sun/java2d/pipe/AAShapePipe.java	Tue Dec 14 13:25:29 2010 -0800
@@ -28,6 +28,7 @@
 import java.awt.BasicStroke;
 import java.awt.Rectangle;
 import java.awt.Shape;
+import java.awt.geom.Rectangle2D;
 import java.awt.geom.PathIterator;
 import sun.awt.SunHints;
 import sun.java2d.SunGraphics2D;
@@ -39,7 +40,9 @@
  * This class sets up the Generator and computes the alpha tiles
  * and then passes them on to a CompositePipe object for painting.
  */
-public class AAShapePipe implements ShapeDrawPipe {
+public class AAShapePipe
+    implements ShapeDrawPipe, ParallelogramPipe
+{
     static RenderingEngine renderengine = RenderingEngine.getInstance();
 
     CompositePipe outpipe;
@@ -65,6 +68,59 @@
         renderPath(sg, s, null);
     }
 
+    private static Rectangle2D computeBBox(double x, double y,
+                                           double dx1, double dy1,
+                                           double dx2, double dy2)
+    {
+        double lox, loy, hix, hiy;
+        lox = hix = x;
+        loy = hiy = y;
+        if (dx1 < 0) { lox += dx1; } else { hix += dx1; }
+        if (dy1 < 0) { loy += dy1; } else { hiy += dy1; }
+        if (dx2 < 0) { lox += dx2; } else { hix += dx2; }
+        if (dy2 < 0) { loy += dy2; } else { hiy += dy2; }
+        return new Rectangle2D.Double(lox, loy, hix-lox, hiy-loy);
+    }
+
+    public void fillParallelogram(SunGraphics2D sg,
+                                  double x, double y,
+                                  double dx1, double dy1,
+                                  double dx2, double dy2)
+    {
+        Region clip = sg.getCompClip();
+        int abox[] = new int[4];
+        AATileGenerator aatg =
+            renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, 0, 0,
+                                            clip, abox);
+        if (aatg == null) {
+            // Nothing to render
+            return;
+        }
+
+        renderTiles(sg, computeBBox(x, y, dx1, dy1, dx2, dy2), aatg, abox);
+    }
+
+    public void drawParallelogram(SunGraphics2D sg,
+                                  double x, double y,
+                                  double dx1, double dy1,
+                                  double dx2, double dy2,
+                                  double lw1, double lw2)
+    {
+        Region clip = sg.getCompClip();
+        int abox[] = new int[4];
+        AATileGenerator aatg =
+            renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, 0, 0,
+                                            clip, abox);
+        if (aatg == null) {
+            // Nothing to render
+            return;
+        }
+
+        // Note that bbox is of the original shape, not the wide path.
+        // This is appropriate for handing to Paint methods...
+        renderTiles(sg, computeBBox(x, y, dx1, dy1, dx2, dy2), aatg, abox);
+    }
+
     private static byte[] theTile;
 
     public synchronized static byte[] getAlphaTile(int len) {
@@ -85,8 +141,6 @@
         boolean adjust = (bs != null &&
                           sg.strokeHint != SunHints.INTVAL_STROKE_PURE);
         boolean thin = (sg.strokeState <= sg.STROKE_THINDASHED);
-        Object context = null;
-        byte alpha[] = null;
 
         Region clip = sg.getCompClip();
         int abox[] = new int[4];
@@ -98,6 +152,14 @@
             return;
         }
 
+        renderTiles(sg, s, aatg, abox);
+    }
+
+    public void renderTiles(SunGraphics2D sg, Shape s,
+                            AATileGenerator aatg, int abox[])
+    {
+        Object context = null;
+        byte alpha[] = null;
         try {
             context = outpipe.startSequence(sg, s,
                                             new Rectangle(abox[0], abox[1],
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/classes/sun/java2d/pipe/AlphaColorPipe.java
--- openjdk.orig/jdk/src/share/classes/sun/java2d/pipe/AlphaColorPipe.java	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/classes/sun/java2d/pipe/AlphaColorPipe.java	Tue Dec 14 13:25:29 2010 -0800
@@ -34,7 +34,7 @@
  * into a destination that supports direct alpha compositing of a solid
  * color, according to one of the rules in the AlphaComposite class.
  */
-public class AlphaColorPipe implements CompositePipe {
+public class AlphaColorPipe implements CompositePipe, ParallelogramPipe {
     public AlphaColorPipe() {
     }
 
@@ -64,4 +64,23 @@
     public void endSequence(Object context) {
         return;
     }
+
+    public void fillParallelogram(SunGraphics2D sg,
+                                  double x, double y,
+                                  double dx1, double dy1,
+                                  double dx2, double dy2)
+    {
+        sg.alphafill.FillAAPgram(sg, sg.getSurfaceData(), sg.composite,
+                                 x, y, dx1, dy1, dx2, dy2);
+    }
+
+    public void drawParallelogram(SunGraphics2D sg,
+                                  double x, double y,
+                                  double dx1, double dy1,
+                                  double dx2, double dy2,
+                                  double lw1, double lw2)
+    {
+        sg.alphafill.DrawAAPgram(sg, sg.getSurfaceData(), sg.composite,
+                                 x, y, dx1, dy1, dx2, dy2, lw1, lw2);
+    }
 }
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/classes/sun/java2d/pipe/RenderingEngine.java
--- openjdk.orig/jdk/src/share/classes/sun/java2d/pipe/RenderingEngine.java	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/classes/sun/java2d/pipe/RenderingEngine.java	Tue Dec 14 13:25:29 2010 -0800
@@ -281,6 +281,72 @@
                                                        int bbox[]);
 
     /**
+     * Construct an antialiased tile generator for the given parallelogram
+     * store the bounds of the tile iteration in the bbox parameter.
+     * The parallelogram is specified as a starting point and 2 delta
+     * vectors that indicate the slopes of the 2 pairs of sides of the
+     * parallelogram.
+     * The 4 corners of the parallelogram are defined by the 4 points:
+     * <ul>
+     * <li> {@code x}, {@code y}
+     * <li> {@code x+dx1}, {@code y+dy1}
+     * <li> {@code x+dx1+dx2}, {@code y+dy1+dy2}
+     * <li> {@code x+dx2}, {@code y+dy2}
+     * </ul>
+     * The {@code lw1} and {@code lw2} parameters provide a specification
+     * for an optionally stroked parallelogram if they are positive numbers.
+     * The {@code lw1} parameter is the ratio of the length of the {@code dx1},
+     * {@code dx2} delta vector to half of the line width in that same
+     * direction.
+     * The {@code lw2} parameter provides the same ratio for the other delta
+     * vector.
+     * If {@code lw1} and {@code lw2} are both greater than zero, then
+     * the parallelogram figure is doubled by both expanding and contracting
+     * each delta vector by its corresponding {@code lw} value.
+     * If either (@code lw1) or {@code lw2} are also greater than 1, then
+     * the inner (contracted) parallelogram disappears and the figure is
+     * simply a single expanded parallelogram.
+     * The {@code clip} parameter specifies the current clip in effect
+     * in device coordinates and can be used to prune the data for the
+     * operation, but the renderer is not required to perform any
+     * clipping.
+     * <p>
+     * Upon returning, this method will fill the {@code bbox} parameter
+     * with 4 values indicating the bounds of the iteration of the
+     * tile generator.
+     * The iteration order of the tiles will be as specified by the
+     * pseudo-code:
+     * <pre>
+     *     for (y = bbox[1]; y < bbox[3]; y += tileheight) {
+     *         for (x = bbox[0]; x < bbox[2]; x += tilewidth) {
+     *         }
+     *     }
+     * </pre>
+     * If there is no output to be rendered, this method may return
+     * null.
+     *
+     * @param x the X coordinate of the first corner of the parallelogram
+     * @param y the Y coordinate of the first corner of the parallelogram
+     * @param dx1 the X coordinate delta of the first leg of the parallelogram
+     * @param dy1 the Y coordinate delta of the first leg of the parallelogram
+     * @param dx2 the X coordinate delta of the second leg of the parallelogram
+     * @param dy2 the Y coordinate delta of the second leg of the parallelogram
+     * @param lw1 the line width ratio for the first leg of the parallelogram
+     * @param lw2 the line width ratio for the second leg of the parallelogram
+     * @param clip the current clip in effect in device coordinates
+     * @param bbox returns the bounds of the iteration
+     * @return the {@code AATileGenerator} instance to be consulted
+     *         for tile coverages, or null if there is no output to render
+     * @since 1.7
+     */
+    public abstract AATileGenerator getAATileGenerator(double x, double y,
+                                                       double dx1, double dy1,
+                                                       double dx2, double dy2,
+                                                       double lw1, double lw2,
+                                                       Region clip,
+                                                       int bbox[]);
+
+    /**
      * Returns the minimum pen width that the antialiasing rasterizer
      * can represent without dropouts occuring.
      * @since 1.7
@@ -393,5 +459,24 @@
                                              bs, thin, normalize,
                                              bbox);
         }
+        public AATileGenerator getAATileGenerator(double x, double y,
+                                                  double dx1, double dy1,
+                                                  double dx2, double dy2,
+                                                  double lw1, double lw2,
+                                                  Region clip,
+                                                  int bbox[])
+        {
+            System.out.println(name+".getAATileGenerator("+
+                               x+", "+y+", "+
+                               dx1+", "+dy1+", "+
+                               dx2+", "+dy2+", "+
+                               lw1+", "+lw2+", "+
+                               clip+")");
+            return target.getAATileGenerator(x, y,
+                                             dx1, dy1,
+                                             dx2, dy2,
+                                             lw1, lw2,
+                                             clip, bbox);
+        }
     }
 }
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java
--- openjdk.orig/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/classes/sun/java2d/pisces/PiscesRenderingEngine.java	Tue Dec 14 13:25:29 2010 -0800
@@ -557,6 +557,69 @@
         return ptg;
     }
 
+    public AATileGenerator getAATileGenerator(double x, double y,
+                                              double dx1, double dy1,
+                                              double dx2, double dy2,
+                                              double lw1, double lw2,
+                                              Region clip,
+                                              int bbox[])
+    {
+        // REMIND: Deal with large coordinates!
+        double ldx1, ldy1, ldx2, ldy2;
+        boolean innerpgram = (lw1 > 0 && lw2 > 0);
+
+        if (innerpgram) {
+            ldx1 = dx1 * lw1;
+            ldy1 = dy1 * lw1;
+            ldx2 = dx2 * lw2;
+            ldy2 = dy2 * lw2;
+            x -= (ldx1 + ldx2) / 2.0;
+            y -= (ldy1 + ldy2) / 2.0;
+            dx1 += ldx1;
+            dy1 += ldy1;
+            dx2 += ldx2;
+            dy2 += ldy2;
+            if (lw1 > 1 && lw2 > 1) {
+                // Inner parallelogram was entirely consumed by stroke...
+                innerpgram = false;
+            }
+        } else {
+            ldx1 = ldy1 = ldx2 = ldy2 = 0;
+        }
+
+        Renderer r = new Renderer(3, 3,
+                                  clip.getLoX(), clip.getLoY(),
+                                  clip.getWidth(), clip.getHeight(),
+                                  PathIterator.WIND_EVEN_ODD);
+
+        r.moveTo((float) x, (float) y);
+        r.lineTo((float) (x+dx1), (float) (y+dy1));
+        r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
+        r.lineTo((float) (x+dx2), (float) (y+dy2));
+        r.closePath();
+
+        if (innerpgram) {
+            x += ldx1 + ldx2;
+            y += ldy1 + ldy2;
+            dx1 -= 2.0 * ldx1;
+            dy1 -= 2.0 * ldy1;
+            dx2 -= 2.0 * ldx2;
+            dy2 -= 2.0 * ldy2;
+            r.moveTo((float) x, (float) y);
+            r.lineTo((float) (x+dx1), (float) (y+dy1));
+            r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
+            r.lineTo((float) (x+dx2), (float) (y+dy2));
+            r.closePath();
+        }
+
+        r.pathDone();
+
+        r.endRendering();
+        PiscesTileGenerator ptg = new PiscesTileGenerator(r, r.MAX_AA_ALPHA);
+        ptg.getBbox(bbox);
+        return ptg;
+    }
+
     /**
      * Returns the minimum pen width that the antialiasing rasterizer
      * can represent without dropouts occuring.
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/native/sun/java2d/loops/DrawParallelogram.c
--- openjdk.orig/jdk/src/share/native/sun/java2d/loops/DrawParallelogram.c	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/native/sun/java2d/loops/DrawParallelogram.c	Tue Dec 14 13:25:29 2010 -0800
@@ -26,14 +26,11 @@
 #include "math.h"
 #include "GraphicsPrimitiveMgr.h"
 #include "LineUtils.h"
-#include "LoopMacros.h"
 #include "Trace.h"
+#include "ParallelogramUtils.h"
 
-#include "sun_java2d_loops_FillParallelogram.h"
 #include "sun_java2d_loops_DrawParallelogram.h"
 
-DECLARE_SOLID_DRAWLINE(AnyInt);
-
 #define HANDLE_PGRAM_EDGE(X1, Y1, X2, Y2, \
                           pRasInfo, pixel, pPrim, pFunc, pCompInfo) \
     do { \
@@ -46,28 +43,6 @@
                                ix1, iy1, ix2, iy2, JNI_TRUE); \
     } while (0)
 
-#define PGRAM_MIN_MAX(bmin, bmax, v0, dv1, dv2) \
-    do { \
-        double vmin, vmax; \
-        if (dv1 < 0) { \
-            vmin = v0+dv1; \
-            vmax = v0; \
-        } else { \
-            vmin = v0; \
-            vmax = v0+dv1; \
-        } \
-        if (dv2 < 0) { \
-            vmin -= dv2; \
-        } else { \
-            vmax += dv2; \
-        } \
-        bmin = (jint) floor(vmin + 0.5); \
-        bmax = (jint) floor(vmax + 0.5); \
-    } while(0)
-
-#define PGRAM_INIT_X(starty, x, y, slope) \
-    (DblToLong((x) + (slope) * ((starty)+0.5 - (y))) + LongOneHalf - 1)
-
 typedef struct {
     jdouble x0;
     jdouble y0;
@@ -136,20 +111,8 @@
      * Sort parallelogram by y values, ensure that each delta vector
      * has a non-negative y delta.
      */
-    if (dy1 < 0) {
-        x0 += dx1;  y0 += dy1;
-        dx1 = -dx1; dy1 = -dy1;
-    }
-    if (dy2 < 0) {
-        x0 += dx2;  y0 += dy2;
-        dx2 = -dx2; dy2 = -dy2;
-    }
-    /* Sort delta vectors so dxy1 is left of dxy2. */
-    if (dx1 * dy2 > dx2 * dy1) {
-        double v = dx1; dx1 = dx2; dx2 = v;
-               v = dy1; dy1 = dy2; dy2 = v;
-               v = lw1; lw1 = lw2; lw2 = v;
-    }
+    SORT_PGRAM(x0, y0, dx1, dy1, dx2, dy2,
+               v = lw1; lw1 = lw2; lw2 = v;);
 
     // dx,dy for line width in the "1" and "2" directions.
     ldx1 = dx1 * lw1;
@@ -161,7 +124,7 @@
     ox0 = x0 - (ldx1 + ldx2) / 2.0;
     oy0 = y0 - (ldy1 + ldy2) / 2.0;
 
-    PGRAM_MIN_MAX(ix1, ix2, ox0, dx1+ldx1, dx2+ldx2);
+    PGRAM_MIN_MAX(ix1, ix2, ox0, dx1+ldx1, dx2+ldx2, JNI_FALSE);
     iy1 = (jint) floor(oy0 + 0.5);
     iy2 = (jint) floor(oy0 + dy1 + ldy1 + dy2 + ldy2 + 0.5);
 
@@ -212,7 +175,7 @@
             // Only need to generate 4 quads if the interior still
             // has a hole in it (i.e. if the line width ratios were
             // both less than 1.0)
-            if (lw1 < 1.0f && lw2 < 1.0f) {
+            if (lw1 < 1.0 && lw2 < 1.0) {
                 // If the line widths are both less than a pixel wide
                 // then we can use a drawline function instead for even
                 // more performance.
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/native/sun/java2d/loops/FillParallelogram.c
--- openjdk.orig/jdk/src/share/native/sun/java2d/loops/FillParallelogram.c	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/native/sun/java2d/loops/FillParallelogram.c	Tue Dec 14 13:25:29 2010 -0800
@@ -25,31 +25,10 @@
 
 #include "math.h"
 #include "GraphicsPrimitiveMgr.h"
+#include "ParallelogramUtils.h"
 
 #include "sun_java2d_loops_FillParallelogram.h"
 
-#define PGRAM_MIN_MAX(bmin, bmax, v0, dv1, dv2) \
-    do { \
-        double vmin, vmax; \
-        if (dv1 < 0) { \
-            vmin = v0+dv1; \
-            vmax = v0; \
-        } else { \
-            vmin = v0; \
-            vmax = v0+dv1; \
-        } \
-        if (dv2 < 0) { \
-            vmin -= dv2; \
-        } else { \
-            vmax += dv2; \
-        } \
-        bmin = (jint) floor(vmin + 0.5); \
-        bmax = (jint) floor(vmax + 0.5); \
-    } while(0)
-
-#define PGRAM_INIT_X(starty, x, y, slope) \
-    (DblToLong((x) + (slope) * ((starty)+0.5 - (y))) + LongOneHalf - 1)
-
 /*
  * Class:     sun_java2d_loops_FillParallelogram
  * Method:    FillParallelogram
@@ -76,22 +55,11 @@
 
     /*
      * Sort parallelogram by y values, ensure that each delta vector
-     * has a non-negative y delta, and eliminate degenerate parallelograms.
+     * has a non-negative y delta.
      */
-    if (dy1 < 0) {
-        x0 += dx1;  y0 += dy1;
-        dx1 = -dx1; dy1 = -dy1;
-    }
-    if (dy2 < 0) {
-        x0 += dx2;  y0 += dy2;
-        dx2 = -dx2; dy2 = -dy2;
-    }
-    /* Sort delta vectors so dxy1 is left of dxy2. */
-    if (dx1 * dy2 > dx2 * dy1) {
-        double v = dx1; dx1 = dx2; dx2 = v;
-               v = dy1; dy1 = dy2; dy2 = v;
-    }
-    PGRAM_MIN_MAX(ix1, ix2, x0, dx1, dx2);
+    SORT_PGRAM(x0, y0, dx1, dy1, dx2, dy2, );
+
+    PGRAM_MIN_MAX(ix1, ix2, x0, dx1, dx2, JNI_FALSE);
     iy1 = (jint) floor(y0 + 0.5);
     iy2 = (jint) floor(y0 + dy1 + dy2 + 0.5);
 
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/native/sun/java2d/loops/MaskFill.c
--- openjdk.orig/jdk/src/share/native/sun/java2d/loops/MaskFill.c	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/share/native/sun/java2d/loops/MaskFill.c	Tue Dec 14 13:25:29 2010 -0800
@@ -23,7 +23,11 @@
  * questions.
  */
 
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
 #include "GraphicsPrimitiveMgr.h"
+#include "ParallelogramUtils.h"
 
 #include "sun_java2d_loops_MaskFill.h"
 
@@ -93,6 +97,967 @@
             }
         }
         SurfaceData_InvokeRelease(env, sdOps, &rasInfo);
+   }
+   SurfaceData_InvokeUnlock(env, sdOps, &rasInfo);
+}
+
+#define MASK_BUF_LEN 1024
+
+#define DblToMask(v) ((unsigned char) ((v)*255.9999))
+
+/* Fills an aligned rectangle with potentially translucent edges. */
+static void
+fillAARect(NativePrimitive *pPrim, SurfaceDataRasInfo *pRasInfo,
+           CompositeInfo *pCompInfo, jint color, unsigned char *pMask,
+           void *pDst,
+           jdouble x1, jdouble y1, jdouble x2, jdouble y2)
+{
+    jint cx1 = pRasInfo->bounds.x1;
+    jint cy1 = pRasInfo->bounds.y1;
+    jint cx2 = pRasInfo->bounds.x2;
+    jint cy2 = pRasInfo->bounds.y2;
+    jint rx1 = (jint) ceil(x1);
+    jint ry1 = (jint) ceil(y1);
+    jint rx2 = (jint) floor(x2);
+    jint ry2 = (jint) floor(y2);
+    jint width = cx2 - cx1;
+    jint scan = pRasInfo->scanStride;
+    /* Convert xy12 into the edge coverage fractions for those edges. */
+    x1 = rx1-x1;
+    y1 = ry1-y1;
+    x2 = x2-rx2;
+    y2 = y2-ry2;
+    if (ry2 < ry1) {
+        /* Accumulate bottom coverage into top coverage. */
+        y1 = y1 + y2 - 1.0;
+        /* prevent processing of "bottom fractional row" */
+        ry2 = cy2;
+    }
+    if (rx2 < rx1) {
+        /* Accumulate right coverage into left coverage. */
+        x1 = x1 + x2 - 1.0;
+        /* prevent processing of "right fractional column" */
+        rx2 = cx2;
+    }
+    /* Check for a visible "top fractional row" and process it */
+    if (cy1 < ry1) {
+        unsigned char midcov = DblToMask(y1);
+        jint x;
+        for (x = 0; x < width; x++) {
+            pMask[x] = midcov;
+        }
+        if (cx1 < rx1) {
+            pMask[0] = DblToMask(y1 * x1);
+        }
+        if (cx2 > rx2) {
+            pMask[width-1] = DblToMask(y1 * x2);
+        }
+        (*pPrim->funcs.maskfill)(pDst,
+                                 pMask, 0, 0,
+                                 width, 1,
+                                 color, pRasInfo,
+                                 pPrim, pCompInfo);
+        pDst = PtrAddBytes(pDst, scan);
+        cy1++;
+    }
+    /* Check for a visible "left fract, solid middle, right fract" section. */
+    if (cy1 < ry2 && cy1 < cy2) {
+        jint midh = ((ry2 < cy2) ? ry2 : cy2) - cy1;
+        jint midx = cx1;
+        void *pMid = pDst;
+        /* First process the left "fractional column" if it is visible. */
+        if (midx < rx1) {
+            pMask[0] = DblToMask(x1);
+            /* Note: maskscan == 0 means we reuse this value for every row. */
+            (*pPrim->funcs.maskfill)(pMid,
+                                     pMask, 0, 0,
+                                     1, midh,
+                                     color, pRasInfo,
+                                     pPrim, pCompInfo);
+            pMid = PtrAddBytes(pMid, pRasInfo->pixelStride);
+            midx++;
+        }
+        /* Process the central solid section if it is visible. */
+        if (midx < rx2 && midx < cx2) {
+            jint midw = ((rx2 < cx2) ? rx2 : cx2) - midx;
+            /* A NULL mask buffer means "all coverages are 0xff" */
+            (*pPrim->funcs.maskfill)(pMid,
+                                     NULL, 0, 0,
+                                     midw, midh,
+                                     color, pRasInfo,
+                                     pPrim, pCompInfo);
+            pMid = PtrCoord(pMid, midw, pRasInfo->pixelStride, 0, 0);
+            midx += midw;
+        }
+        /* Finally process the right "fractional column" if it is visible. */
+        if (midx < cx2) {
+            pMask[0] = DblToMask(x2);
+            /* Note: maskscan == 0 means we reuse this value for every row. */
+            (*pPrim->funcs.maskfill)(pMid,
+                                     pMask, 0, 0,
+                                     1, midh,
+                                     color, pRasInfo,
+                                     pPrim, pCompInfo);
+        }
+        cy1 += midh;
+        pDst = PtrCoord(pDst, 0, 0, midh, scan);
+    }
+    /* Check for a visible "bottom fractional row" and process it */
+    if (cy1 < cy2) {
+        unsigned char midcov = DblToMask(y2);
+        jint x;
+        for (x = 0; x < width; x++) {
+            pMask[x] = midcov;
+        }
+        if (cx1 < rx1) {
+            pMask[0] = DblToMask(y2 * x1);
+        }
+        if (cx2 > rx2) {
+            pMask[width-1] = DblToMask(y2 * x2);
+        }
+        (*pPrim->funcs.maskfill)(pDst,
+                                 pMask, 0, 0,
+                                 width, 1,
+                                 color, pRasInfo,
+                                 pPrim, pCompInfo);
+    }
+}
+
+/*
+ * Support code for arbitrary tracing and MaskFill filling of
+ * non-rectilinear (diagonal) parallelograms.
+ *
+ * This code is based upon the following model of AA coverage.
+ *
+ * Each edge of a parallelogram (for fillPgram) or a double
+ * parallelogram (inner and outer parallelograms for drawPgram)
+ * can be rasterized independently because the geometry is well
+ * defined in such a way that none of the sides will ever cross
+ * each other and they have a fixed ordering that is fairly
+ * well predetermined.
+ *
+ * So, for each edge we will look at the diagonal line that
+ * the edge makes as it passes through a row of pixels.  Some
+ * such diagonal lines may pass entirely through the row of
+ * pixels in a single pixel column.  Some may cut across the
+ * row and pass through several pixel columns before they pass
+ * on to the next row.
+ *
+ * As the edge passes through the row of pixels it will affect
+ * the coverage of the pixels it passes through as well as all
+ * of the pixels to the right of the edge.  The coverage will
+ * either be increased (by a left edge of a parallelogram) or
+ * decreased (by a right edge) for all pixels to the right, until
+ * another edge passing the opposite direction is encountered.
+ *
+ * The coverage added or subtracted by an edge as it crosses a
+ * given pixel is calculated using a trapezoid formula in the
+ * following manner:
+ *
+ *                /
+ *     +-----+---/-+-----+
+ *     |     |  /  |     |
+ *     |     | /   |     |
+ *     +-----+/----+-----+
+ *           /
+ *
+ * The area to the right of that edge for the pixel where it
+ * crosses is given as:
+ *
+ *     trapheight * (topedge + bottomedge)/2
+ *
+ * Another thing to note is that the above formula gives the
+ * contribution of that edge to the given pixel where it crossed,
+ * but in so crossing the pixel row, it also created 100% coverage
+ * for all of the pixels to the right.
+ *
+ * This example was simplified in that the edge depicted crossed
+ * the complete pixel row and it did so entirely within the bounds
+ * of a single pixel column.  In practice, many edges may start or
+ * end in a given row and thus provide only partial row coverage
+ * (i.e. the total "trapheight" in the formula never reaches 1.0).
+ * And in other cases, edges may travel sideways through several
+ * pixel columns on a given pixel row from where they enter it to
+ * where the leave it (which also mans that the trapheight for a
+ * given pixel will be less than 1.0, but by the time the edge
+ * completes its journey through the pixel row the "coverage shadow"
+ * that it casts on all pixels to the right eventually reaches 100%).
+ *
+ * In order to simplify the calculations so that we don't have to
+ * keep propagating coverages we calculate for one edge "until we
+ * reach another edge" we will process one edge at a time and
+ * simply record in a buffer the amount that an edge added to
+ * or subtracted from the coverage for a given pixel and its
+ * following right-side neighbors.  Thus, the true total coverage
+ * of a given pixel is only determined by summing the deltas for
+ * that pixel and all of the pixels to its left.  Since we already
+ * have to scan the buffer to change floating point coverages into
+ * mask values for a MaskFill loop, it is simple enough to sum the
+ * values as we perform that scan from left to right.
+ *
+ * In the above example, note that 2 deltas need to be recorded even
+ * though the edge only intersected a single pixel.  The delta recorded
+ * for the pixel where the edge crossed will be approximately 55%
+ * (guesstimating by examining the poor ascii art) which is fine for
+ * determining how to render that pixel, but the rest of the pixels
+ * to its right should have their coverage modified by a full 100%
+ * and the 55% delta value we recorded for the pixel that the edge
+ * crossed will not get them there.  We adjust for this by adding
+ * the "remainder" of the coverage implied by the shadow to the
+ * pixel immediately to the right of where we record a trapezoidal
+ * contribution.  In this case a delta of 45% will be recorded in
+ * the pixel immediately to the right to raise the total to 100%.
+ *
+ * As we sum these delta values as we process the line from left
+ * to right, these delta values will typically drive the sum from
+ * 0% up to 100% and back down to 0% over the course of a single
+ * pixel row.  In the case of a drawn (double) parallelogram the
+ * sum will go to 100% and back to 0% twice on most scanlines.
+ *
+ * The fillAAPgram and drawAAPgram functions drive the main flow
+ * of the algorithm with help from the following structures,
+ * macros, and functions.  It is probably best to start with
+ * those 2 functions to gain an understanding of the algorithm.
+ */
+typedef struct {
+    jdouble x;
+    jdouble y;
+    jdouble xbot;
+    jdouble ybot;
+    jdouble xnexty;
+    jdouble ynextx;
+    jdouble xnextx;
+    jdouble linedx;
+    jdouble celldx;
+    jdouble celldy;
+    jboolean isTrailing;
+} EdgeInfo;
+
+#define MIN_DELTA  (1.0/256.0)
+
+/*
+ * Calculates slopes and deltas for an edge and stores results in an EdgeInfo.
+ * Returns true if the edge was valid (i.e. not ignored for some reason).
+ */
+static jboolean
+storeEdge(EdgeInfo *pEdge,
+          jdouble x, jdouble y, jdouble dx, jdouble dy,
+          jint cx1, jint cy1, jint cx2, jint cy2,
+          jboolean isTrailing)
+{
+    jdouble xbot = x + dx;
+    jdouble ybot = y + dy;
+    jboolean ret;
+
+    pEdge->x = x;
+    pEdge->y = y;
+    pEdge->xbot = xbot;
+    pEdge->ybot = ybot;
+
+    /* Note that parallelograms are sorted so dy is always non-negative */
+    if (dy > MIN_DELTA &&        /* NaN and horizontal protection */
+        ybot > cy1 &&            /* NaN and "OUT_ABOVE" protection */
+        y < cy2 &&               /* NaN and "OUT_BELOW" protection */
+        xbot == xbot &&          /* NaN protection */
+        (x < cx2 || xbot < cx2)) /* "OUT_RIGHT" protection */
+        /* Note: "OUT_LEFT" segments may still contribute coverage... */
+    {
+        /* no NaNs, dy is not horizontal, and segment contributes to clip */
+        if (dx < -MIN_DELTA || dx > MIN_DELTA) {
+            /* dx is not vertical */
+            jdouble linedx;
+            jdouble celldy;
+            jdouble nextx;
+
+            linedx = dx / dy;
+            celldy = dy / dx;
+            if (y < cy1) {
+                pEdge->x = x = x + (cy1 - y) * linedx;
+                pEdge->y = y = cy1;
+            }
+            pEdge->linedx = linedx;
+            if (dx < 0) {
+                pEdge->celldx = -1.0;
+                pEdge->celldy = -celldy;
+                pEdge->xnextx = nextx = ceil(x) - 1.0;
+            } else {
+                pEdge->celldx = +1.0;
+                pEdge->celldy = celldy;
+                pEdge->xnextx = nextx = floor(x) + 1.0;
+            }
+            pEdge->ynextx = y + (nextx - x) * celldy;
+            pEdge->xnexty = x + ((floor(y) + 1) - y) * linedx;
+        } else {
+            /* dx is essentially vertical */
+            if (y < cy1) {
+                pEdge->y = y = cy1;
+            }
+            pEdge->xbot = x;
+            pEdge->linedx = 0.0;
+            pEdge->celldx = 0.0;
+            pEdge->celldy = 1.0;
+            pEdge->xnextx = x;
+            pEdge->xnexty = x;
+            pEdge->ynextx = ybot;
+        }
+        ret = JNI_TRUE;
+    } else {
+        /* There is some reason to ignore this segment, "celldy=0" omits it */
+        pEdge->ybot = y;
+        pEdge->linedx = dx;
+        pEdge->celldx = dx;
+        pEdge->celldy = 0.0;
+        pEdge->xnextx = xbot;
+        pEdge->xnexty = xbot;
+        pEdge->ynextx = y;
+        ret = JNI_FALSE;
+    }
+    pEdge->isTrailing = isTrailing;
+    return ret;
+}
+
+/*
+ * Calculates and stores slopes and deltas for all edges of a parallelogram.
+ * Returns true if at least 1 edge was valid (i.e. not ignored for some reason).
+ *
+ * The inverted flag is true for an outer parallelogram (left and right
+ * edges are leading and trailing) and false for an inner parallelogram
+ * (where the left edge is trailing and the right edge is leading).
+ */
+static jboolean
+storePgram(EdgeInfo *pLeftEdge, EdgeInfo *pRightEdge,
+           jdouble x, jdouble y,
+           jdouble dx1, jdouble dy1,
+           jdouble dx2, jdouble dy2,
+           jint cx1, jint cy1, jint cx2, jint cy2,
+           jboolean inverted)
+{
+    jboolean ret = JNI_FALSE;
+    ret = (storeEdge(pLeftEdge  + 0,
+                     x    , y    , dx1, dy1,
+                     cx1, cy1, cx2, cy2, inverted) || ret);
+    ret = (storeEdge(pLeftEdge  + 1,
+                     x+dx1, y+dy1, dx2, dy2,
+                     cx1, cy1, cx2, cy2, inverted) || ret);
+    ret = (storeEdge(pRightEdge + 0,
+                     x    , y    , dx2, dy2,
+                     cx1, cy1, cx2, cy2, !inverted) || ret);
+    ret = (storeEdge(pRightEdge + 1,
+                     x+dx2, y+dy2, dx1, dy1,
+                     cx1, cy1, cx2, cy2, !inverted) || ret);
+    return ret;
+}
+
+/*
+ * The X0,Y0,X1,Y1 values represent a trapezoidal fragment whose
+ * coverage must be accounted for in the accum buffer.
+ *
+ * All four values are assumed to fall within (or on the edge of)
+ * a single pixel.
+ *
+ * The trapezoid area is accumulated into the proper element of
+ * the accum buffer and the remainder of the "slice height" is
+ * accumulated into the element to its right.
+ */
+#define INSERT_ACCUM(pACCUM, IMIN, IMAX, X0, Y0, X1, Y1, CX1, CX2, MULT) \
+    do { \
+        jdouble xmid = ((X0) + (X1)) * 0.5; \
+        if (xmid <= (CX2)) { \
+            jdouble sliceh = ((Y1) - (Y0)); \
+            jdouble slicearea; \
+            jint i; \
+            if (xmid < (CX1)) { \
+                /* Accumulate the entire slice height into accum[0]. */ \
+                i = 0; \
+                slicearea = sliceh; \
+            } else { \
+                jdouble xpos = floor(xmid); \
+                i = ((jint) xpos) - (CX1); \
+                slicearea = (xpos+1-xmid) * sliceh; \
+            } \
+            if (IMIN > i) { \
+                IMIN = i; \
+            } \
+            (pACCUM)[i++] += (jfloat) ((MULT) * slicearea); \
+            (pACCUM)[i++] += (jfloat) ((MULT) * (sliceh - slicearea)); \
+            if (IMAX < i) { \
+                IMAX = i; \
+            } \
+        } \
+    } while (0)
+
+/*
+ * Accumulate the contributions for a given edge crossing a given
+ * scan line into the corresponding entries of the accum buffer.
+ * CY1 is the Y coordinate of the top edge of the scanline and CY2
+ * is equal to (CY1 + 1) and is the Y coordinate of the bottom edge
+ * of the scanline.  CX1 and CX2 are the left and right edges of the
+ * clip (or area of interest) being rendered.
+ *
+ * The edge is processed from the top edge to the bottom edge and
+ * a single pixel column at a time.
+ */
+#define ACCUM_EDGE(pEDGE, pACCUM, IMIN, IMAX, CX1, CY1, CX2, CY2) \
+    do { \
+        jdouble x, y, xnext, ynext, xlast, ylast, dx, dy, mult; \
+        y = (pEDGE)->y; \
+        dy = (pEDGE)->celldy; \
+        ylast = (pEDGE)->ybot; \
+        if (ylast <= (CY1) || y >= (CY2) || dy == 0.0) { \
+            break; \
+        } \
+        x = (pEDGE)->x; \
+        dx = (pEDGE)->celldx; \
+        if (ylast > (CY2)) { \
+            ylast = (CY2); \
+            xlast = (pEDGE)->xnexty; \
+        } else { \
+            xlast = (pEDGE)->xbot; \
+        } \
+        xnext = (pEDGE)->xnextx; \
+        ynext = (pEDGE)->ynextx; \
+        mult = ((pEDGE)->isTrailing) ? -1.0 : 1.0; \
+        while (ynext <= ylast) { \
+            INSERT_ACCUM(pACCUM, IMIN, IMAX, \
+                         x, y, xnext, ynext, \
+                         CX1, CX2, mult); \
+            x = xnext; \
+            y = ynext; \
+            xnext += dx; \
+            ynext += dy; \
+        } \
+        (pEDGE)->ynextx = ynext; \
+        (pEDGE)->xnextx = xnext; \
+        INSERT_ACCUM(pACCUM, IMIN, IMAX, \
+                     x, y, xlast, ylast, \
+                     CX1, CX2, mult); \
+        (pEDGE)->x = xlast; \
+        (pEDGE)->y = ylast; \
+        (pEDGE)->xnexty = xlast + (pEDGE)->linedx; \
+    } while(0)
+
+/* Main function to fill a single Parallelogram */
+static void
+fillAAPgram(NativePrimitive *pPrim, SurfaceDataRasInfo *pRasInfo,
+            CompositeInfo *pCompInfo, jint color, unsigned char *pMask,
+            void *pDst,
+            jdouble x1, jdouble y1,
+            jdouble dx1, jdouble dy1,
+            jdouble dx2, jdouble dy2)
+{
+    jint cx1 = pRasInfo->bounds.x1;
+    jint cy1 = pRasInfo->bounds.y1;
+    jint cx2 = pRasInfo->bounds.x2;
+    jint cy2 = pRasInfo->bounds.y2;
+    jint width = cx2 - cx1;
+    EdgeInfo edges[4];
+    jfloat localaccum[MASK_BUF_LEN + 1];
+    jfloat *pAccum;
+
+    if (!storePgram(edges + 0, edges + 2,
+                    x1, y1, dx1, dy1, dx2, dy2,
+                    cx1, cy1, cx2, cy2,
+                    JNI_FALSE))
+    {
+        return;
+    }
+
+    pAccum = ((width > MASK_BUF_LEN)
+              ? malloc((width + 1) * sizeof(jfloat))
+              : localaccum);
+    if (pAccum == NULL) {
+        return;
+    }
+    memset(pAccum, 0, (width+1) * sizeof(jfloat));
+
+    while (cy1 < cy2) {
+        jint lmin, lmax, rmin, rmax;
+        jint moff, x;
+        jdouble accum;
+        unsigned char lastcov;
+
+        lmin = rmin = width + 2;
+        lmax = rmax = 0;
+        ACCUM_EDGE(&edges[0], pAccum, lmin, lmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[1], pAccum, lmin, lmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[2], pAccum, rmin, rmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[3], pAccum, rmin, rmax,
+                   cx1, cy1, cx2, cy1+1);
+        if (lmax > width) {
+            lmax = width; /* Extra col has data we do not need. */
+        }
+        if (rmax > width) {
+            rmax = width; /* Extra col has data we do not need. */
+        }
+        /* If ranges overlap, handle both in the first pass. */
+        if (rmin <= lmax) {
+            lmax = rmax;
+        }
+
+        x = lmin;
+        accum = 0.0;
+        moff = 0;
+        lastcov = 0;
+        while (x < lmax) {
+            accum += pAccum[x];
+            pAccum[x] = 0.0f;
+            pMask[moff++] = lastcov = DblToMask(accum);
+            x++;
+        }
+        /* Check for a solid center section. */
+        if (lastcov == 0xFF) {
+            jint endx;
+            void *pRow;
+
+            /* First process the existing partial coverage data. */
+            if (moff > 0) {
+                pRow = PtrCoord(pDst, x-moff, pRasInfo->pixelStride, 0, 0);
+                (*pPrim->funcs.maskfill)(pRow,
+                                         pMask, 0, 0,
+                                         moff, 1,
+                                         color, pRasInfo,
+                                         pPrim, pCompInfo);
+                moff = 0;
+            }
+
+            /* Where does the center section end? */
+            /* If there is no right AA edge in the accum buffer, then */
+            /* the right edge was beyond the clip, so fill out to width */
+            endx = (rmin < rmax) ? rmin : width;
+            if (x < endx) {
+                pRow = PtrCoord(pDst, x, pRasInfo->pixelStride, 0, 0);
+                (*pPrim->funcs.maskfill)(pRow,
+                                         NULL, 0, 0,
+                                         endx - x, 1,
+                                         color, pRasInfo,
+                                         pPrim, pCompInfo);
+                x = endx;
+            }
+        } else if (lastcov > 0 && rmin >= rmax) {
+            /* We are not at 0 coverage, but there is no right edge, */
+            /* force a right edge so we process pixels out to width. */
+            rmax = width;
+        }
+        /* The following loop will process the right AA edge and/or any */
+        /* partial coverage center section not processed above. */
+        while (x < rmax) {
+            accum += pAccum[x];
+            pAccum[x] = 0.0f;
+            pMask[moff++] = DblToMask(accum);
+            x++;
+        }
+        if (moff > 0) {
+            void *pRow = PtrCoord(pDst, x-moff, pRasInfo->pixelStride, 0, 0);
+            (*pPrim->funcs.maskfill)(pRow,
+                                     pMask, 0, 0,
+                                     moff, 1,
+                                     color, pRasInfo,
+                                     pPrim, pCompInfo);
+        }
+        pDst = PtrAddBytes(pDst, pRasInfo->scanStride);
+        cy1++;
+    }
+    if (pAccum != localaccum) {
+        free(pAccum);
+    }
+}
+
+/*
+ * Class:     sun_java2d_loops_MaskFill
+ * Method:    FillAAPgram
+ * Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Ljava/awt/Composite;DDDDDD)V
+ */
+JNIEXPORT void JNICALL
+Java_sun_java2d_loops_MaskFill_FillAAPgram
+    (JNIEnv *env, jobject self,
+     jobject sg2d, jobject sData, jobject comp,
+     jdouble x0, jdouble y0,
+     jdouble dx1, jdouble dy1,
+     jdouble dx2, jdouble dy2)
+{
+    SurfaceDataOps *sdOps;
+    SurfaceDataRasInfo rasInfo;
+    NativePrimitive *pPrim;
+    CompositeInfo compInfo;
+    jint ix1, iy1, ix2, iy2;
+
+    if ((dy1 == 0 && dx1 == 0) || (dy2 == 0 && dx2 == 0)) {
+        return;
+    }
+
+    /*
+     * Sort parallelogram by y values, ensure that each delta vector
+     * has a non-negative y delta.
+     */
+    SORT_PGRAM(x0, y0, dx1, dy1, dx2, dy2, );
+
+    PGRAM_MIN_MAX(ix1, ix2, x0, dx1, dx2, JNI_TRUE);
+    iy1 = (jint) floor(y0);
+    iy2 = (jint) ceil(y0 + dy1 + dy2);
+
+    pPrim = GetNativePrim(env, self);
+    if (pPrim == NULL) {
+        return;
+    }
+    if (pPrim->pCompType->getCompInfo != NULL) {
+        (*pPrim->pCompType->getCompInfo)(env, &compInfo, comp);
+    }
+
+    sdOps = SurfaceData_GetOps(env, sData);
+    if (sdOps == 0) {
+        return;
+    }
+
+    GrPrim_Sg2dGetClip(env, sg2d, &rasInfo.bounds);
+    SurfaceData_IntersectBoundsXYXY(&rasInfo.bounds, ix1, iy1, ix2, iy2);
+    if (rasInfo.bounds.y2 <= rasInfo.bounds.y1 ||
+        rasInfo.bounds.x2 <= rasInfo.bounds.x1)
+    {
+        return;
+    }
+
+    if (sdOps->Lock(env, sdOps, &rasInfo, pPrim->dstflags) != SD_SUCCESS) {
+        return;
+    }
+
+    ix1 = rasInfo.bounds.x1;
+    iy1 = rasInfo.bounds.y1;
+    ix2 = rasInfo.bounds.x2;
+    iy2 = rasInfo.bounds.y2;
+    if (ix2 > ix1 && iy2 > iy1) {
+        jint width = ix2 - ix1;
+        jint color = GrPrim_Sg2dGetEaRGB(env, sg2d);
+        unsigned char localmask[MASK_BUF_LEN];
+        unsigned char *pMask = ((width > MASK_BUF_LEN)
+                                ? malloc(width)
+                                : localmask);
+
+        sdOps->GetRasInfo(env, sdOps, &rasInfo);
+        if (rasInfo.rasBase != NULL && pMask != NULL) {
+            void *pDst = PtrCoord(rasInfo.rasBase,
+                                  ix1, rasInfo.pixelStride,
+                                  iy1, rasInfo.scanStride);
+            if (dy1 == 0 && dx2 == 0) {
+                if (dx1 < 0) {
+                    // We sorted by Y above, but not by X
+                    x0 += dx1;
+                    dx1 = -dx1;
+                }
+                fillAARect(pPrim, &rasInfo, &compInfo,
+                           color, pMask, pDst,
+                           x0, y0, x0+dx1, y0+dy2);
+            } else if (dx1 == 0 && dy2 == 0) {
+                if (dx2 < 0) {
+                    // We sorted by Y above, but not by X
+                    x0 += dx2;
+                    dx2 = -dx2;
+                }
+                fillAARect(pPrim, &rasInfo, &compInfo,
+                           color, pMask, pDst,
+                           x0, y0, x0+dx2, y0+dy1);
+            } else {
+                fillAAPgram(pPrim, &rasInfo, &compInfo,
+                            color, pMask, pDst,
+                            x0, y0, dx1, dy1, dx2, dy2);
+            }
+        }
+        SurfaceData_InvokeRelease(env, sdOps, &rasInfo);
+        if (pMask != NULL && pMask != localmask) {
+            free(pMask);
+        }
     }
     SurfaceData_InvokeUnlock(env, sdOps, &rasInfo);
 }
+
+/* Main function to fill a double pair of (inner and outer) parallelograms */
+static void
+drawAAPgram(NativePrimitive *pPrim, SurfaceDataRasInfo *pRasInfo,
+            CompositeInfo *pCompInfo, jint color, unsigned char *pMask,
+            void *pDst,
+            jdouble ox0, jdouble oy0,
+            jdouble dx1, jdouble dy1,
+            jdouble dx2, jdouble dy2,
+            jdouble ldx1, jdouble ldy1,
+            jdouble ldx2, jdouble ldy2)
+{
+    jint cx1 = pRasInfo->bounds.x1;
+    jint cy1 = pRasInfo->bounds.y1;
+    jint cx2 = pRasInfo->bounds.x2;
+    jint cy2 = pRasInfo->bounds.y2;
+    jint width = cx2 - cx1;
+    EdgeInfo edges[8];
+    jfloat localaccum[MASK_BUF_LEN + 1];
+    jfloat *pAccum;
+
+    if (!storePgram(edges + 0, edges + 6,
+                    ox0, oy0,
+                    dx1 + ldx1, dy1 + ldy1,
+                    dx2 + ldx2, dy2 + ldy2,
+                    cx1, cy1, cx2, cy2,
+                    JNI_FALSE))
+    {
+        /* If outer pgram does not contribute, then inner cannot either. */
+        return;
+    }
+    storePgram(edges + 2, edges + 4,
+               ox0 + ldx1 + ldx2, oy0 + ldy1 + ldy2,
+               dx1 - ldx1, dy1 - ldy1,
+               dx2 - ldx2, dy2 - ldy2,
+               cx1, cy1, cx2, cy2,
+               JNI_TRUE);
+
+    pAccum = ((width > MASK_BUF_LEN)
+              ? malloc((width + 1) * sizeof(jfloat))
+              : localaccum);
+    if (pAccum == NULL) {
+        return;
+    }
+    memset(pAccum, 0, (width+1) * sizeof(jfloat));
+
+    while (cy1 < cy2) {
+        jint lmin, lmax, rmin, rmax;
+        jint moff, x;
+        jdouble accum;
+        unsigned char lastcov;
+
+        lmin = rmin = width + 2;
+        lmax = rmax = 0;
+        ACCUM_EDGE(&edges[0], pAccum, lmin, lmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[1], pAccum, lmin, lmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[2], pAccum, lmin, lmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[3], pAccum, lmin, lmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[4], pAccum, rmin, rmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[5], pAccum, rmin, rmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[6], pAccum, rmin, rmax,
+                   cx1, cy1, cx2, cy1+1);
+        ACCUM_EDGE(&edges[7], pAccum, rmin, rmax,
+                   cx1, cy1, cx2, cy1+1);
+        if (lmax > width) {
+            lmax = width; /* Extra col has data we do not need. */
+        }
+        if (rmax > width) {
+            rmax = width; /* Extra col has data we do not need. */
+        }
+        /* If ranges overlap, handle both in the first pass. */
+        if (rmin <= lmax) {
+            lmax = rmax;
+        }
+
+        x = lmin;
+        accum = 0.0;
+        moff = 0;
+        lastcov = 0;
+        while (x < lmax) {
+            accum += pAccum[x];
+            pAccum[x] = 0.0f;
+            pMask[moff++] = lastcov = DblToMask(accum);
+            x++;
+        }
+        /* Check for an empty or solidcenter section. */
+        if (lastcov == 0 || lastcov == 0xFF) {
+            jint endx;
+            void *pRow;
+
+            /* First process the existing partial coverage data. */
+            if (moff > 0) {
+                pRow = PtrCoord(pDst, x-moff, pRasInfo->pixelStride, 0, 0);
+                (*pPrim->funcs.maskfill)(pRow,
+                                         pMask, 0, 0,
+                                         moff, 1,
+                                         color, pRasInfo,
+                                         pPrim, pCompInfo);
+                moff = 0;
+            }
+
+            /* Where does the center section end? */
+            /* If there is no right AA edge in the accum buffer, then */
+            /* the right edge was beyond the clip, so fill out to width */
+            endx = (rmin < rmax) ? rmin : width;
+            if (x < endx) {
+                if (lastcov == 0xFF) {
+                    pRow = PtrCoord(pDst, x, pRasInfo->pixelStride, 0, 0);
+                    (*pPrim->funcs.maskfill)(pRow,
+                                             NULL, 0, 0,
+                                             endx - x, 1,
+                                             color, pRasInfo,
+                                             pPrim, pCompInfo);
+                }
+                x = endx;
+            }
+        } else if (rmin >= rmax) {
+            /* We are not at 0 coverage, but there is no right edge, */
+            /* force a right edge so we process pixels out to width. */
+            rmax = width;
+        }
+        /* The following loop will process the right AA edge and/or any */
+        /* partial coverage center section not processed above. */
+        while (x < rmax) {
+            accum += pAccum[x];
+            pAccum[x] = 0.0f;
+            pMask[moff++] = lastcov = DblToMask(accum);
+            x++;
+        }
+        if (moff > 0) {
+            void *pRow = PtrCoord(pDst, x-moff, pRasInfo->pixelStride, 0, 0);
+            (*pPrim->funcs.maskfill)(pRow,
+                                     pMask, 0, 0,
+                                     moff, 1,
+                                     color, pRasInfo,
+                                     pPrim, pCompInfo);
+        }
+        if (lastcov == 0xFF && x < width) {
+            void *pRow = PtrCoord(pDst, x, pRasInfo->pixelStride, 0, 0);
+            (*pPrim->funcs.maskfill)(pRow,
+                                     NULL, 0, 0,
+                                     width - x, 1,
+                                     color, pRasInfo,
+                                     pPrim, pCompInfo);
+        }
+        pDst = PtrAddBytes(pDst, pRasInfo->scanStride);
+        cy1++;
+    }
+    if (pAccum != localaccum) {
+        free(pAccum);
+    }
+}
+
+/*
+ * Class:     sun_java2d_loops_MaskFill
+ * Method:    DrawAAPgram
+ * Signature: (Lsun/java2d/SunGraphics2D;Lsun/java2d/SurfaceData;Ljava/awt/Composite;DDDDDDDD)V
+ */
+JNIEXPORT void JNICALL
+Java_sun_java2d_loops_MaskFill_DrawAAPgram
+    (JNIEnv *env, jobject self,
+     jobject sg2d, jobject sData, jobject comp,
+     jdouble x0, jdouble y0,
+     jdouble dx1, jdouble dy1,
+     jdouble dx2, jdouble dy2,
+     jdouble lw1, jdouble lw2)
+{
+    SurfaceDataOps *sdOps;
+    SurfaceDataRasInfo rasInfo;
+    NativePrimitive *pPrim;
+    CompositeInfo compInfo;
+    jint ix1, iy1, ix2, iy2;
+    jdouble ldx1, ldy1, ldx2, ldy2;
+    jdouble ox0, oy0;
+
+    if ((dy1 == 0 && dx1 == 0) || (dy2 == 0 && dx2 == 0)) {
+        return;
+    }
+
+    /*
+     * Sort parallelogram by y values, ensure that each delta vector
+     * has a non-negative y delta.
+     */
+    SORT_PGRAM(x0, y0, dx1, dy1, dx2, dy2,
+               v = lw1; lw1 = lw2; lw2 = v;);
+
+    // dx,dy for line width in the "1" and "2" directions.
+    ldx1 = dx1 * lw1;
+    ldy1 = dy1 * lw1;
+    ldx2 = dx2 * lw2;
+    ldy2 = dy2 * lw2;
+
+    // calculate origin of the outer parallelogram
+    ox0 = x0 - (ldx1 + ldx2) / 2.0;
+    oy0 = y0 - (ldy1 + ldy2) / 2.0;
+
+    if (lw1 >= 1.0 || lw2 >= 1.0) {
+        /* Only need to fill an outer pgram if the interior no longer
+         * has a hole in it (i.e. if either of the line width ratios
+         * were greater than or equal to 1.0).
+         */
+        Java_sun_java2d_loops_MaskFill_FillAAPgram(env, self,
+                                                   sg2d, sData, comp,
+                                                   ox0, oy0,
+                                                   dx1 + ldx1, dy1 + ldy1,
+                                                   dx2 + ldx2, dy2 + ldy2);
+        return;
+    }
+
+    PGRAM_MIN_MAX(ix1, ix2, ox0, dx1+ldx1, dx2+ldx2, JNI_TRUE);
+    iy1 = (jint) floor(oy0);
+    iy2 = (jint) ceil(oy0 + dy1 + ldy1 + dy2 + ldy2);
+
+    pPrim = GetNativePrim(env, self);
+    if (pPrim == NULL) {
+        return;
+    }
+    if (pPrim->pCompType->getCompInfo != NULL) {
+        (*pPrim->pCompType->getCompInfo)(env, &compInfo, comp);
+    }
+
+    sdOps = SurfaceData_GetOps(env, sData);
+    if (sdOps == 0) {
+        return;
+    }
+
+    GrPrim_Sg2dGetClip(env, sg2d, &rasInfo.bounds);
+    SurfaceData_IntersectBoundsXYXY(&rasInfo.bounds, ix1, iy1, ix2, iy2);
+    if (rasInfo.bounds.y2 <= rasInfo.bounds.y1 ||
+        rasInfo.bounds.x2 <= rasInfo.bounds.x1)
+    {
+        return;
+    }
+
+    if (sdOps->Lock(env, sdOps, &rasInfo, pPrim->dstflags) != SD_SUCCESS) {
+        return;
+    }
+
+    ix1 = rasInfo.bounds.x1;
+    iy1 = rasInfo.bounds.y1;
+    ix2 = rasInfo.bounds.x2;
+    iy2 = rasInfo.bounds.y2;
+    if (ix2 > ix1 && iy2 > iy1) {
+        jint width = ix2 - ix1;
+        jint color = GrPrim_Sg2dGetEaRGB(env, sg2d);
+        unsigned char localmask[MASK_BUF_LEN];
+        unsigned char *pMask = ((width > MASK_BUF_LEN)
+                                ? malloc(width)
+                                : localmask);
+
+        sdOps->GetRasInfo(env, sdOps, &rasInfo);
+        if (rasInfo.rasBase != NULL && pMask != NULL) {
+            void *pDst = PtrCoord(rasInfo.rasBase,
+                                  ix1, rasInfo.pixelStride,
+                                  iy1, rasInfo.scanStride);
+            /*
+             * NOTE: aligned rects could probably be drawn
+             * even faster with a little work here.
+             * if (dy1 == 0 && dx2 == 0) {
+             *     drawAARect(pPrim, &rasInfo, &compInfo,
+             *                color, pMask, pDst,
+             *                ox0, oy0, ox0+dx1+ldx1, oy0+dy2+ldy2, ldx1, ldy2);
+             * } else if (dx1 == 0 && dy2 == 0) {
+             *     drawAARect(pPrim, &rasInfo, &compInfo,
+             *                color, pMask, pDst,
+             *                ox0, oy0, ox0+dx2+ldx2, oy0+dy1+ldy1, ldx2, ldy1);
+             * } else {
+             */
+            drawAAPgram(pPrim, &rasInfo, &compInfo,
+                        color, pMask, pDst,
+                        ox0, oy0,
+                        dx1, dy1, dx2, dy2,
+                        ldx1, ldy1, ldx2, ldy2);
+            /*
+             * }
+             */
+        }
+        SurfaceData_InvokeRelease(env, sdOps, &rasInfo);
+        if (pMask != NULL && pMask != localmask) {
+            free(pMask);
+        }
+    }
+    SurfaceData_InvokeUnlock(env, sdOps, &rasInfo);
+}
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/share/native/sun/java2d/loops/ParallelogramUtils.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ openjdk/jdk/src/share/native/sun/java2d/loops/ParallelogramUtils.h	Tue Dec 14 13:25:29 2010 -0800
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ParallelogramUtils_h_Included
+#define ParallelogramUtils_h_Included
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PGRAM_MIN_MAX(bmin, bmax, v0, dv1, dv2, AA) \
+    do { \
+        double vmin, vmax; \
+        if (dv1 < 0) { \
+            vmin = v0+dv1; \
+            vmax = v0; \
+        } else { \
+            vmin = v0; \
+            vmax = v0+dv1; \
+        } \
+        if (dv2 < 0) { \
+            vmin += dv2; \
+        } else { \
+            vmax += dv2; \
+        } \
+        if (AA) { \
+            bmin = (jint) floor(vmin); \
+            bmax = (jint) ceil(vmax); \
+        } else { \
+            bmin = (jint) floor(vmin + 0.5); \
+            bmax = (jint) floor(vmax + 0.5); \
+        } \
+    } while(0)
+
+#define PGRAM_INIT_X(starty, x, y, slope) \
+    (DblToLong((x) + (slope) * ((starty)+0.5 - (y))) + LongOneHalf - 1)
+
+/*
+ * Sort parallelogram by y values, ensure that each delta vector
+ * has a non-negative y delta.
+ */
+#define SORT_PGRAM(x0, y0, dx1, dy1, dx2, dy2, OTHER_SWAP_CODE) \
+    do { \
+        if (dy1 < 0) { \
+            x0 += dx1;  y0 += dy1; \
+            dx1 = -dx1; dy1 = -dy1; \
+        } \
+        if (dy2 < 0) { \
+            x0 += dx2;  y0 += dy2; \
+            dx2 = -dx2; dy2 = -dy2; \
+        } \
+        /* Sort delta vectors so dxy1 is left of dxy2. */ \
+        if (dx1 * dy2 > dx2 * dy1) { \
+            double v; \
+            v = dx1; dx1 = dx2; dx2 = v; \
+            v = dy1; dy1 = dy2; dy2 = v; \
+            OTHER_SWAP_CODE \
+        } \
+    } while(0)
+
+#endif /* ParallelogramUtils_h_Included */
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/solaris/native/sun/java2d/loops/vis_IntArgbPre_Mask.c
--- openjdk.orig/jdk/src/solaris/native/sun/java2d/loops/vis_IntArgbPre_Mask.c	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/solaris/native/sun/java2d/loops/vis_IntArgbPre_Mask.c	Tue Dec 14 13:25:29 2010 -0800
@@ -517,13 +517,15 @@
         ADD_SUFF(AnyIntSetRect)(pRasInfo, 0, 0, width, height,
                                 fgColor, pPrim, pCompInfo);
 #else
+        void *pBase = pRasInfo->rasBase;
+        pRasInfo->rasBase = rasBase;
         if (cnstA != 0xff) {
             fgColor = (cnstA << 24) | (cnstR << 16) | (cnstG << 8) | cnstB;
         }
         ADD_SUFF(AnyIntSetRect)(pRasInfo,
-                                pRasInfo->bounds.x1, pRasInfo->bounds.y1,
-                                pRasInfo->bounds.x2, pRasInfo->bounds.y2,
+                                0, 0, width, height,
                                 fgColor, pPrim, pCompInfo);
+        pRasInfo->rasBase = pBase;
 #endif
         return;
     }
@@ -582,11 +584,13 @@
     }
 
     if (pMask == NULL) {
+        void *pBase = pRasInfo->rasBase;
+        pRasInfo->rasBase = rasBase;
         fgColor = (cnstR << 24) | (cnstG << 16) | (cnstB << 8) | cnstA;
         ADD_SUFF(Any4ByteSetRect)(pRasInfo,
-                                  pRasInfo->bounds.x1, pRasInfo->bounds.y1,
-                                  pRasInfo->bounds.x2, pRasInfo->bounds.y2,
+                                  0, 0, width, height,
                                   fgColor, pPrim, pCompInfo);
+        pRasInfo->rasBase = pBase;
         return;
     }
 
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 src/solaris/native/sun/java2d/loops/vis_SrcMaskFill.c
--- openjdk.orig/jdk/src/solaris/native/sun/java2d/loops/vis_SrcMaskFill.c	Fri Dec 10 16:14:04 2010 -0800
+++ openjdk/jdk/src/solaris/native/sun/java2d/loops/vis_SrcMaskFill.c	Tue Dec 14 13:25:29 2010 -0800
@@ -150,10 +150,12 @@
     }
 
     if (pMask == NULL) {
+        void *pBase = pRasInfo->rasBase;
+        pRasInfo->rasBase = rasBase;
         ADD_SUFF(AnyIntSetRect)(pRasInfo,
-                                pRasInfo->bounds.x1, pRasInfo->bounds.y1,
-                                pRasInfo->bounds.x2, pRasInfo->bounds.y2,
+                                0, 0, width, height,
                                 fgColor, pPrim, pCompInfo);
+        pRasInfo->rasBase = pBase;
         return;
     }
 
@@ -214,15 +216,17 @@
     cnstB = (fgColor      ) & 0xff;
 
     if (pMask == NULL) {
+        void *pBase = pRasInfo->rasBase;
+        pRasInfo->rasBase = rasBase;
         if (cnstA == 0) {
             fgColor = 0;
         } else {
             fgColor = (fgColor << 8) | cnstA;
         }
         ADD_SUFF(Any4ByteSetRect)(pRasInfo,
-                                  pRasInfo->bounds.x1, pRasInfo->bounds.y1,
-                                  pRasInfo->bounds.x2, pRasInfo->bounds.y2,
+                                  0, 0, width, height,
                                   fgColor, pPrim, pCompInfo);
+        pRasInfo->rasBase = pBase;
         return;
     }
 
@@ -390,10 +394,12 @@
     if (cnstA == 0) fgColor = 0;
 
     if (pMask == NULL) {
+        void *pBase = pRasInfo->rasBase;
+        pRasInfo->rasBase = rasBase;
         ADD_SUFF(AnyIntSetRect)(pRasInfo,
-                                pRasInfo->bounds.x1, pRasInfo->bounds.y1,
-                                pRasInfo->bounds.x2, pRasInfo->bounds.y2,
+                                0, 0, width, height,
                                 fgColor, pPrim, pCompInfo);
+        pRasInfo->rasBase = pBase;
         return;
     }
 
@@ -458,10 +464,12 @@
     }
 
     if (pMask == NULL) {
+        void *pBase = pRasInfo->rasBase;
+        pRasInfo->rasBase = rasBase;
         ADD_SUFF(AnyIntSetRect)(pRasInfo,
-                                pRasInfo->bounds.x1, pRasInfo->bounds.y1,
-                                pRasInfo->bounds.x2, pRasInfo->bounds.y2,
+                                0, 0, width, height,
                                 fgColor, pPrim, pCompInfo);
+        pRasInfo->rasBase = pBase;
         return;
     }
 
@@ -526,10 +534,12 @@
     }
 
     if (pMask == NULL) {
+        void *pBase = pRasInfo->rasBase;
+        pRasInfo->rasBase = rasBase;
         ADD_SUFF(Any3ByteSetRect)(pRasInfo,
-                                  pRasInfo->bounds.x1, pRasInfo->bounds.y1,
-                                  pRasInfo->bounds.x2, pRasInfo->bounds.y2,
+                                  0, 0, width, height,
                                   fgColor, pPrim, pCompInfo);
+        pRasInfo->rasBase = pBase;
         return;
     }
 
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 test/java/awt/Graphics2D/RenderClipTest/6766342.tests
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ openjdk/jdk/test/java/awt/Graphics2D/RenderClipTest/6766342.tests	Tue Dec 14 13:25:29 2010 -0800
@@ -0,0 +1,3 @@
+Filled AA Pure Rect(5, 29.4, 10, 10)
+Stroked AA Pure Rect(5, 4.4, 10, 10)
+Stroked AA Line(20, 20, -10, 20)
diff -r 0eeac8ca33e3 -r 4d6f9aaa2600 test/java/awt/Graphics2D/RenderClipTest/RenderClipTest.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ openjdk/jdk/test/java/awt/Graphics2D/RenderClipTest/RenderClipTest.java	Tue Dec 14 13:25:29 2010 -0800
@@ -0,0 +1,1634 @@
+/*
+ * Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6766342
+ * @summary Tests clipping invariance for AA rectangle and line primitives
+ * @run main RenderClipTest -strict -readfile 6766342.tests
+ * @run main RenderClipTest -rectsuite -count 10
+ */
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import java.util.Vector;
+import java.io.*;
+
+public class RenderClipTest {
+    public static double randDblCoord() {
+        return Math.random()*60 - 10;
+    }
+
+    public static float randFltCoord() {
+        return (float) randDblCoord();
+    }
+
+    public static int randIntCoord() {
+        return (int) Math.round(randDblCoord());
+    }
+
+    public static int randInt(int n) {
+        return ((int) (Math.random() * (n*4))) >> 2;
+    }
+
+    static int numtests;
+    static int numerrors;
+    static int numfillfailures;
+    static int numstrokefailures;
+    static int maxerr;
+
+    static boolean useAA;
+    static boolean strokePure;
+    static boolean testFill;
+    static boolean testDraw;
+    static boolean silent;
+    static boolean verbose;
+    static boolean strict;
+    static boolean showErrors;
+    static float lw;
+    static double rot;
+
+    static BufferedImage imgref;
+    static BufferedImage imgtst;
+
+    static Graphics2D grefclear;
+    static Graphics2D gtstclear;
+    static Graphics2D grefrender;
+    static Graphics2D gtstrender;
+
+    public static abstract class AnnotatedRenderOp {
+        public static AnnotatedRenderOp parse(String str) {
+            AnnotatedRenderOp ar;
+            if (((ar = Cubic.tryparse(str)) != null) ||
+                ((ar = Quad.tryparse(str)) != null) ||
+                ((ar = Poly.tryparse(str)) != null) ||
+                ((ar = Path.tryparse(str)) != null) ||
+                ((ar = Rect.tryparse(str)) != null) ||
+                ((ar = Line.tryparse(str)) != null) ||
+                ((ar = RectMethod.tryparse(str)) != null) ||
+                ((ar = LineMethod.tryparse(str)) != null))
+            {
+                return ar;
+            }
+            System.err.println("Unable to parse shape: "+str);
+            return null;
+        }
+
+        public abstract void randomize();
+
+        public abstract void fill(Graphics2D g2d);
+
+        public abstract void draw(Graphics2D g2d);
+    }
+
+    public static abstract class AnnotatedShapeOp extends AnnotatedRenderOp {
+        public abstract Shape getShape();
+
+        public void fill(Graphics2D g2d) {
+            g2d.fill(getShape());
+        }
+
+        public void draw(Graphics2D g2d) {
+            g2d.draw(getShape());
+        }
+    }
+
+    public static void usage(String err) {
+        if (err != null) {
+            System.err.println(err);
+        }
+        System.err.println("usage: java RenderClipTest "+
+                           "[-read[file F]] [-rectsuite] [-fill] [-draw]");
+        System.err.println("                           "+
+                           "[-aa] [-pure] [-lw N] [-rot N]");
+        System.err.println("                           "+
+                           "[-rectmethod] [-linemethod] [-rect] [-line]");
+        System.err.println("                           "+
+                           "[-cubic] [-quad] [-poly] [-path]");
+        System.err.println("                           "+
+                           "[-silent] [-verbose] [-showerr] [-count N]");
+        System.err.println("                           "+
+                           "[-strict] [-usage]");
+        System.err.println("    -read         Read test data from stdin");
+        System.err.println("    -readfile F   Read test data from file F");
+        System.err.println("    -rectsuite    Run a suite of rect/line tests");
+        System.err.println("    -fill         Test g.fill*(...)");
+        System.err.println("    -draw         Test g.draw*(...)");
+        System.err.println("    -aa           Use antialiased rendering");
+        System.err.println("    -pure         Use STROKE_PURE hint");
+        System.err.println("    -lw N         Test line widths of N "+
+                           "(default 1.0)");
+        System.err.println("    -rot N        Test rotation by N degrees "+
+                           "(default 0.0)");
+        System.err.println("    -rectmethod   Test fillRect/drawRect methods");
+        System.err.println("    -linemethod   Test drawLine method");
+        System.err.println("    -rect         Test Rectangle2D shapes");
+        System.err.println("    -line         Test Line2D shapes");
+        System.err.println("    -cubic        Test CubicCurve2D shapes");
+        System.err.println("    -quad         Test QuadCurve2D shapes");
+        System.err.println("    -poly         Test Polygon shapes");
+        System.err.println("    -path         Test GeneralPath shapes");
+        System.err.println("    -silent       Do not print out error curves");
+        System.err.println("    -verbose      Print out progress info");
+        System.err.println("    -showerr      Display errors on screen");
+        System.err.println("    -count N      N tests per shape, then exit "+
+                           "(default 1000)");
+        System.err.println("    -strict       All failures are important");
+        System.err.println("    -usage        Print this help, then exit");
+        System.exit((err != null) ? -1 : 0);
+    }
+
+    public static void main(String argv[]) {
+        boolean readTests = false;
+        String readFile = null;
+        boolean rectsuite = false;
+        int count = 1000;
+        lw = 1.0f;
+        rot = 0.0;
+        Vector<AnnotatedRenderOp> testOps = new Vector<AnnotatedRenderOp>();
+        for (int i = 0; i < argv.length; i++) {
+            String arg = argv[i].toLowerCase();
+            if (arg.equals("-aa")) {
+                useAA = true;
+            } else if (arg.equals("-pure")) {
+                strokePure = true;
+            } else if (arg.equals("-fill")) {
+                testFill = true;
+            } else if (arg.equals("-draw")) {
+                testDraw = true;
+            } else if (arg.equals("-lw")) {
+                if (i+1 >= argv.length) {
+                    usage("Missing argument: "+argv[i]);
+                }
+                lw = Float.parseFloat(argv[++i]);
+            } else if (arg.equals("-rot")) {
+                if (i+1 >= argv.length) {
+                    usage("Missing argument: "+argv[i]);
+                }
+                rot = Double.parseDouble(argv[++i]);
+            } else if (arg.equals("-cubic")) {
+                testOps.add(new Cubic());
+            } else if (arg.equals("-quad")) {
+                testOps.add(new Quad());
+            } else if (arg.equals("-poly")) {
+                testOps.add(new Poly());
+            } else if (arg.equals("-path")) {
+                testOps.add(new Path());
+            } else if (arg.equals("-rect")) {
+                testOps.add(new Rect());
+            } else if (arg.equals("-line")) {
+                testOps.add(new Line());
+            } else if (arg.equals("-rectmethod")) {
+                testOps.add(new RectMethod());
+            } else if (arg.equals("-linemethod")) {
+                testOps.add(new LineMethod());
+            } else if (arg.equals("-verbose")) {
+                verbose = true;
+            } else if (arg.equals("-strict")) {
+                strict = true;
+            } else if (arg.equals("-silent")) {
+                silent = true;
+            } else if (arg.equals("-showerr")) {
+                showErrors = true;
+            } else if (arg.equals("-readfile")) {
+                if (i+1 >= argv.length) {
+                    usage("Missing argument: "+argv[i]);
+                }
+                readTests = true;
+                readFile = argv[++i];
+            } else if (arg.equals("-read")) {
+                readTests = true;
+                readFile = null;
+            } else if (arg.equals("-rectsuite")) {
+                rectsuite = true;
+            } else if (arg.equals("-count")) {
+                if (i+1 >= argv.length) {
+                    usage("Missing argument: "+argv[i]);
+                }
+                count = Integer.parseInt(argv[++i]);
+            } else if (arg.equals("-usage")) {
+                usage(null);
+            } else {
+                usage("Unknown argument: "+argv[i]);
+            }
+        }
+        if (readTests) {
+            if (rectsuite || testDraw || testFill ||
+                useAA || strokePure ||
+                lw != 1.0f || rot != 0.0 ||
+                testOps.size() > 0)
+            {
+                usage("Should not specify test types with -read options");
+            }
+        } else if (rectsuite) {
+            if (testDraw || testFill ||
+                useAA || strokePure ||
+                lw != 1.0f || rot != 0.0 ||
+                testOps.size() > 0)
+            {
+                usage("Should not specify test types with -rectsuite option");
+            }
+        } else {
+            if (!testDraw && !testFill) {
+                usage("No work: Must specify one or both of "+
+                      "-fill or -draw");
+            }
+            if (testOps.size() == 0) {
+                usage("No work: Must specify one or more of "+
+                      "-rect[method], -line[method], "+
+                      "-cubic, -quad, -poly, or -path");
+            }
+        }
+        initImages();
+        if (readTests) {
+            try {
+                InputStream is;
+                if (readFile == null) {
+                    is = System.in;
+                } else {
+                    File f =
+                        new File(System.getProperty("test.src", "."),
+                                 readFile);
+                    is = new FileInputStream(f);
+                }
+                parseAndRun(is);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        } else if (rectsuite) {
+            runRectSuite(count);
+        } else {
+            initGCs();
+            for (int k = 0; k < testOps.size(); k++) {
+                AnnotatedRenderOp ar = testOps.get(k);
+                runRandomTests(ar, count);
+            }
+            disposeGCs();
+        }
+        grefclear.dispose();
+        gtstclear.dispose();
+        grefclear = gtstclear = null;
+        reportStatistics();
+    }
+
+    public static int reportStatistics() {
+        String connector = "";
+        if (numfillfailures > 0) {
+            System.out.print(numfillfailures+" fills ");
+            connector = "and ";
+        }
+        if (numstrokefailures > 0) {
+            System.out.print(connector+numstrokefailures+" strokes ");
+        }
+        int totalfailures = numfillfailures + numstrokefailures;
+        if (totalfailures == 0) {
+            System.out.print("0 ");
+        }
+        System.out.println("out of "+numtests+" tests failed...");
+        int critical = numerrors;
+        if (strict) {
+            critical += totalfailures;
+        }
+        if (critical > 0) {
+            throw new RuntimeException(critical+" tests had critical errors");
+        }
+        System.out.println("No tests had critical errors");
+        return (numerrors+totalfailures);
+    }
+
+    public static void runRectSuite(int count) {
+        AnnotatedRenderOp ops[] = {
+            new Rect(),
+            new RectMethod(),
+            new Line(),
+            new LineMethod(),
+        };
+        // Sometimes different fill algorithms are chosen for
+        // thin and wide line modes, make sure we test both...
+        float filllinewidths[] = { 0.0f, 2.0f };
+        float drawlinewidths[] = { 0.0f, 0.5f, 1.0f,
+                                   2.0f, 2.5f,
+                                   5.0f, 5.3f };
+        double rotations[] = { 0.0, 15.0, 90.0,
+                               135.0, 180.0,
+                               200.0, 270.0,
+                               300.0};
+        for (AnnotatedRenderOp ar: ops) {
+            for (double r: rotations) {
+                rot = r;
+                for (int i = 0; i < 8; i++) {
+                    float linewidths[];
+                    if ((i & 1) == 0) {
+                        if ((ar instanceof Line) ||
+                            (ar instanceof LineMethod))
+                        {
+                            continue;
+                        }
+                        testFill = true;
+                        testDraw = false;
+                        linewidths = filllinewidths;
+                    } else {
+                        testFill = false;
+                        testDraw = true;
+                        linewidths = drawlinewidths;
+                    }
+                    useAA = ((i & 2) != 0);
+                    strokePure = ((i & 4) != 0);
+                    for (float w : linewidths) {
+                        lw = w;
+                        runSuiteTests(ar, count);
+                    }
+                }
+            }
+        }
+    }
+
+    public static void runSuiteTests(AnnotatedRenderOp ar, int count) {
+        if (verbose) {
+            System.out.print("Running ");
+            System.out.print(testFill ? "Fill " : "Draw ");
+            System.out.print(BaseName(ar));
+            if (useAA) {
+                System.out.print(" AA");
+            }
+            if (strokePure) {
+                System.out.print(" Pure");
+            }
+            if (lw != 1.0f) {
+                System.out.print(" lw="+lw);
+            }
+            if (rot != 0.0f) {
+                System.out.print(" rot="+rot);
+            }
+            System.out.println();
+        }
+        initGCs();
+        runRandomTests(ar, count);
+        disposeGCs();
+    }
+
+    public static String BaseName(AnnotatedRenderOp ar) {
+        String s = ar.toString();
+        int leftparen = s.indexOf('(');
+        if (leftparen >= 0) {
+            s = s.substring(0, leftparen);
+        }
+        return s;
+    }
+
+    public static void runRandomTests(AnnotatedRenderOp ar, int count) {
+        for (int i = 0; i < count; i++) {
+            ar.randomize();
+            if (testDraw) {
+                test(ar, false);
+            }
+            if (testFill) {
+                test(ar, true);
+            }
+        }
+    }
+
+    public static void initImages() {
+        imgref = new BufferedImage(40, 40, BufferedImage.TYPE_INT_RGB);
+        imgtst = new BufferedImage(40, 40, BufferedImage.TYPE_INT_RGB);
+        grefclear = imgref.createGraphics();
+        gtstclear = imgtst.createGraphics();
+        grefclear.setColor(Color.white);
+        gtstclear.setColor(Color.white);
+    }
+
+    public static void initGCs() {
+        grefrender = imgref.createGraphics();
+        gtstrender = imgtst.createGraphics();
+        gtstrender.clipRect(10, 10, 20, 20);
+        grefrender.setColor(Color.blue);
+        gtstrender.setColor(Color.blue);
+        if (lw != 1.0f) {
+            BasicStroke bs = new BasicStroke(lw);
+            grefrender.setStroke(bs);
+            gtstrender.setStroke(bs);
+        }
+        if (rot != 0.0) {
+            double rotrad = Math.toRadians(rot);
+            grefrender.rotate(rotrad, 20, 20);
+            gtstrender.rotate(rotrad, 20, 20);
+        }
+        if (strokePure) {
+            grefrender.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+                                        RenderingHints.VALUE_STROKE_PURE);
+            gtstrender.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+                                        RenderingHints.VALUE_STROKE_PURE);
+        }
+        if (useAA) {
+            grefrender.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                        RenderingHints.VALUE_ANTIALIAS_ON);
+            gtstrender.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                        RenderingHints.VALUE_ANTIALIAS_ON);
+            maxerr = 1;
+        }
+    }
+
+    public static void disposeGCs() {
+        grefrender.dispose();
+        gtstrender.dispose();
+        grefrender = gtstrender = null;
+    }
+
+    public static void parseAndRun(InputStream in) throws IOException {
+        BufferedReader br = new BufferedReader(new InputStreamReader(in));
+        String str;
+        while ((str = br.readLine()) != null) {
+            if (str.startsWith("Stroked ") || str.startsWith("Filled ")) {
+                parseTest(str);
+                continue;
+            }
+            if (str.startsWith("Running ")) {
+                continue;
+            }
+            if (str.startsWith("Failed: ")) {
+                continue;
+            }
+            if (str.indexOf(" out of ") > 0 &&
+                str.indexOf(" tests failed...") > 0)
+            {
+                continue;
+            }
+            if (str.indexOf(" tests had critical errors") > 0) {
+                continue;
+            }
+            System.err.println("Unparseable line: "+str);
+        }
+    }
+
+    public static void parseTest(String origstr) {
+        String str = origstr;
+        boolean isfill = false;
+        useAA = strokePure = false;
+        lw = 1.0f;
+        rot = 0.0;
+        if (str.startsWith("Stroked ")) {
+            str = str.substring(8);
+            isfill = false;
+        } else if (str.startsWith("Filled ")) {
+            str = str.substring(7);
+            isfill = true;
+        } else {
+            System.err.println("Unparseable test line: "+origstr);
+        }
+        if (str.startsWith("AA ")) {
+            str = str.substring(3);
+            useAA = true;
+        }
+        if (str.startsWith("Pure ")) {
+            str = str.substring(5);
+            strokePure = true;
+        }
+        if (str.startsWith("Lw=")) {
+            int index = str.indexOf(' ', 3);
+            if (index > 0) {
+                lw = Float.parseFloat(str.substring(3, index));
+                str = str.substring(index+1);
+            }
+        }
+        if (str.startsWith("Rot=")) {
+            int index = str.indexOf(' ', 4);
+            if (index > 0) {
+                rot = Double.parseDouble(str.substring(4, index));
+                str = str.substring(index+1);
+            }
+        }
+        AnnotatedRenderOp ar = AnnotatedRenderOp.parse(str);
+        if (ar != null) {
+            initGCs();
+            test(ar, isfill);
+            disposeGCs();
+        } else {
+            System.err.println("Unparseable test line: "+origstr);
+        }
+    }
+
+    public static void test(AnnotatedRenderOp ar, boolean isfill) {
+        grefclear.fillRect(0, 0, 40, 40);
+        gtstclear.fillRect(0, 0, 40, 40);
+        if (isfill) {
+            ar.fill(grefrender);
+            ar.fill(gtstrender);
+        } else {
+            ar.draw(grefrender);
+            ar.draw(gtstrender);
+        }
+        check(imgref, imgtst, ar, isfill);
+    }
+
+    public static int[] getData(BufferedImage img) {
+        Raster r = img.getRaster();
+        DataBufferInt dbi = (DataBufferInt) r.getDataBuffer();
+        return dbi.getData();
+    }
+
+    public static int getScan(BufferedImage img) {
+        Raster r = img.getRaster();
+        SinglePixelPackedSampleModel sppsm =
+            (SinglePixelPackedSampleModel) r.getSampleModel();
+        return sppsm.getScanlineStride();
+    }
+
+    public static int getOffset(BufferedImage img) {
+        Raster r = img.getRaster();
+        SinglePixelPackedSampleModel sppsm =
+            (SinglePixelPackedSampleModel) r.getSampleModel();
+        return sppsm.getOffset(-r.getSampleModelTranslateX(),
+                               -r.getSampleModelTranslateY());
+    }
+
+    final static int opaque = 0xff000000;
+    final static int whitergb = Color.white.getRGB();
+
+    public static final int maxdiff(int rgb1, int rgb2) {
+        int maxd = 0;
+        for (int i = 0; i < 32; i += 8) {
+            int c1 = (rgb1 >> i) & 0xff;
+            int c2 = (rgb2 >> i) & 0xff;
+            int d = Math.abs(c1-c2);
+            if (maxd < d) {
+                maxd = d;
+            }
+        }
+        return maxd;
+    }
+
+    public static void check(BufferedImage imgref, BufferedImage imgtst,
+                             AnnotatedRenderOp ar, boolean wasfill)
+    {
+        numtests++;
+        int dataref[] = getData(imgref);
+        int datatst[] = getData(imgtst);
+        int scanref = getScan(imgref);
+        int scantst = getScan(imgtst);
+        int offref = getOffset(imgref);
+        int offtst = getOffset(imgtst);
+
+        // We want to check for errors outside the clip at a higher
+        // priority than errors involving different pixels touched
+        // inside the clip.
+
+        // Check above clip
+        if (check(ar, wasfill,
+                  null, 0, 0,
+                  datatst, scantst, offtst,
+                  0, 0, 40, 10))
+        {
+            return;
+        }
+        // Check below clip
+        if (check(ar, wasfill,
+                  null, 0, 0,
+                  datatst, scantst, offtst,
+                  0, 30, 40, 40))
+        {
+            return;
+        }
+        // Check left of clip
+        if (check(ar, wasfill,
+                  null, 0, 0,
+                  datatst, scantst, offtst,
+                  0, 10, 10, 30))
+        {
+            return;
+        }
+        // Check right of clip
+        if (check(ar, wasfill,
+                  null, 0, 0,
+                  datatst, scantst, offtst,
+                  30, 10, 40, 30))
+        {
+            return;
+        }
+        // Check inside clip
+        check(ar, wasfill,
+              dataref, scanref, offref,
+              datatst, scantst, offtst,
+              10, 10, 30, 30);
+    }
+
+    public static boolean check(AnnotatedRenderOp ar, boolean wasfill,
+                                int dataref[], int scanref, int offref,
+                                int datatst[], int scantst, int offtst,
+                                int x0, int y0, int x1, int y1)
+    {
+        offref += scanref * y0;
+        offtst += scantst * y0;
+        for (int y = y0; y < y1; y++) {
+            for (int x = x0; x < x1; x++) {
+                boolean failed;
+                String reason;
+                int rgbref;
+                int rgbtst;
+
+                rgbtst = datatst[offtst+x] | opaque;
+                if (dataref == null) {
+                    /* Outside of clip, must be white, no error tolerance */
+                    rgbref = whitergb;
+                    failed = (rgbtst != rgbref);
+                    reason = "stray pixel rendered outside of clip";
+                } else {
+                    /* Inside of clip, check for maxerr delta in components */
+                    rgbref = dataref[offref+x] | opaque;
+                    failed = (rgbref != rgbtst &&
+                              maxdiff(rgbref, rgbtst) > maxerr);
+                    reason = "different pixel rendered inside clip";
+                }
+                if (failed) {
+                    if (dataref == null) {
+                        numerrors++;
+                    }
+                    if (wasfill) {
+                        numfillfailures++;
+                    } else {
+                        numstrokefailures++;
+                    }
+                    if (!silent) {
+                        System.out.println("Failed: "+reason+" at "+x+", "+y+
+                                           " ["+Integer.toHexString(rgbref)+
+                                           " != "+Integer.toHexString(rgbtst)+
+                                           "]");
+                        System.out.print(wasfill ? "Filled " : "Stroked ");
+                        if (useAA) System.out.print("AA ");
+                        if (strokePure) System.out.print("Pure ");
+                        if (lw != 1) System.out.print("Lw="+lw+" ");
+                        if (rot != 0) System.out.print("Rot="+rot+" ");
+                        System.out.println(ar);
+                    }
+                    if (showErrors) {
+                        show(imgref, imgtst);
+                    }
+                    return true;
+                }
+            }
+            offref += scanref;
+            offtst += scantst;
+        }
+        return false;
+    }
+
+    static ErrorWindow errw;
+
+    public static void show(BufferedImage imgref, BufferedImage imgtst) {
+        ErrorWindow errw = new ErrorWindow();
+        errw.setImages(imgref, imgtst);
+        errw.setVisible(true);
+        errw.waitForHide();
+        errw.dispose();
+    }
+
+    public static class Cubic extends AnnotatedShapeOp {
+        public static Cubic tryparse(String str) {
+            str = str.trim();
+            if (!str.startsWith("Cubic(")) {
+                return null;
+            }
+            str = str.substring(6);
+            double coords[] = new double[8];
+            boolean foundparen = false;
+            for (int i = 0; i < coords.length; i++) {
+                int index = str.indexOf(",");
+                if (index < 0) {
+                    if (i < coords.length-1) {
+                        return null;
+                    }
+                    index = str.indexOf(")");
+                    if (index < 0) {
+                        return null;
+                    }
+                    foundparen = true;
+                }
+                String num = str.substring(0, index);
+                try {
+                    coords[i] = Double.parseDouble(num);
+                } catch (NumberFormatException nfe) {
+                    return null;
+                }
+                str = str.substring(index+1);
+            }
+            if (!foundparen || str.length() > 0) {
+                return null;
+            }
+            Cubic c = new Cubic();
+            c.cubic.setCurve(coords[0], coords[1],
+                             coords[2], coords[3],
+                             coords[4], coords[5],
+                             coords[6], coords[7]);
+            return c;
+        }
+
+        private CubicCurve2D cubic = new CubicCurve2D.Double();
+
+        public void randomize() {
+            cubic.setCurve(randDblCoord(), randDblCoord(),
+                           randDblCoord(), randDblCoord(),
+                           randDblCoord(), randDblCoord(),
+                           randDblCoord(), randDblCoord());
+        }
+
+        public Shape getShape() {
+            return cubic;
+        }
+
+        public String toString() {
+            return ("Cubic("+
+                    cubic.getX1()+", "+
+                    cubic.getY1()+", "+
+                    cubic.getCtrlX1()+", "+
+                    cubic.getCtrlY1()+", "+
+                    cubic.getCtrlX2()+", "+
+                    cubic.getCtrlY2()+", "+
+                    cubic.getX2()+", "+
+                    cubic.getY2()
+                    +")");
+        }
+    }
+
+    public static class Quad extends AnnotatedShapeOp {
+        public static Quad tryparse(String str) {
+            str = str.trim();
+            if (!str.startsWith("Quad(")) {
+                return null;
+            }
+            str = str.substring(5);
+            double coords[] = new double[6];
+            boolean foundparen = false;
+            for (int i = 0; i < coords.length; i++) {
+                int index = str.indexOf(",");
+                if (index < 0) {
+                    if (i < coords.length-1) {
+                        return null;
+                    }
+                    index = str.indexOf(")");
+                    if (index < 0) {
+                        return null;
+                    }
+                    foundparen = true;
+                }
+                String num = str.substring(0, index);
+                try {
+                    coords[i] = Double.parseDouble(num);
+                } catch (NumberFormatException nfe) {
+                    return null;
+                }
+                str = str.substring(index+1);
+            }
+            if (!foundparen || str.length() > 0) {
+                return null;
+            }
+            Quad c = new Quad();
+            c.quad.setCurve(coords[0], coords[1],
+                            coords[2], coords[3],
+                            coords[4], coords[5]);
+            return c;
+        }
+
+        private QuadCurve2D quad = new QuadCurve2D.Double();
+
+        public void randomize() {
+            quad.setCurve(randDblCoord(), randDblCoord(),
+                          randDblCoord(), randDblCoord(),
+                          randDblCoord(), randDblCoord());
+        }
+
+        public Shape getShape() {
+            return quad;
+        }
+
+        public String toString() {
+            return ("Quad("+
+                    quad.getX1()+", "+
+                    quad.getY1()+", "+
+                    quad.getCtrlX()+", "+
+                    quad.getCtrlY()+", "+
+                    quad.getX2()+", "+
+                    quad.getY2()
+                    +")");
+        }
+    }
+
+    public static class Poly extends AnnotatedShapeOp {
+        public static Poly tryparse(String str) {
+            str = str.trim();
+            if (!str.startsWith("Poly(")) {
+                return null;
+            }
+            str = str.substring(5);
+            Polygon p = new Polygon();
+            while (true) {
+                int x, y;
+                str = str.trim();
+                if (str.startsWith(")")) {
+                    str = str.substring(1);
+                    break;
+                }
+                if (p.npoints > 0) {
+                    if (str.startsWith(",")) {
+                        str = str.substring(2).trim();
+                    } else {
+                        return null;
+                    }
+                }
+                if (str.startsWith("[")) {
+                    str = str.substring(1);
+                } else {
+                    return null;
+                }
+                int index = str.indexOf(",");
+                if (index < 0) {
+                    return null;
+                }
+                String num = str.substring(0, index);
+                try {
+                    x = Integer.parseInt(num);
+                } catch (NumberFormatException nfe) {
+                    return null;
+                }
+                str = str.substring(index+1);
+                index = str.indexOf("]");
+                if (index < 0) {
+                    return null;
+                }
+                num = str.substring(0, index).trim();
+                try {
+                    y = Integer.parseInt(num);
+                } catch (NumberFormatException nfe) {
+                    return null;
+                }
+                str = str.substring(index+1);
+                p.addPoint(x, y);
+            }
+            if (str.length() > 0) {
+                return null;
+            }
+            if (p.npoints < 3) {
+                return null;
+            }
+            return new Poly(p);
+        }
+
+        private Polygon poly;
+
+        public Poly() {
+            this.poly = new Polygon();
+        }
+
+        private Poly(Polygon p) {
+            this.poly = p;
+        }
+
+        public void randomize() {
+            poly.reset();
+            poly.addPoint(randIntCoord(), randIntCoord());
+            poly.addPoint(randIntCoord(), randIntCoord());
+            poly.addPoint(randIntCoord(), randIntCoord());
+            poly.addPoint(randIntCoord(), randIntCoord());
+            poly.addPoint(randIntCoord(), randIntCoord());
+        }
+
+        public Shape getShape() {
+            return poly;
+        }
+
+        public String toString() {
+            StringBuffer sb = new StringBuffer(100);
+            sb.append("Poly(");
+            for (int i = 0; i < poly.npoints; i++) {
+                if (i != 0) {
+                    sb.append(", ");
+                }
+                sb.append("[");
+                sb.append(poly.xpoints[i]);
+                sb.append(", ");
+                sb.append(poly.ypoints[i]);
+                sb.append("]");
+            }
+            sb.append(")");
+            return sb.toString();
+        }
+    }
+
+    public static class Path extends AnnotatedShapeOp {
+        public static Path tryparse(String str) {
+            str = str.trim();
+            if (!str.startsWith("Path(")) {
+                return null;
+            }
+            str = str.substring(5);
+            GeneralPath gp = new GeneralPath();
+            float coords[] = new float[6];
+            int numsegs = 0;
+            while (true) {
+                int type;
+                int n;
+                str = str.trim();
+                if (str.startsWith(")")) {
+                    str = str.substring(1);
+                    break;
+                }
+                if (str.startsWith("M[")) {
+                    type = PathIterator.SEG_MOVETO;
+                    n = 2;
+                } else if (str.startsWith("L[")) {
+                    type = PathIterator.SEG_LINETO;
+                    n = 2;
+                } else if (str.startsWith("Q[")) {
+                    type = PathIterator.SEG_QUADTO;
+                    n = 4;
+                } else if (str.startsWith("C[")) {
+                    type = PathIterator.SEG_CUBICTO;
+                    n = 6;
+                } else if (str.startsWith("E[")) {
+                    type = PathIterator.SEG_CLOSE;
+                    n = 0;
+                } else {
+                    return null;
+                }
+                str = str.substring(2);
+                if (n == 0) {
+                    if (str.startsWith("]")) {
+                        str = str.substring(1);
+                    } else {
+                        return null;
+                    }
+                }
+                for (int i = 0; i < n; i++) {
+                    int index;
+                    if (i < n-1) {
+                        index = str.indexOf(",");
+                    } else {
+                        index = str.indexOf("]");
+                    }
+                    if (index < 0) {
+                        return null;
+                    }
+                    String num = str.substring(0, index);
+                    try {
+                        coords[i] = Float.parseFloat(num);
+                    } catch (NumberFormatException nfe) {
+                        return null;
+                    }
+                    str = str.substring(index+1).trim();
+                }
+                switch (type) {
+                case PathIterator.SEG_MOVETO:
+                    gp.moveTo(coords[0], coords[1]);
+                    break;
+                case PathIterator.SEG_LINETO:
+                    gp.lineTo(coords[0], coords[1]);
+                    break;
+                case PathIterator.SEG_QUADTO:
+                    gp.quadTo(coords[0], coords[1],
+                              coords[2], coords[3]);
+                    break;
+                case PathIterator.SEG_CUBICTO:
+                    gp.curveTo(coords[0], coords[1],
+                               coords[2], coords[3],
+                               coords[4], coords[5]);
+                    break;
+                case PathIterator.SEG_CLOSE:
+                    gp.closePath();
+                    break;
+                }
+                numsegs++;
+            }
+            if (str.length() > 0) {
+                return null;
+            }
+            if (numsegs < 2) {
+                return null;
+            }
+            return new Path(gp);
+        }
+
+        private GeneralPath path;
+
+        public Path() {
+            this.path = new GeneralPath();
+        }
+
+        private Path(GeneralPath gp) {
+            this.path = gp;
+        }
+
+        public void randomize() {
+            path.reset();
+            path.moveTo(randFltCoord(), randFltCoord());
+            for (int i = randInt(5)+3; i > 0; --i) {
+                switch(randInt(5)) {
+                case 0:
+                    path.moveTo(randFltCoord(), randFltCoord());
+                    break;
+                case 1:
+                    path.lineTo(randFltCoord(), randFltCoord());
+                    break;
+                case 2:
+                    path.quadTo(randFltCoord(), randFltCoord(),
+                                randFltCoord(), randFltCoord());
+                    break;
+                case 3:
+                    path.curveTo(randFltCoord(), randFltCoord(),
+                                 randFltCoord(), randFltCoord(),
+                                 randFltCoord(), randFltCoord());
+                    break;
+                case 4:
+                    path.closePath();
+                    break;
+                }
+            }
+        }
+
+        public Shape getShape() {
+            return path;
+        }
+
+        public String toString() {
+            StringBuffer sb = new StringBuffer(100);
+            sb.append("Path(");
+            PathIterator pi = path.getPathIterator(null);
+            float coords[] = new float[6];
+            boolean first = true;
+            while (!pi.isDone()) {
+                int n;
+                char c;
+                switch(pi.currentSegment(coords)) {
+                case PathIterator.SEG_MOVETO:
+                    c = 'M';
+                    n = 2;
+                    break;
+                case PathIterator.SEG_LINETO:
+                    c = 'L';
+                    n = 2;
+                    break;
+                case PathIterator.SEG_QUADTO:
+                    c = 'Q';
+                    n = 4;
+                    break;
+                case PathIterator.SEG_CUBICTO:
+                    c = 'C';
+                    n = 6;
+                    break;
+                case PathIterator.SEG_CLOSE:
+                    c = 'E';
+                    n = 0;
+                    break;
+                default:
+                    throw new InternalError("Unknown segment!");
+                }
+                sb.append(c);
+                sb.append("[");
+                for (int i = 0; i < n; i++) {
+                    if (i != 0) {
+                        sb.append(",");
+                    }
+                    sb.append(coords[i]);
+                }
+                sb.append("]");
+                pi.next();
+            }
+            sb.append(")");
+            return sb.toString();
+        }
+    }
+
+    public static class Rect extends AnnotatedShapeOp {
+        public static Rect tryparse(String str) {
+            str = str.trim();
+            if (!str.startsWith("Rect(")) {
+                return null;
+            }
+            str = str.substring(5);
+            double coords[] = new double[4];
+            boolean foundparen = false;
+            for (int i = 0; i < coords.length; i++) {
+                int index = str.indexOf(",");
+                if (index < 0) {
+                    if (i < coords.length-1) {
+                        return null;
+                    }
+                    index = str.indexOf(")");
+                    if (index < 0) {
+                        return null;
+                    }
+                    foundparen = true;
+                }
+                String num = str.substring(0, index);
+                try {
+                    coords[i] = Double.parseDouble(num);
+                } catch (NumberFormatException nfe) {
+                    return null;
+                }
+                str = str.substring(index+1);
+            }
+            if (!foundparen || str.length() > 0) {
+                return null;
+            }
+            Rect r = new Rect();
+            r.rect.setRect(coords[0], coords[1],
+                           coords[2], coords[3]);
+            return r;
+        }
+
+        private Rectangle2D rect = new Rectangle2D.Double();
+
+        public void randomize() {
+            rect.setRect(randDblCoord(), randDblCoord(),
+                         randDblCoord(), randDblCoord());
+        }
+
+        public Shape getShape() {
+            return rect;
+        }
+
+        public String toString() {
+            return ("Rect("+
+                    rect.getX()+", "+
+                    rect.getY()+", "+
+                    rect.getWidth()+", "+
+                    rect.getHeight()
+                    +")");
+        }
+    }
+
+    public static class Line extends AnnotatedShapeOp {
+        public static Line tryparse(String str) {
+            str = str.trim();
+            if (!str.startsWith("Line(")) {
+                return null;
+            }
+            str = str.substring(5);
+            double coords[] = new double[4];
+            boolean foundparen = false;
+            for (int i = 0; i < coords.length; i++) {
+                int index = str.indexOf(",");
+                if (index < 0) {
+                    if (i < coords.length-1) {
+                        return null;
+                    }
+                    index = str.indexOf(")");
+                    if (index < 0) {
+                        return null;
+                    }
+                    foundparen = true;
+                }
+                String num = str.substring(0, index);
+                try {
+                    coords[i] = Double.parseDouble(num);
+                } catch (NumberFormatException nfe) {
+                    return null;
+                }
+                str = str.substring(index+1);
+            }
+            if (!foundparen || str.length() > 0) {
+                return null;
+            }
+            Line l = new Line();
+            l.line.setLine(coords[0], coords[1],
+                           coords[2], coords[3]);
+            return l;
+        }
+
+        private Line2D line = new Line2D.Double();
+
+        public void randomize() {
+            line.setLine(randDblCoord(), randDblCoord(),
+                         randDblCoord(), randDblCoord());
+        }
+
+        public Shape getShape() {
+            return line;
+        }
+
+        public String toString() {
+            return ("Line("+
+                    line.getX1()+", "+
+                    line.getY1()+", "+
+                    line.getX2()+", "+
+                    line.getY2()
+                    +")");
+        }
+    }
+
+    public static class RectMethod extends AnnotatedRenderOp {
+        public static RectMethod tryparse(String str) {
+            str = str.trim();
+            if (!str.startsWith("RectMethod(")) {
+                return null;
+            }
+            str = str.substring(11);
+            int coords[] = new int[4];
+            boolean foundparen = false;
+            for (int i = 0; i < coords.length; i++) {
+                int index = str.indexOf(",");
+                if (index < 0) {
+                    if (i < coords.length-1) {
+                        return null;
+                    }
+                    index = str.indexOf(")");
+                    if (index < 0) {
+                        return null;
+                    }
+                    foundparen = true;
+                }
+                String num = str.substring(0, index).trim();
+                try {
+                    coords[i] = Integer.parseInt(num);
+                } catch (NumberFormatException nfe) {
+                    return null;
+                }
+                str = str.substring(index+1);
+            }
+            if (!foundparen || str.length() > 0) {
+                return null;
+            }
+            RectMethod rm = new RectMethod();
+            rm.rect.setBounds(coords[0], coords[1],
+                              coords[2], coords[3]);
+            return rm;
+        }
+
+        private Rectangle rect = new Rectangle();
+
+        public void randomize() {
+            rect.setBounds(randIntCoord(), randIntCoord(),
+                           randIntCoord(), randIntCoord());
+        }
+
+        public void fill(Graphics2D g2d) {
+            g2d.fillRect(rect.x, rect.y, rect.width, rect.height);
+        }
+
+        public void draw(Graphics2D g2d) {
+            g2d.drawRect(rect.x, rect.y, rect.width, rect.height);
+        }
+
+        public String toString() {
+            return ("RectMethod("+
+                    rect.x+", "+
+                    rect.y+", "+
+                    rect.width+", "+
+                    rect.height
+                    +")");
+        }
+    }
+
+    public static class LineMethod extends AnnotatedRenderOp {
+        public static LineMethod tryparse(String str) {
+            str = str.trim();
+            if (!str.startsWith("LineMethod(")) {
+                return null;
+            }
+            str = str.substring(11);
+            int coords[] = new int[4];
+            boolean foundparen = false;
+            for (int i = 0; i < coords.length; i++) {
+                int index = str.indexOf(",");
+                if (index < 0) {
+                    if (i < coords.length-1) {
+                        return null;
+                    }
+                    index = str.indexOf(")");
+                    if (index < 0) {
+                        return null;
+                    }
+                    foundparen = true;
+                }
+                String num = str.substring(0, index).trim();
+                try {
+                    coords[i] = Integer.parseInt(num);
+                } catch (NumberFormatException nfe) {
+                    return null;
+                }
+                str = str.substring(index+1);
+            }
+            if (!foundparen || str.length() > 0) {
+                return null;
+            }
+            LineMethod lm = new LineMethod();
+            lm.line = coords;
+            return lm;
+        }
+
+        private int line[] = new int[4];
+
+        public void randomize() {
+            line[0] = randIntCoord();
+            line[1] = randIntCoord();
+            line[2] = randIntCoord();
+            line[3] = randIntCoord();
+        }
+
+        public void fill(Graphics2D g2d) {
+        }
+
+        public void draw(Graphics2D g2d) {
+            g2d.drawLine(line[0], line[1], line[2], line[3]);
+        }
+
+        public String toString() {
+            return ("LineMethod("+
+                    line[0]+", "+
+                    line[1]+", "+
+                    line[2]+", "+
+                    line[3]
+                    +")");
+        }
+    }
+
+    public static class ErrorWindow extends Frame {
+        ImageCanvas unclipped;
+        ImageCanvas reference;
+        ImageCanvas actual;
+        ImageCanvas diff;
+
+        public ErrorWindow() {
+            super("Error Comparison Window");
+
+            unclipped = new ImageCanvas();
+            reference = new ImageCanvas();
+            actual = new ImageCanvas();
+            diff = new ImageCanvas();
+
+            setLayout(new SmartGridLayout(0, 2, 5, 5));
+            addImagePanel(unclipped, "Unclipped rendering");
+            addImagePanel(reference, "Clipped reference");
+            addImagePanel(actual, "Actual clipped");
+            addImagePanel(diff, "Difference");
+
+            addWindowListener(new WindowAdapter() {
+                public void windowClosing(WindowEvent e) {
+                    setVisible(false);
+                }
+            });
+        }
+
+        public void addImagePanel(ImageCanvas ic, String label) {
+            add(ic);
+            add(new Label(label));
+        }
+
+        public void setImages(BufferedImage imgref, BufferedImage imgtst) {
+            unclipped.setImage(imgref);
+            reference.setReference(imgref);
+            actual.setImage(imgtst);
+            diff.setDiff(reference.getImage(), imgtst);
+            invalidate();
+            pack();
+            repaint();
+        }
+
+        public void setVisible(boolean vis) {
+            super.setVisible(vis);
+            synchronized (this) {
+                notifyAll();
+            }
+        }
+
+        public synchronized void waitForHide() {
+            while (isShowing()) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    System.exit(2);
+                }
+            }
+        }
+    }
+
+    public static class SmartGridLayout implements LayoutManager {
+        int rows;
+        int cols;
+        int hgap;
+        int vgap;
+
+        public SmartGridLayout(int r, int c, int h, int v) {
+            this.rows = r;
+            this.cols = c;
+            this.hgap = h;
+            this.vgap = v;
+        }
+
+        public void addLayoutComponent(String name, Component comp) {
+        }
+
+        public void removeLayoutComponent(Component comp) {
+        }
+
+        public int[][] getGridSizes(Container parent, boolean min) {
+            int ncomponents = parent.getComponentCount();
+            int nrows = rows;
+            int ncols = cols;
+
+            if (nrows > 0) {
+                ncols = (ncomponents + nrows - 1) / nrows;
+            } else {
+                nrows = (ncomponents + ncols - 1) / ncols;
+            }
+            int widths[] = new int[ncols+1];
+            int heights[] = new int[nrows+1];
+            int x = 0;
+            int y = 0;
+            for (int i = 0 ; i < ncomponents ; i++) {
+                Component comp = parent.getComponent(i);
+                Dimension d = (min
+                               ? comp.getMinimumSize()
+                               : comp.getPreferredSize());
+                if (widths[x] < d.width) {
+                    widths[x] = d.width;
+                }
+                if (heights[y] < d.height) {
+                    heights[y] = d.height;
+                }
+                x++;
+                if (x >= ncols) {
+                    x = 0;
+                    y++;
+                }
+            }
+            for (int i = 0; i < ncols; i++) {
+                widths[ncols] += widths[i];
+            }
+            for (int i = 0; i < nrows; i++) {
+                heights[nrows] += heights[i];
+            }
+            return new int[][] { widths, heights };
+        }
+
+        public Dimension getSize(Container parent, boolean min) {
+            int sizes[][] = getGridSizes(parent, min);
+            int widths[] = sizes[0];
+            int heights[] = sizes[1];
+            int nrows = heights.length-1;
+            int ncols = widths.length-1;
+            int w = widths[ncols];
+            int h = heights[nrows];
+            Insets insets = parent.getInsets();
+            return new Dimension(insets.left+insets.right + w+(ncols+1)*hgap,
+                                 insets.top+insets.bottom + h+(nrows+1)*vgap);
+        }
+
+        public Dimension preferredLayoutSize(Container parent) {
+            return getSize(parent, false);
+        }
+
+        public Dimension minimumLayoutSize(Container parent) {
+            return getSize(parent, true);
+        }
+
+        public void layoutContainer(Container parent) {
+            int pref[][] = getGridSizes(parent, false);
+            int min[][] = getGridSizes(parent, true);
+            int minwidths[] = min[0];
+            int minheights[] = min[1];
+            int prefwidths[] = pref[0];
+            int prefheights[] = pref[1];
+            int nrows = minheights.length - 1;
+            int ncols = minwidths.length - 1;
+            Insets insets = parent.getInsets();
+            int w = parent.getWidth() - insets.left - insets.right;
+            int h = parent.getHeight() - insets.top - insets.bottom;
+            w = w - (ncols+1)*hgap;
+            h = h - (nrows+1)*vgap;
+            int widths[] = calculateSizes(w, ncols, minwidths, prefwidths);
+            int heights[] = calculateSizes(h, nrows, minheights, prefheights);
+            int ncomponents = parent.getComponentCount();
+            int x = insets.left + hgap;
+            int y = insets.top + vgap;
+            int r = 0;
+            int c = 0;
+            for (int i = 0; i < ncomponents; i++) {
+                parent.getComponent(i).setBounds(x, y, widths[c], heights[r]);
+                x += widths[c++] + hgap;
+                if (c >= ncols) {
+                    c = 0;
+                    x = insets.left + hgap;
+                    y += heights[r++] + vgap;
+                    if (r >= nrows) {
+                        // just in case
+                        break;
+                    }
+                }
+            }
+        }
+
+        public static int[] calculateSizes(int total, int num,
+                                           int minsizes[], int prefsizes[])
+        {
+            if (total <= minsizes[num]) {
+                return minsizes;
+            }
+            if (total >= prefsizes[num]) {
+                return prefsizes;
+            }
+            int sizes[] = new int[total];
+            int prevhappy = 0;
+            int nhappy = 0;
+            int happysize = 0;
+            do {
+                int addsize = (total - happysize) / (num - nhappy);
+                happysize = 0;
+                for (int i = 0; i < num; i++) {
+                    if (sizes[i] >= prefsizes[i] ||
+                        minsizes[i] + addsize > prefsizes[i])
+                    {
+                        happysize += (sizes[i] = prefsizes[i]);
+                        nhappy++;
+                    } else {
+                        sizes[i] = minsizes[i] + addsize;
+                    }
+                }
+            } while (nhappy < num && nhappy > prevhappy);
+            return sizes;
+        }
+    }
+
+    public static class ImageCanvas extends Canvas {
+        BufferedImage image;
+
+        public void setImage(BufferedImage img) {
+            this.image = img;
+        }
+
+        public BufferedImage getImage() {
+            return image;
+        }
+
+        public void checkImage(int w, int h) {
+            if (image == null ||
+                image.getWidth() < w ||
+                image.getHeight() < h)
+            {
+                image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+            }
+        }
+
+        public void setReference(BufferedImage img) {
+            checkImage(img.getWidth(), img.getHeight());
+            Graphics g = image.createGraphics();
+            g.drawImage(img, 0, 0, null);
+            g.setColor(Color.white);
+            g.fillRect(0, 0, 30, 10);
+            g.fillRect(30, 0, 10, 30);
+            g.fillRect(10, 30, 30, 10);
+            g.fillRect(0, 10, 10, 30);
+            g.dispose();
+        }
+
+        public void setDiff(BufferedImage imgref, BufferedImage imgtst) {
+            int w = Math.max(imgref.getWidth(), imgtst.getWidth());
+            int h = Math.max(imgref.getHeight(), imgtst.getHeight());
+            checkImage(w, h);
+            Graphics g = image.createGraphics();
+            g.drawImage(imgref, 0, 0, null);
+            g.setXORMode(Color.white);
+            g.drawImage(imgtst, 0, 0, null);
+            g.setPaintMode();
+            g.setColor(new Color(1f, 1f, 0f, 0.25f));
+            g.fillRect(10, 10, 20, 20);
+            g.setColor(new Color(1f, 0f, 0f, 0.25f));
+            g.fillRect(0, 0, 30, 10);
+            g.fillRect(30, 0, 10, 30);
+            g.fillRect(10, 30, 30, 10);
+            g.fillRect(0, 10, 10, 30);
+            g.dispose();
+        }
+
+        public Dimension getPreferredSize() {
+            if (image == null) {
+                return new Dimension();
+            } else {
+                return new Dimension(image.getWidth(), image.getHeight());
+            }
+        }
+
+        public void paint(Graphics g) {
+            g.drawImage(image, 0, 0, null);
+        }
+    }
+}
