Wow,
this looks interesting. I will test it tommorow with piccolo[0].

cya
Robert

[0] - 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