Java Examples In A Nutshell (3rd Edition) [Electronic resources]

O'Reilly Media, Inc

نسخه متنی -صفحه : 285/ 141
نمايش فراداده

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

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);
}
}