Paths

Paths are a way to construct complex shapes, implemented by the Path algebra and the OpenPath and ClosedPath types. A path specifies how to control a virtual pen to draw a shape. There are three different commands that a path can contain:

  • straight lines;
  • bezier curves; and
  • straight line movement that doesn't draw to the screen.

Here's an example of a path. Notice that it uses all of the different commands described above.

import doodle.core.*
import doodle.java2d.*
import doodle.syntax.all.*

val feather =
  ClosedPath.empty
    .lineTo(100, 100)
    .curveTo(90, 75, 90, 25, 10, 10)
    .moveTo(100, 100)
    .curveTo(75, 90, 25, 90, 10, 10)
    .path

Drawing this path creates the output below.

A path with straight lines and bezier curves

Open and Closed Path

The example above is a ClosedPath, which suggests there is also an OpenPath. This is correct. The difference is how the path ends. If a ClosedPath doesn't end at the point it started (which is the origin), it will have a straight line inserted joining the end to the start. An OpenPath will not.

Here's an example showing the difference. We create a curve, drawn as both an open and a closed path.

val open =
  OpenPath.empty.curveTo(90, 0, 100, 10, 50, 50).path.strokeColor(Color.red)

val closed =
  ClosedPath.empty.curveTo(90, 0, 100, 10, 50, 50).path.strokeColor(Color.blue)

val curves = open.beside(closed)

Notice how the open path is just the curve, while the closed path has an extra line added joining the start and end points.

A curve drawn as an open and as a closed path

Creating Paths

The idiomatic way to create a path is by calling the empty method on either ClosedPath or OpenPath, and then calling the lineTo, curveTo, and moveTo methods on the resulting object. This is the method used in all the examples we've seen so far. An alternative approach is to creates instances of PathElement and create a path from a List[PathElement].

The close method converts an OpenPath to a ClosedPath, and the open method does the reverse.

Creating Pictures from Paths

To convert a path to a Picture you can use the path syntax method, which is demonstrated in the examples above, or use the path method on the Picture object.

To create an Image from a path, use the path method on Image. Here's an example.

import doodle.image.*

val openPath = OpenPath.empty.lineTo(100, 100)

val closedPath = ClosedPath.empty.lineTo(100, 100)

val path1 = Image.path(openPath)
val path2 = Image.path(closedPath)

Utilities

There are several utilities to create common shapes. For example, OpenPath has methods to create arcs and regular polygons, and ClosedPath has methods to create circles and pie slices. See the companion objects for both for the complete list. Many of these also exist as methods on PathElement.

One particularly useful method is interpolatingSpline, which creates a curve that intersects a given sequence of points. In the example below we create a list of points from a sine curve, and interpolate a spline between the points.

val points =
  for (x <- 0.to(360)) yield Point(x, x.degrees.sin * 100)

val curve = Picture.interpolatingSpline(points.toList)

The output, shown below, shows that a smooth curve is created.

A curve that intersects a sequence of points

Implementation

Image fully supports path. The underlying algebra is Path. ClosedPath, OpenPath, and PathElement are all part of doodle.core, and not tied to either algebras or Image.

Copyright © Noel Welsh. Built with 💖