Paper Garden is a program written in about 300 lines of PaperScript using Paper.js. It generates stylized drawings of plants arranged in a wall garden. The plants are rendered as clumps of leaves, sometimes with hanging vines or long drooping flowers. All of this is drawn with rough little circles meant to simulate pen doodles from my sketchbook.
The image above was drawn by the Paper Garden script. For this image, the script was configured to generate, compose, and draw 255 plants. The script allows you to configure the number and spacing of plants; the number, spacing, and sorting of leaves on each plant; and how rough, sloppy, and thick the strokes are. This allows the script to squeeze out a range of different looks.
You can configure, edit, and run the script in the editor below, or go on to read a breakdown of how it works.
Paper Garden is written in a procedural style and comprises 18 functions. The diagram below organizes these functions into general responsibilities and maps out the key relationships between them.
The application doesn’t have much of an interface: you load it and it draws plants.
makeScene() is the main entry point for the application.
These functions build the structure of the drawing.
createPlants() decides where plants will go and what kind they should be.
createPlant() creates the leaves, flowers, and vines that make up an individual plant.
createVine() draw the plant parts.
These functions generate an array of nicely arranged points.
clusterPoints() creates some randomly placed points near
0,0 and then calls
relaxPoints() to space them out.
relaxPoints() repeatedly compares each pair of points and pushes them apart if they are too close.
Each of the
sort*() functions take an array of points and reorders them based on their positions. Shapes drawn early will be covered up by later shapes, so the order of the points in the array has a big impact on the look of the drawing.
For more on point placing see the chapter on strategies.
create*() functions all call
drawCircle() to create the individual circles in the drawing. The look of the circles created by
drawCircle() is configured through global parameters.
These functions are called from all over the program.
map() map a value from one range to another
randomRange() creates a random value in a given range
randomPoint() creates a random Point between
pick() chooses and returns a random item from an array
shuffle() randomly reorders the items in an array
Paper Garden produces images with a somewhat hand-drawn look based on a doodle from my sketchbook, but
new Path.Circle() makes perfect circles.
drawCiricle() uses a few tricks and techniques to achieve a more hand-drawn appearance.
In my sketchbook doodle, I drew the circles front to back. When drawing with a pen, it is easy to not draw part of the circle and hard to erase marks. In Paper.js the opposite is true. New circles easily cover any marks already on the drawing, and drawing only part of the circle requires substantial extra work. Because of this, Paper Flowers draws from back to front.
This approach works fine for rasterized output on the screen but won’t work on vector outputs like pen plotters or laser engraving which will draw the entire path of each circle even if another circle covers it. See
Plotting the Drawing below.
The circles in my doodle are not very round. Paper Garden simulates this by displacing each control point of the Bezier curve in a random direction. The amount of displacement is controlled by the
The lines in the circles don’t always perfectly meet. To recreate this,
drawCircle() creates an unstroked white circle behind the stroked one. This circle is made larger to create gaps—
GAPPY—and randomly offset so the gaps are not even—
The strokes in the doodle have uneven widths and uneven color. In particular, the first little bit of each stroke is heavier than the rest. To simulate this,
drawCircle() duplicates the circle path and draws it again with a slightly thicker stroke. This thicker stroke is broken up using
dashArray to create a randomized dash pattern.
The circles produced by
drawCircle() are rough, gappy, and uneven, but they are still much more “perfect” than the strokes on the doodle. This is largely an artistic judgment. I wanted to keep some of the richness and subtlety of the hand-drawn doodle but wanted the final image to look less sloppy and rushed.
Also, Paper Flower generates drawings that are much more complex than the doodle above with hundreds of plants and thousands of leaves. This added complexity already makes the overall drawing more rich and interesting. The stroke quality should support that interest without distracting from it.
Individual plants are generated by the
createPlant() function. This function places the leaves by following these steps:
The layout of the entire garden is generated by
createPlants(). This function follows the same steps used to generate the leaves with one addition: it removes some of the points after they are arranged.
Thats pretty much it: decide where plants should be, decide where the leaves should be, draw the leaves. To add a little more variation and interest, some of the leaves are swaped out for vines or flowers.
With different drawing parameters Paper Garden can generate a range of looks, but I like the slightly hand-drawn line-drawing style best, so I tried drawing it on a Silhouette Cameo pen plotter. The Paper Garden script can export vector files to draw with the plotter, but a little extra work needs to be done to get them to work correctly.
The version of the script above draws the circles with a white fill to cover up the lines from other circles. Since the pen plotter only draws lines, using fills to cover things up isn’t possible. I created a version of the script for plotting that procedurally removes the hidden lines.
This script shows how I removed the lines.
And here are some of the plots.