Manuel,

As you point out, strokes only apply to draws, not to fills. However,
you can have all the pieces you need from the source. If you have
a look at the WaveStroke source code, you will see that it's
createStrokedShape member first converts the input shape so that it
has a wavy outline you are looking for and then strokes that outline
with a BasicStorke to create the desired wave stroke effect.
What you are asking for is that intermediate shape (strokedShape in
the source code).
As an example, I have attached a modified version of WaveStroke to this
email that contains a member, createWavyOutline that returns the Shape
you are asking for.
I will include that additional member in future revisions of the GLF.

I hope this helps.
V.


Manuel Wasinger wrote:
>
> Thanks a lot for the tip with "WaveStroke" and "ShapeStroke".
> But there is one more problem occuring:
>
> As far as I understood the (Graphics2D)setStroke is used only by the "draw"-
> but not by the "fill"-methods.
> What shall I do if I need  a FILLED Shape with wavy outlines?
>
> Is there for example a possibility to use both the "setStroke" and the
> "setPaint" methods before calling "fillPolygon"?
>
> I really hope there is some solution as this is really important for my
> studies.
> Manuel.
>
> ===========================================================================
> To unsubscribe, send email to [EMAIL PROTECTED] and include in the body
> of the message "signoff JAVA2D-INTEREST".  For general help, send email to
> [EMAIL PROTECTED] and include in the body of the message "help".
/*
 * @(#)WaveStroke.java
 *
 * Copyright (c) 1999 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

package com.sun.glf.goodies;

import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;

import javax.swing.*;

import com.sun.glf.*;
import com.sun.glf.util.*;

/**
 * This Stroke iterates through the input Shape object's outline and
 * replaces all the segments with a 'wave' pattern. The resulting
 * Shape is then stroked by a regular BasicStroke.
 *
 * @author      Vincent Hardy
 * @version     1.1, 04/10/2000 Added createWavyOutline member.
 *              1.0, 09/15/1998
 * @see         java.awt.Stroke
 */
public class WaveStroke implements Stroke{
  /**
   * Implementation.
   * @see #addSegment
   */
  private AffineTransform t = new AffineTransform();

  /**
   * Used by the FlatteningPathIterator
   * @see #createStrokedShape
   */
  private static final float FLATNESS = 1;

  /**
   * Default wave constants
   */
  private static final float DEFAULT_WAVE_LENGTH = 12;
  private static final float DEFAULT_WAVE_AMPLITUDE = 4;
  private static final float DEFAULT_WIDTH = 1;

  /*
   * Coordinates of the base pattern we use to
   * reproduce along the path of the stroked Shape
   */
  private float positivePhasePoints[] = { 1, 2, 3, 2,
                                                                                       
                                                                                 5, 2, 
6, 0,
                                                                                       
                                                                                 7, 
-2, 9, -2,
                                                                                       
                                                                                 11, 
-2, 12, 0 };
  private float negativePhasePoints[] = { 1, -2, 3, -2,
                                                                                       
                                                                                 5, 
-2, 6, 0,
                                                                                       
                                                                                 7, 2, 
9, 2,
                                                                                       
                                                                                 11, 
2, 12, 0 };

  private float waveLength = DEFAULT_WAVE_LENGTH;
  private float waveAmplitude = DEFAULT_WAVE_AMPLITUDE;
  private float width = DEFAULT_WIDTH;

  /**
   * BasicStroke used to stroke the outline of the Shape
   * after we replaced line segments with wave patterns
   */
  private BasicStroke basicStroke;

  /**
   * Used to bundle parameters to addSegment
   * @see #addSegment
   */
  static class AddSegmentControl{
    float x, y, dx, dy, mx, my, startX, startY;
    GeneralPath s;
    boolean start, negativePhase, startOverride, isClose;
  }

  /**
   * Constructor
   */
  public WaveStroke(float width){
    this(width, DEFAULT_WAVE_LENGTH, DEFAULT_WAVE_AMPLITUDE);
  }

  /**
   * @param waveLength lenght of each wave pattern. Should be more than zero.
   * @param waveAmplitude amplitude of each wave pattern. Should be more than zero.
   * @param width width of the wave
   */
  public WaveStroke(float width, float waveLength, float waveAmplitude){
    if(waveLength<=0 || waveAmplitude<=0 || width<=0)
      throw new IllegalArgumentException("Arguments should be greater than zero");

    // basicStroke is used to provide the appropriate thickness to the stroked path.
    // see createStrokedShape
    basicStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);

    // Transform phase points according to length and amplitude
    AffineTransform scale = new AffineTransform();
    scale.setToScale(waveLength/DEFAULT_WAVE_LENGTH, 
waveAmplitude/DEFAULT_WAVE_AMPLITUDE);
    scale.transform(positivePhasePoints, 0, positivePhasePoints, 0, 
positivePhasePoints.length/2);
    scale.transform(negativePhasePoints, 0, negativePhasePoints, 0, 
negativePhasePoints.length/2);

    this.waveLength = waveLength;
    this.waveAmplitude = waveAmplitude;
    this.width = width;
  }

        /**
         * @param Shape whose outline is to be converted with a wave form.
         */
        public Shape createWavyOutline(Shape shape){
    // Flatten path.
    MunchTransform shapeMuncher = new MunchTransform(FLATNESS);
    PathIterator pi = shapeMuncher.transform(shape).getPathIterator(null, FLATNESS);

    // PathIterator pi = shape.getPathIterator(null, FLATNESS);

    // Iterate through the path and process each
    // line segment.
    float seg[] = new float[6];
    int segType = 0;
    boolean start = false;
    boolean negativePhase = false;
    AddSegmentControl ctl = new AddSegmentControl();
    GeneralPath strokedShape = new GeneralPath(); // Return value
    ctl.s = strokedShape;

    while(!pi.isDone()){
      segType = pi.currentSegment(seg);
      switch(segType){
      case PathIterator.SEG_MOVETO:
                                ctl.x = seg[0];
                                ctl.y = seg[1];
                                ctl.mx = ctl.x;
                                ctl.my = ctl.y;
                                ctl.start = true;
                                break;
      case PathIterator.SEG_LINETO:
                                ctl.dx = seg[0];
                                ctl.dy = seg[1];
                                ctl.isClose = (ctl.dx==ctl.mx && ctl.dy==ctl.my);
                                addSegment(ctl);
                                ctl.start = false;
                                ctl.x = ctl.dx;
                                ctl.y = ctl.dy;
                                break;

      case PathIterator.SEG_CLOSE:
                                ctl.isClose = true;
                                ctl.dx = ctl.mx;
                                ctl.dy = ctl.my;
                                addSegment(ctl);
                                ctl.start = false;
                                ctl.x = ctl.mx;
                                ctl.y = ctl.my;
                                break;

      case PathIterator.SEG_QUADTO:
      case PathIterator.SEG_CUBICTO:
      default:
                                throw new Error("Illegal seg type : " + segType);
      }
      pi.next();
    }
                return strokedShape;
        }

  /**
   * Implementation of the Stroke interface. The input Shape is flattened
   * by a FlatteningPathIterator. The resulting path is iterated through and
   * line segments are replaced by a wave curve.
   * @see #addSegment
   * @see java.awt.geom.PathIterator
   * @see java.awt.geom.FlatteningPathIterator
   */
  public Shape createStrokedShape(Shape shape){
    return basicStroke.createStrokedShape(createWavyOutline(shape));
  }

  /**
   * Replaces the (x,y) to (dx, dy) segment by a succession of wave patterns.
   * This will fit as many full waves as possible. The remaining space is filled
   * with a quad curve to (dx, dy)
   * @see #createStrokedShape
   * @return whether or not next segment should be a negative phase segment.
   */
  private void addSegment(AddSegmentControl ctl){
    // For readibility
    float x = ctl.x, y = ctl.y, dx = ctl.dx, dy = ctl.dy;
    GeneralPath s = ctl.s;

    // Check if there is a start override
    if(!ctl.start && ctl.startOverride){
      x = ctl.startX;
      y = ctl.startY;
    }

    if(ctl.start)
      ctl.s.moveTo(x, y);

    // Use set of points depending on the phase
    float wavePoints[] = positivePhasePoints;
    if(ctl.negativePhase)
      wavePoints = negativePhasePoints;

    // Do not process if this is an empty segment
    if(x==dx && y==dy)
      return;

    // Process segment length
    float d = (float)Point2D.distance(x, y, dx, dy);

    // Wave rotation angle
    double theta = Math.atan2((dy-y), (dx-x));

    // Number of waves which fit onto the segment
    float ratio = d/waveLength;
    int n = (int)Math.floor(ratio);

    // Reset transform
    t.setToScale(1, 1);

    // Translate the wave to its initial position
    t.translate(x, y);

    // First, rotate the wave
    float working[] = new float[wavePoints.length];
    t.rotate(theta);

    if(n>1 && (ratio-n)<0.5){
      float scale = ratio/(float)n;
      t.scale(scale, 1);
      ratio = n;
    }

    t.transform(wavePoints, 0, working, 0, wavePoints.length/2);

    // Repeat the wave n times along the path
    float xShift=(dx-x)/ratio;
    float yShift=(dy-y)/ratio;

    int nQuads = working.length/4;
    t.setToTranslation(xShift, yShift);

    for(int i=0; i<n; i++){
      for(int j=0; j<nQuads; j++)
                                s.quadTo(working[4*j], working[4*j+1],
                                                                 working[4*j+2], 
working[4*j+3]);
      t.transform(working, 0, working, 0, working.length/2);
    }

    // Finish up with an ad-hoc quad curve

    // Squeeze a half wave if there is enough space
    if(ctl.isClose){
      if(ratio-n > 0.75){
                                s.quadTo(working[0], working[1], working[2], 
working[3]);
                                s.quadTo(working[4], working[5], working[6], 
working[7]);
                                s.quadTo(working[8], working[9], working[10], 
working[11]);
                                s.quadTo(working[12], working[13], dx, dy);
                                ctl.startOverride = false;
      }
      else if(ratio-n > 0.25){
                                s.quadTo(working[0], working[1], working[2], 
working[3]);
                                s.quadTo(working[4], working[5], dx, dy);
                                ctl.negativePhase = !ctl.negativePhase;
                                ctl.startOverride = false;
      }
      else{
                                s.lineTo(dx, dy);
                                ctl.startOverride = false;
      }
    }
    else{
      if(ratio-n >= 0.5){
                                s.quadTo(working[0], working[1], working[2], 
working[3]);
                                s.quadTo(working[4], working[5], working[6], 
working[7]);
                                ctl.negativePhase = !ctl.negativePhase;
                                ctl.startOverride = true;
                                ctl.startX = x + (n + 0.5f)*xShift;
                                ctl.startY = y + (n + 0.5f)*yShift;
      }
      else{
                                ctl.startOverride = true;
                                ctl.startX = x + n*xShift;
                                ctl.startY = y + n*yShift;
      }
    }
  }

  public float getWidth(){
    return width;
  }
  public float getWaveLength(){
    return waveLength;
  }

  public float getWaveAmplitude(){
    return waveAmplitude;
  }

  /**
   * Unit testing
   */
  public static void main(String args[]){
    JFrame frame = new JFrame("WaveStroke unit testing");
    frame.getContentPane().setBackground(Color.white);

    Line2D line = new Line2D.Double(0, 0, 100, 0);
    Rectangle square = new Rectangle(0, 0, 100, 100);
    Ellipse2D disc = new Ellipse2D.Double(0, 0, 100, 100);
    GeneralPath eiffel = new GeneralPath();
    eiffel.moveTo(80.f, 40.f);
    eiffel.lineTo(100.f, 40.f);
    eiffel.quadTo(100.f, 100.f, 140.f, 160.f);
    eiffel.lineTo(110.f, 160.f);
    eiffel.curveTo(110.f, 120.f, 70.f,
                                                                         120.f, 70.f, 
160.f);
    eiffel.lineTo(40.f, 160.f);
    eiffel.quadTo(80.f, 100.f, 80.f, 40.f);

    GeneralPath triangle = new GeneralPath();
    triangle.moveTo(50, 0);
    triangle.lineTo(100, 100);
    triangle.lineTo(0, 100);
    triangle.closePath();

    Shape shapes[] = { line, square, disc, eiffel, triangle };

    Stroke strokes[] = { new BasicStroke(),
                                                                                       
          new WaveStroke(3),
                                                                                       
          new WaveStroke(1),
                                                                                       
          new WaveStroke(1, 24, 4),
                                                                                       
          new WaveStroke(3, 12, 8),
                                                                                       
          new WaveStroke(1, 12, 8),
                                                                                       
          new WaveStroke(3, 6, 4) };


    frame.getContentPane().setLayout(new GridLayout(0, strokes.length + 1));

    for(int i=0; i<shapes.length; i++){
      Shape shape = shapes[i];
      for(int j=0; j<strokes.length; j++)
                                addTestComponent(frame, strokes[j], shape);

                        // Add a final test component to test createWavyOutline
                        addTestWavyComponent(frame, (WaveStroke)strokes[1], shape);
    }

    frame.addWindowListener(new WindowAdapter(){
                                public void windowClosing(WindowEvent evt){
                                        System.exit(0);
                                }
                        });

    frame.pack();
    frame.setVisible(true);
  }

  private static void addTestComponent(JFrame frame, Stroke stroke, Shape shape){
    Rectangle bounds = shape.getBounds();
    LayerComposition cmp = new LayerComposition(new Dimension(bounds.width + 20, 
bounds.height + 20));
    ShapeLayer layer = new ShapeLayer(cmp, shape, new StrokeRenderer(Color.gray, 
stroke), Position.CENTER);
    // ShapeLayer plainLayer = new ShapeLayer(cmp, shape, new 
StrokeRenderer(Color.black, new BasicStroke()), Position.CENTER);
    cmp.setBackgroundPaint(Color.white);
    cmp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
RenderingHints.VALUE_ANTIALIAS_ON);
    cmp.setLayers(new Layer[]{layer});
    frame.getContentPane().add(new CompositionComponent(cmp));
  }

  private static void addTestWavyComponent(JFrame frame, WaveStroke stroke, Shape 
shape){
    Rectangle bounds = shape.getBounds();
    LayerComposition cmp = new LayerComposition(new Dimension(bounds.width + 20, 
bounds.height + 20));
    ShapeLayer layer = new ShapeLayer(cmp, stroke.createWavyOutline(shape), new 
FillRenderer(Color.gray), Position.CENTER);
                // ShapeLayer plainLayer = new ShapeLayer(cmp, shape, new 
StrokeRenderer(Color.black, new BasicStroke()), Position.CENTER);
    cmp.setBackgroundPaint(Color.white);
    cmp.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
RenderingHints.VALUE_ANTIALIAS_ON);
    cmp.setLayers(new Layer[]{layer});
    frame.getContentPane().add(new CompositionComponent(cmp));
  }
}

Reply via email to