Hi,
unfortunately I cannot say whether your fixes are mathematically right. However
I tried some piccolo apps[0] which I wrote years ago. Some of them worked
already but the ones that indirectly make use of BasicStroke did not work yet.
With your patch these problems are solved.

The piccolo distribution[1] contains an example application. With your patch
most of them work now!

Great stuff.

cya
Robert

[0] - http://www.inf.fu-berlin.de/~rschuste/picoapps.tar.gz
[1] - http://www.cs.umd.edu/hcil/jazz/

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.
> 
> 
> 
> ------------------------------------------------------------------------
> 
> 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     25 Jul 2006 21:38:29 -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].getFlatness() <= 1 || 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      25 Jul 2006 21:38:29 -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/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      25 Jul 2006 21:38:29 -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/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  25 Jul 2006 21:38:29 -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();
>  
>  }
> Index: gnu/java/awt/peer/gtk/CairoGraphics2D.java
> ===================================================================
> RCS file: 
> /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/CairoGraphics2D.java,v
> retrieving revision 1.33
> diff -u -r1.33 CairoGraphics2D.java
> --- gnu/java/awt/peer/gtk/CairoGraphics2D.java        25 Jul 2006 20:58:19 
> -0000      1.33
> +++ gnu/java/awt/peer/gtk/CairoGraphics2D.java        25 Jul 2006 21:38:29 
> -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: 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 25 Jul 2006 21:38:30 -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;
> +  }
> +}

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to