Fun with Java2D - Strokes

Introduction

When you're drawing shapes with Java2D, wouldn't it be nice to spice them up a bit?
Java2D gives you dashed lines, different line joins and caps and line widths, but what if you want to draw
a railway line or a county boundary on a map? How about double lines? How about drawing text along a path?
This article explains how to use the Java2D Stroke interface to do all this and more.

License

The downloadable source code on this page is released under the Apache License. Basically,
this means that you are free to do whatever you like with this code,
but it's not my fault if your satellite/nuclear power station/missile system fails as a
result. Have fun!

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this code
except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific language governing permissions and
limitations under the License.

Strokes

Java comes with just one sort of line shape - the BasicStroke class.
BasicStroke draws wide lines with round or square ends and round, bevelled
or mitered joins. However, Java allows you to write your own stroke implementations which lets you have
any sort of line you can think of.

A 10-point wide BasicStroke

The thing which does the work when drawing lines is the Stroke interface. This has exactly one interesting
method:

public Shape createStrokedShape( Shape shape );

All the method does is return a Shape which describes the outline of the stroke. When you call Graphics2D.draw(),
all it does is call the current Stroke's getStrokedShape() method and then fills the result. Thus the code:

g.draw( shape );

is more-or-less equivalent to:

g.fill( g.getStroke().getStrokedShape( shape ) );

Note: At the time of writing, Mac OS X Java produces an error message if you try to use a custom stroke, even though
it actually works correctly. The workround is to use the code above instead of calling the draw() method.

A Simple Stroke: CompositeStroke

Enough of the theory: let's get down to writing a Stroke of our own. You might think that we'll need lots of geometry
for this - and mostly you'd be right - but we can do some simpler things. One interesting thing we can do is take
an existing stroke and draw the outline of the stroke using another stroke. A picture is worth a thousand words here:

Drawing Shapes: ShapeStroke

Dashed lines are good, but sometime you need something more. Try drawing one of those lines on a map which consist
of lots of crosses or railway lines and you'll see what I mean. Here I'll show a Stroke which will take an
array of existing Shapes, which you can think of as the dash shapes, and will draw them along the path to be drawn,
like this (note how the points of the stars follow the path):

A ShapeStroke using a star and a circle.

Here's the code to produce the figure. Star is a Shape class in the shape of, er, a star.

Now we're going to have to get into geometry, but it won't be too hard. What we need to do is to walk the path,
drawing a shape every so often rotated so that it follows the path. We'll flatten the path first so that we don't
have to worry about curves and only have straight line segments to deal with. We'll walk along the path, segment
by segment, keeping track of how far we've moved. If it's time to place a shape, we'll do so. The only tricky part
is keeping track of segments which are too short to have a shape in, but still contribute their length, and segments
which are long enough to require several shapes along their length. Note that when we reach the end of the path,
it may not be time to draw a shape which means that the stroke will appear to not reach the end of the path.
We can work round this, if desired, by measuring the path before we start and adjusting the spacing so that it
fits exactly.

Text Along a Path

Now that we can draw all sorts of lines using shapes, it's worth remembering that characters are just shapes.
We can produce a Stroke which draws text along a path by simply modifying ShapeStroke to draw characters and
to take account of the different width of each character.

More fun with Strokes: Adding Randomness

Sometimes, Java2D drawing is just too regular. It would be nice to introduce a bit of randomness. WobbleStroke
is a stroke which takes an existing stroke and adds random points to the resulting Shape. You can apply it to
any Stroke, but here I've shown the effect it has on a BasicStroke.

Making a wobbly BasicStroke with WobbleStroke

Note that simply moving the control points of the path won't have this effect - we need to add new points to
the path. This is easily implemented using the same algorithm we used for ShapeStroke, but instead of drawing
a shape at each point, we add a new randomized point to the output path.

Zigzags

Suppose you're drawing a sewing pattern and you need to have some of those zigzag stiches in it. You could
draw a zigzag line, but what you really want to do is draw a straight line along the stitches and have it
come out zigzag on the screen. By now, you may be suspecting that I have a Stroke to do this, and you'd be right -
ZigzagStroke will do this. This uses the same technique we've used everywhere before, but instead of drawing
discrete shapes at each point, it make sure that the new shapes (the zigs and zags) join together correctly.

Zigzag lines with ZigzagStroke

Of course, we're not limited to zigzag - you could use the same technique to do any sort of repeating pattern,
such as wavy lines, square waves or loops.

Compound Strokes

Earlier, we saw CompositeStroke, which used one Stroke to stroke the output of another. Another thing which
is useful is to add strokes together. Remember the railway line example? We could use ShapeStroke to draw the
railway sleepers, but it won't draw the track line very well. The answer is to draw the track line separately
with BasicStroke. We'd like to have a simple Stroke to do this though - one that we can stash away in a library
and use directly. CompoundStroke will do this. It takes two strokes and returns the union, interersection, or difference
of the two.