CustomStrokes.java Source code

Java tutorial

Introduction

Here is the source code for CustomStrokes.java

Source

/*
 * Copyright (c) 2000 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 2nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book (recommended),
 * visit http://www.davidflanagan.com/javaexamples2.
 */

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.GlyphVector;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;

/** A demonstration of writing custom Stroke classes */
public class CustomStrokes extends JPanel {
    static final int WIDTH = 750, HEIGHT = 200; // Size of our example

    public String getName() {
        return "Custom Strokes";
    }

    public int getWidth() {
        return WIDTH;
    }

    public int getHeight() {
        return HEIGHT;
    }

    // These are the various stroke objects we'll demonstrate
    Stroke[] strokes = new Stroke[] { new BasicStroke(4.0f), // The standard,
            // predefined
            // stroke
            new NullStroke(), // A Stroke that does nothing
            new DoubleStroke(8.0f, 2.0f), // A Stroke that strokes twice
            new ControlPointsStroke(2.0f), // Shows the vertices & control
            // points
            new SloppyStroke(2.0f, 3.0f) // Perturbs the shape before stroking
    };

    /** Draw the example */
    public void paint(Graphics g1) {
        Graphics2D g = (Graphics2D) g1;
        // Get a shape to work with. Here we'll use the letter B
        Font f = new Font("Serif", Font.BOLD, 200);
        GlyphVector gv = f.createGlyphVector(g.getFontRenderContext(), "B");
        Shape shape = gv.getOutline();

        // Set drawing attributes and starting position
        g.setColor(Color.black);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.translate(10, 175);

        // Draw the shape once with each stroke
        for (int i = 0; i < strokes.length; i++) {
            g.setStroke(strokes[i]); // set the stroke
            g.draw(shape); // draw the shape
            g.translate(140, 0); // move to the right
        }
    }

    public static void main(String[] a) {
        JFrame f = new JFrame();
        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        f.setContentPane(new CustomStrokes());
        f.setSize(750, 200);
        f.setVisible(true);
    }

}

/**
 * This Stroke implementation does nothing. Its createStrokedShape() method
 * returns an unmodified shape. Thus, drawing a shape with this Stroke is the
 * same as filling that shape!
 */

class NullStroke implements Stroke {
    public Shape createStrokedShape(Shape s) {
        return s;
    }
}

/**
 * This Stroke implementation applies a BasicStroke to a shape twice. If you
 * draw with this Stroke, then instead of outlining the shape, you're outlining
 * the outline of the shape.
 */

class DoubleStroke implements Stroke {
    BasicStroke stroke1, stroke2; // the two strokes to use

    public DoubleStroke(float width1, float width2) {
        stroke1 = new BasicStroke(width1); // Constructor arguments specify
        stroke2 = new BasicStroke(width2); // the line widths for the strokes
    }

    public Shape createStrokedShape(Shape s) {
        // Use the first stroke to create an outline of the shape
        Shape outline = stroke1.createStrokedShape(s);
        // Use the second stroke to create an outline of that outline.
        // It is this outline of the outline that will be filled in
        return stroke2.createStrokedShape(outline);
    }
}

/**
 * This Stroke implementation strokes the shape using a thin line, and also
 * displays the end points and Bezier curve control points of all the line and
 * curve segments that make up the shape. The radius argument to the constructor
 * specifies the size of the control point markers. Note the use of PathIterator
 * to break the shape down into its segments, and of GeneralPath to build up the
 * stroked shape.
 */

class ControlPointsStroke implements Stroke {
    float radius; // how big the control point markers should be

    public ControlPointsStroke(float radius) {
        this.radius = radius;
    }

    public Shape createStrokedShape(Shape shape) {
        // Start off by stroking the shape with a thin line. Store the
        // resulting shape in a GeneralPath object so we can add to it.
        GeneralPath strokedShape = new GeneralPath(new BasicStroke(1.0f).createStrokedShape(shape));

        // Use a PathIterator object to iterate through each of the line and
        // curve segments of the shape. For each one, mark the endpoint and
        // control points (if any) by adding a rectangle to the GeneralPath
        float[] coords = new float[6];
        for (PathIterator i = shape.getPathIterator(null); !i.isDone(); i.next()) {
            int type = i.currentSegment(coords);
            Shape s = null, s2 = null, s3 = null;
            switch (type) {
            case PathIterator.SEG_CUBICTO:
                markPoint(strokedShape, coords[4], coords[5]); // falls through
            case PathIterator.SEG_QUADTO:
                markPoint(strokedShape, coords[2], coords[3]); // falls through
            case PathIterator.SEG_MOVETO:
            case PathIterator.SEG_LINETO:
                markPoint(strokedShape, coords[0], coords[1]); // falls through
            case PathIterator.SEG_CLOSE:
                break;
            }
        }

        return strokedShape;
    }

    /** Add a small square centered at (x,y) to the specified path */
    void markPoint(GeneralPath path, float x, float y) {
        path.moveTo(x - radius, y - radius); // Begin a new sub-path
        path.lineTo(x + radius, y - radius); // Add a line segment to it
        path.lineTo(x + radius, y + radius); // Add a second line segment
        path.lineTo(x - radius, y + radius); // And a third
        path.closePath(); // Go back to last moveTo position
    }
}

/**
 * This Stroke implementation randomly perturbs the line and curve segments that
 * make up a Shape, and then strokes that perturbed shape. It uses PathIterator
 * to loop through the Shape and GeneralPath to build up the modified shape.
 * Finally, it uses a BasicStroke to stroke the modified shape. The result is a
 * "sloppy" looking shape.
 */

class SloppyStroke implements Stroke {
    BasicStroke stroke;

    float sloppiness;

    public SloppyStroke(float width, float sloppiness) {
        this.stroke = new BasicStroke(width); // Used to stroke modified shape
        this.sloppiness = sloppiness; // How sloppy should we be?
    }

    public Shape createStrokedShape(Shape shape) {
        GeneralPath newshape = new GeneralPath(); // Start with an empty shape

        // Iterate through the specified shape, perturb its coordinates, and
        // use them to build up the new shape.
        float[] coords = new float[6];
        for (PathIterator i = shape.getPathIterator(null); !i.isDone(); i.next()) {
            int type = i.currentSegment(coords);
            switch (type) {
            case PathIterator.SEG_MOVETO:
                perturb(coords, 2);
                newshape.moveTo(coords[0], coords[1]);
                break;
            case PathIterator.SEG_LINETO:
                perturb(coords, 2);
                newshape.lineTo(coords[0], coords[1]);
                break;
            case PathIterator.SEG_QUADTO:
                perturb(coords, 4);
                newshape.quadTo(coords[0], coords[1], coords[2], coords[3]);
                break;
            case PathIterator.SEG_CUBICTO:
                perturb(coords, 6);
                newshape.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
                break;
            case PathIterator.SEG_CLOSE:
                newshape.closePath();
                break;
            }
        }

        // Finally, stroke the perturbed shape and return the result
        return stroke.createStrokedShape(newshape);
    }

    // Randomly modify the specified number of coordinates, by an amount
    // specified by the sloppiness field.
    void perturb(float[] coords, int numCoords) {
        for (int i = 0; i < numCoords; i++)
            coords[i] += (float) ((Math.random() * 2 - 1.0) * sloppiness);
    }
}