I've updated the patch slightly and committed it.  Sven's looked at it
and the only issue was the constant flatness of one bit; I've changed it
to flatten down to a squared flatness of radius / 3  (ie,
getFlatnessSq() <= (radius / 3)).  This also saves us a Math.sqrt call,
and looks reasonable after a few visual tests... easy to change later if
we want.

Cheers,
Francis


On Tue, 2006-07-25 at 17:50 -0400, Francis Kung wrote:
> Hello,
> 
> Attached is a patch to implement, fix, and clean up aspects of
> BasicStroke.createStrokedShape()
> 
> I would appreciate any comments anyone might have.
> 
> Thanks,
> Francis
> 
> 
> 2006-07-25  Francis Kung  <[EMAIL PROTECTED]>
> 
>       * gnu/java/awt/java2d/CubicSegment.java: Added import.
>       (cp1): Renamed from first().
>       (c2): Renamed from last().
>       (first): Renamed to cp1().
>       (getDisplacedSegments): Implemented.
>       (last): Renamed to cp2().
>       * gnu/java/awt/java2d/LineSegment.java
>       (cp1): Renamed from first().
>       (c2): Renamed from last().
>       (first): Renamed to cp1().
>       (last): Renamed to cp2().
>       * gnu/java/awt/java2d/QuadSegment.java
>       (cp1): Renamed from first().
>       (c2): Renamed from last().
>       (first): Renamed to cp1().
>       (last): Renamed to cp2().
>       * gnu/java/awt/java2d/Segment.java: Added comments.
>       (first): New field.
>       (Segment): Keep track of first element in list.
>       (add): Update first & last element variables.
>       (cp1): Renamed from first().
>       (c2): Renamed from last().
>       (first()): Renamed to cp1() to reduce ambiguity.
>       (last()): Renamed to cp2() to reduce ambiguity.
>       (reverseAll): Update first element variable..
>       * gnu/java/awt/peer/gtk/CairoGraphics2D.java
>       (draw): Remove flattening path iterator.
>       * java/awt/BasicStroke.java: Clarified comments.
>       (addSegments): Refactored some code into joinSegments and 
>       joinInnerSegments.
>       (capEnd): Rename of Segment.first() and Segment.end().
>       (joinInnerSegments): New method.
>       (joinOuterSegments): New method.
>       (joinSegments): Refactored some code into joinOuterSegments.
>       (solidStroke): Connect segments together properly.
> 
### Eclipse Workspace Patch 1.0
#P classpath
Index: java/awt/BasicStroke.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/BasicStroke.java,v
retrieving revision 1.14
diff -u -r1.14 BasicStroke.java
--- java/awt/BasicStroke.java	10 Jul 2006 18:43:38 -0000	1.14
+++ java/awt/BasicStroke.java	27 Jul 2006 20:55:55 -0000
@@ -117,6 +117,7 @@
   /** The dash phase. */
   private final float phase;
 
+  // The inner and outer paths of the stroke
   private Segment start, end;
 
   /**
@@ -434,8 +435,8 @@
             else
               addSegments(p);
 
-            x = coords[0];
-            y = coords[1];
+            x = coords[2];
+            y = coords[3];
             break;
 
           case PathIterator.SEG_CUBICTO:
@@ -451,17 +452,25 @@
             else
               addSegments(p);
 
-            x = coords[0];
-            y = coords[1];
+            x = coords[4];
+            y = coords[5];
             break;
 
           case PathIterator.SEG_CLOSE:
-            p = (new LineSegment(x, y, x0, y0)).getDisplacedSegments(width/2.0);
-            addSegments(p);
+            if (x == x0 && y == y0)
+              {
+                joinSegments(new Segment[] { start.first, end.first });
+              }
+            else
+              {
+                p = (new LineSegment(x, y, x0, y0)).getDisplacedSegments(width / 2.0);
+                addSegments(p);
+              }
             convertPath(output, start);
             convertPath(output, end);
             start = end = null;
             pathOpen = false;
+            output.setWindingRule(GeneralPath.WIND_EVEN_ODD);
             break;
           }
         pi.next();
@@ -498,7 +507,7 @@
   }
 
   /**
-   * Convert and add the linked list of Segments in s to a GeneralPath p.
+   * Append the Segments in s to the GeneralPath p
    */
   private void convertPath(GeneralPath p, Segment s)
   {
@@ -526,18 +535,28 @@
 
     p.closePath();
   }
-
+  
   /**
-   * Add to segments to start and end, joining the outer pair and 
+   * Add the segments to start and end (the inner and outer edges of the stroke) 
    */
   private void addSegments(Segment[] segments)
   {
-    double[] p0 = start.last.last();
+    joinSegments(segments);
+    start.add(segments[0]);
+    end.add(segments[1]);
+  }
+
+  private void joinSegments(Segment[] segments)
+  {
+    double[] p0 = start.last.cp2();
     double[] p1 = new double[]{start.last.P2.getX(), start.last.P2.getY()};
-    double[] p2 = new double[]{segments[0].P1.getX(), segments[0].P1.getY()};
-    double[] p3 = segments[0].first();
+    double[] p2 = new double[]{segments[0].first.P1.getX(), segments[0].first.P1.getY()};
+    double[] p3 = segments[0].cp1();
     Point2D p;
 
+    p = lineIntersection(p0[0],p0[1],p1[0],p1[1],
+                                 p2[0],p2[1],p3[0],p3[1], false);
+
     double det = (p1[0] - p0[0])*(p3[1] - p2[1]) - 
       (p3[0] - p2[0])*(p1[1] - p0[1]);
 
@@ -545,42 +564,14 @@
       {
         // start and segment[0] form the 'inner' part of a join, 
         // connect the overlapping segments
-        p = lineIntersection(p0[0],p0[1],p1[0],p1[1],p2[0],p2[1],p3[0],p3[1], false);
-        if( p == null ) 
-          {
-            // Dodgy.
-            start.add(new LineSegment(start.last.P2, segments[0].P1));
-            p = new Point2D.Double((segments[0].P1.getX()+ start.last.P2.getX())/2.0,
-                                   (segments[0].P1.getY()+ start.last.P2.getY())/2.0);
-          }
-        else
-          segments[0].P1 = start.last.P2 = p;
-
-        start.add( segments[0] );
-        joinSegments(end, segments[1], p);
+        joinInnerSegments(start, segments[0], p);
+        joinOuterSegments(end, segments[1], p);
       }
     else
       {
         // end and segment[1] form the 'inner' part 
-        p0 = end.last.last();
-        p1 = new double[]{end.last.P2.getX(), end.last.P2.getY()};
-        p2 = new double[]{segments[1].P1.getX(), segments[1].P1.getY()};
-        p3 = segments[1].first();
-
-        p = lineIntersection(p0[0],p0[1],p1[0],p1[1],
-                             p2[0],p2[1],p3[0],p3[1], false);
-        if( p == null )
-          {
-            // Dodgy.
-            end.add(new LineSegment(end.last.P2, segments[1].P1));
-            p = new Point2D.Double((segments[1].P1.getX()+ end.last.P2.getX())/2.0,
-                                   (segments[1].P1.getY()+ end.last.P2.getY())/2.0);
-          }
-        else
-          segments[1].P1 = end.last.P2 = p;
-
-        end.add( segments[1] );
-        joinSegments(start, segments[0], p);
+        joinInnerSegments(end, segments[1], p);
+        joinOuterSegments(start, segments[0], p);
       }
   }
 
@@ -601,7 +592,7 @@
         break;
 
       case CAP_SQUARE:
-        p0 = a.last.last();
+        p0 = a.last.cp2();
         p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
         dx = p1[0] - p0[0];
         dy = p1[1] - p0[1];
@@ -616,7 +607,7 @@
         break;
 
       case CAP_ROUND:
-        p0 = a.last.last();
+        p0 = a.last.cp2();
         p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
         dx = p1[0] - p0[0];
         dy = p1[1] - p0[1];
@@ -675,7 +666,7 @@
    * insideP is the inside intersection point of the join, needed for
    * calculating miter lengths.
    */
-  private void joinSegments(Segment a, Segment b, Point2D insideP)
+  private void joinOuterSegments(Segment a, Segment b, Point2D insideP)
   {
     double[] p0, p1;
     double dx, dy, l;
@@ -684,10 +675,10 @@
     switch( join )
       {
       case JOIN_MITER:
-        p0 = a.last.last();
+        p0 = a.last.cp2();
         p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
         double[] p2 = new double[]{b.P1.getX(), b.P1.getY()};
-        double[] p3 = b.first();
+        double[] p3 = b.cp1();
         Point2D p = lineIntersection(p0[0],p0[1],p1[0],p1[1],p2[0],p2[1],p3[0],p3[1], true);
         if( p == null || insideP == null )
           a.add(new LineSegment(a.last.P2, b.P1));
@@ -704,7 +695,7 @@
         break;
 
       case JOIN_ROUND:
-        p0 = a.last.last();
+        p0 = a.last.cp2();
         p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()};
         dx = p1[0] - p0[0];
         dy = p1[1] - p0[1];
@@ -714,7 +705,7 @@
         c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy);
 
         p0 = new double[]{b.P1.getX(), b.P1.getY()};
-        p1 = b.first();
+        p1 = b.cp1();
 
         dx = p0[0] - p1[0]; // backwards direction.
         dy = p0[1] - p1[1];
@@ -729,6 +720,29 @@
         a.add(new LineSegment(a.last.P2, b.P1));
         break;
       }
-    a.add(b);
   }
-} 
+
+  /**
+   * Join a and b segments, removing any overlap
+   */
+  private void joinInnerSegments(Segment a, Segment b, Point2D p)
+  {
+    double[] p0 = a.last.cp2();
+    double[] p1 = new double[] { a.last.P2.getX(), a.last.P2.getY() };
+    double[] p2 = new double[] { b.P1.getX(), b.P1.getY() };
+    double[] p3 = b.cp1();
+
+    if (p == null)
+      {
+        // Dodgy.
+        a.add(new LineSegment(a.last.P2, b.P1));
+        p = new Point2D.Double((b.P1.getX() + a.last.P2.getX()) / 2.0,
+                               (b.P1.getY() + a.last.P2.getY()) / 2.0);
+      }
+    else
+      // This assumes segments a and b are single segments, which is
+      // incorrect - if they are a linked list of segments (ie, passed in
+      // from a flattening operation), this produces strange results!!
+      a.last.P2 = b.P1 = p;
+  }
+}
Index: gnu/java/awt/peer/gtk/CairoGraphics2D.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/CairoGraphics2D.java,v
retrieving revision 1.34
diff -u -r1.34 CairoGraphics2D.java
--- gnu/java/awt/peer/gtk/CairoGraphics2D.java	26 Jul 2006 18:20:11 -0000	1.34
+++ gnu/java/awt/peer/gtk/CairoGraphics2D.java	27 Jul 2006 20:55:55 -0000
@@ -921,21 +921,12 @@
   public void draw(Shape s)
   {
     if ((stroke != null && ! (stroke instanceof BasicStroke))
-        || (comp instanceof AlphaComposite
-            && ((AlphaComposite) comp).getAlpha() != 1.0))
+        || (comp instanceof AlphaComposite && ((AlphaComposite) comp).getAlpha() != 1.0))
       {
-        // FIXME: This is a hack to work around BasicStrokes's current
-        // limitations wrt cubic curves.
-        // See CubicSegment.getDisplacedSegments().
-        if (stroke instanceof BasicStroke)
-          {
-            PathIterator flatten = s.getPathIterator(null, 1.0);
-            GeneralPath p = new GeneralPath();
-            p.append(flatten, false);
-            s = p;
-          }
-	fill(stroke.createStrokedShape(s));
-	return;
+        // Cairo doesn't support stroking with alpha, so we create the stroked
+        // shape and fill with alpha instead
+        fill(stroke.createStrokedShape(s));
+        return;
       }
 
     createPath(s);
Index: gnu/java/awt/java2d/QuadSegment.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/java2d/QuadSegment.java,v
retrieving revision 1.2
diff -u -r1.2 QuadSegment.java
--- gnu/java/awt/java2d/QuadSegment.java	10 Jul 2006 18:43:38 -0000	1.2
+++ gnu/java/awt/java2d/QuadSegment.java	27 Jul 2006 20:55:55 -0000
@@ -217,12 +217,12 @@
     P2 = p;
   }
 
-  public double[] first()
+  public double[] cp1()
   {
     return new double[]{cp.getX(), cp.getY()}; 
   }
 
-  public double[] last()
+  public double[] cp2()
   {
     return new double[]{cp.getX(), cp.getY()}; 
   }
Index: gnu/java/awt/java2d/CubicSegment.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/java2d/CubicSegment.java,v
retrieving revision 1.2
diff -u -r1.2 CubicSegment.java
--- gnu/java/awt/java2d/CubicSegment.java	10 Jul 2006 18:43:38 -0000	1.2
+++ gnu/java/awt/java2d/CubicSegment.java	27 Jul 2006 20:55:55 -0000
@@ -39,6 +39,7 @@
 package gnu.java.awt.java2d;
 
 
+import java.awt.geom.CubicCurve2D;
 import java.awt.geom.Point2D;
 
 /**
@@ -100,28 +101,67 @@
   }
 
   /**
-   * Get the "top" and "bottom" segments of this segment.
-   * First array element is p0 + normal, second is p0 - normal.
+   * Get the "top" and "bottom" segments of this segment. First array element is
+   * p0 + normal, second is p0 - normal.
    */
   public Segment[] getDisplacedSegments(double radius)
   {
+    // It is, apparently, impossible to derive a curve parallel to a bezier
+    // curve (unless it's a straight line), so we have no choice but to
+    // approximate the displaced segments. Similar to FlattenPathIterator.
+
+    Segment segmentTop = null;
+    Segment segmentBottom = null;
     this.radius = radius;
-    double x0 = P1.getX();
-    double y0 = P1.getY();
-    double x1 = cp1.getX();
-    double y1 = cp1.getY();
-    double x2 = cp2.getX();
-    double y2 = cp2.getY();
-    double x3 = P2.getX();
-    double y3 = P2.getY();
-    double[] p1 = normal(x0, y0, x1, y1);
-    double[] p2 = normal(x2, y2, x3, y3);
-
-    // FIXME: Doesn't compile.
-    // return new Segment[]{s1, s2};
-    return new Segment[0];
-  }
 
+    CubicCurve2D[] curves = new CubicCurve2D[10];
+    curves[0] = new CubicCurve2D.Double(P1.getX(), P1.getY(), cp1.getX(),
+                                        cp1.getY(), cp2.getX(), cp2.getY(),
+                                        P2.getX(), P2.getY());
+    int numCurves = 1;
+
+    // Hard-coded a recursion limit of 10 and flatness of 1... should we make
+    // this an option somewhere?
+    while (numCurves > 0)
+      {
+        // The curve is flat enough, or we've reached our recursion limit,
+        // so take the current start/end points and add it as a line segment
+        // to our final approximated curves
+        if (curves[numCurves - 1].getFlatnessSq() <= (radius / 3) || numCurves == 10)
+          {
+            Segment[] displaced = new LineSegment(
+                                                  curves[numCurves - 1].getP1(),
+                                                  curves[numCurves - 1].getP2()).getDisplacedSegments(radius);
+            if (segmentTop == null)
+              {
+                segmentTop = displaced[0];
+                segmentBottom = displaced[1];
+              }
+            else
+              {
+                segmentTop.add(displaced[0]);
+                segmentBottom.add(displaced[1]);
+              }
+            numCurves--;
+          }
+
+        // Otherwise, subdivide again and continue
+        else
+          {
+            CubicCurve2D left = new CubicCurve2D.Double();
+            CubicCurve2D right = new CubicCurve2D.Double();
+            curves[numCurves - 1].subdivide(left, right);
+            curves[numCurves - 1] = right;
+            curves[numCurves] = left;
+            curves[numCurves - 1] = right;
+            curves[numCurves] = left;
+            numCurves++;
+          }
+      }
+
+    return new Segment[] { segmentTop, segmentBottom };
+  }
+  
   public void reverse()
   {
     Point2D temp = P1;
@@ -132,12 +172,12 @@
     cp2 = temp;
   }
 
-  public double[] first()
+  public double[] cp1()
   {
     return new double[]{cp1.getX(), cp1.getY()}; 
   }
 
-  public double[] last()
+  public double[] cp2()
   {
     return new double[]{cp2.getX(), cp2.getY()}; 
   }
Index: gnu/java/awt/java2d/LineSegment.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/java2d/LineSegment.java,v
retrieving revision 1.2
diff -u -r1.2 LineSegment.java
--- gnu/java/awt/java2d/LineSegment.java	10 Jul 2006 18:43:38 -0000	1.2
+++ gnu/java/awt/java2d/LineSegment.java	27 Jul 2006 20:55:55 -0000
@@ -106,12 +106,12 @@
     P2 = p;
   }
 
-  public double[] first()
+  public double[] cp1()
   {
     return new double[]{P2.getX(), P2.getY()}; 
   }
 
-  public double[] last()
+  public double[] cp2()
   {
     return new double[]{P1.getX(), P1.getY()}; 
   }
Index: gnu/java/awt/java2d/Segment.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/java2d/Segment.java,v
retrieving revision 1.1
diff -u -r1.1 Segment.java
--- gnu/java/awt/java2d/Segment.java	24 Apr 2006 14:37:23 -0000	1.1
+++ gnu/java/awt/java2d/Segment.java	27 Jul 2006 20:55:55 -0000
@@ -42,24 +42,38 @@
 
 public abstract class Segment implements Cloneable
 {
-  // segment type, PathIterator segment types are used.
+  // Start and end points of THIS segment
   public Point2D P1;
   public Point2D P2;
+  
+  // Segments can be linked together internally as a linked list
+  public Segment first;
   public Segment next;
   public Segment last;
+  
+  // Half the stroke width
   protected double radius;
 
+  /**
+   * Create a new, empty segment
+   */
   public Segment()
   {
     P1 = P2 = null;
+    first = this;
     next = null;
     last = this;
   }
 
+  /**
+   * Add a segment to the polygon
+   * @param newsegment segment to add
+   */
   public void add(Segment newsegment)
   {
+    newsegment.first = first;
     last.next = newsegment;
-    last = last.next;
+    last = last.next.last;
   }
 
   /**
@@ -68,6 +82,7 @@
   public void reverseAll()
   {
     reverse();
+    first = last;
     Segment v = next;
     Segment former = this;
     next = null;
@@ -91,7 +106,7 @@
 
   /**
    * Get the normal vector to the slope of the line.
-   * Returns: 0.5*width*(norm of derivative of the (x0,y0)-(x1,y1) vector)
+   * @return vector of length radius, normal to the (x0,y0)-(x1,y1) vector)
    */
   protected double[] normal(double x0, double y0, double x1, double y1)
   {
@@ -117,6 +132,9 @@
     return new double[]{ dx, dy };
   }
 
+  /**
+   * Reverse the current segment
+   */
   public abstract void reverse();
 
   /**
@@ -125,7 +143,16 @@
    */
   public abstract Segment[] getDisplacedSegments(double radius);
 
-  public abstract double[] first();
-  public abstract double[] last();
+  /**
+   * Returns the coordinates of the first control point, or the start point
+   * for a line segment.
+   */
+  public abstract double[] cp1();
+  
+  /**
+   * Returns the coordinates of the second control point, or the end point
+   * for a line segment.
+   */
+  public abstract double[] cp2();
 
 }

Reply via email to