12.13 Custom Strokes

As we saw in Example 12-9, the Stroke class converts a line-drawing operation into an area-filling operation by taking the Shape whose outline is to be drawn and returning a stroked shape that represents the outline itself. Because Stroke is such a simple interface, it is relatively easy to implement custom Stroke classes that perform interesting graphical effects. Example 12-17 includes four custom Stroke implementations that it uses along with a simple BasicStroke object to produce the output shown in Figure 12-12.

Figure 12-12. Special effects with custom Stroke classes
figs/jex3_1212.gif

You should pay particular attention to the ControlPointsStroke and SloppyStroke implementations. These classes are interesting because they use a PathIterator object to break a shape down into its component line and curve segments (just the opposite of what was done in the Spiral class shown in Example 12-15). These two custom Stroke classes also use the GeneralPath class of java.awt.geom to build a custom shape out of arbitrary line and curve segments (which shows how closely linked the GeneralPath class and the PathIterator interface are).

Example 12-17. CustomStrokes.java
package je3.graphics; import java.awt.*; import java.awt.geom.*; import java.awt.font.*; /** A demonstration of writing custom Stroke classes */ public class CustomStrokes implements GraphicsExample {     static final int WIDTH = 750, HEIGHT = 200;        // Size of our example     public String getName( ) {return "Custom Strokes";} // From GraphicsExample     public int getWidth( ) { return WIDTH; }            // From GraphicsExample     public int getHeight( ) { return HEIGHT; }          // From GraphicsExample     // 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 draw(Graphics2D g, Component c) {         // 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         }     } } /**  * 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);     } }


Java Examples in a Nutshell
Java Examples in a Nutshell, 3rd Edition
ISBN: 0596006209
EAN: 2147483647
Year: 2003
Pages: 285

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net