A couple of months ago, when I took a deep dive into learning Clojure, I found out about a library called Quil. It provides bindings for the popular Processing library, a DSL for generating graphics that runs on the JVM.
Generative art as a niche is something I hadn’t exactly looked into very much, so I wasn’t aware of what was being done, or what was possible. Ask a hundred people what they consider generative art to be, andy you’ll likely get a hundred different opinions. The common thread, however, is that at least some component of the artwork is algorithmically generated. Typically, one exploits the computer’s strengths to add to the artwork something that would be hard to realize otherwise, like true randomness, infinitely repeating patterns or a time component that is in principle unbounded in time. Many of these aspects are also clearly examplified in so-called generative music, that uses algorithms and randomness to generate (typically infinite) pieces of music. An absolute must-listen is Alex Bainter’s generative.fm.
Inspired by a couple of cool examples and talks, I figured it’d be fun and good practice playing around with Quil for a bit. Why?
(< a b)
rather than
(a < b)
. Muscle memory takes repetition.In particular, I really enjoyed the talks by Dan Lidral Porter and Tyler Hobbs. The latter also runs an amazing website showcasing some of his art, as well as some in-depth commentary on his process. I thought I’d share some beginner tips on how to get started using Quil and how to set everything up.
Disclaimer: I will refrain from referring to the examples below as “art”. I feel that for it to even remotely qualify as such, there ought to be a creative and communicative component to the work, which I think is not the case. Consider it a guide to creating pretty pictures with code.
To get us started, I figured I’d run over the examples in Dan Porter’s talk
in a little more detail. Let’s start off by having
Leiningen scaffold out a
new Quil project for us: lein new quil sierpinski
. (You are using
Leiningen, right?)
This command scaffolds out a simple animation of a moving circle. The actual
code is located in the src/sierpinski/
subdirectory (or whichever project
name you chose). It defaults to using Quil’s fun-mode
(“functional mode”)
middleware. Functional mode essentially means that rather than creating the
animation in an imperative way, where we have a state that we mutate to show
something different on every frame, instead we start with an initial, immutable
state, and define an update function that will produce the new state to be
drawn. This is very much in the MVC vein.
Quil also allows you to generate
simple static grahpics, in which case fun-mode
becomes obsolete, since we
aren’t redrawing anything.
The functions that the template filled out for us are:
setup
: This is run at the start of the rendering, whether it’s an
animation or a static image. Typically this is where you’d set some
initial parameters like background color, frame rate, and, if we’re using
fun-mode
, it is expected to return the initial state of the animation.update state
: Takes the previous state as an argument and returns an
updated state. Since we’ll be restricting ourselves to static graphics in
this article, we won’t be using this one.draw-state
: This one takes the state and actually uses the data to generate
a graphic using Quil’s primitives: points, lines, fills, text, etc…defsketch
: Defines a sketch into which we pass all our defined functions,
along with some metadata like title and canvas size. Running the defsketch
function is what will actually render our image.We’ll be generating an image of the Sierpinski fractal. This might not strike you as particularly exciting, but the way in which we’ll obtain it, I think, is delightful. We’ll be picking one algorithm out of a vast family of algorithms that generate fractals called the chaos game. The algorithm goes as follows:
That sounds simple enough, and it’s not at all clear on first glance why this would ever yield the Sierpinski triangle.^{2} That’s what makes it all the more delightful. Such an intricate and unexpected result from the simplest of procedures: keep halving the distance between to points.
What I like, in particular, about this is how naturally it transfers to
Clojure (or, more general, functional) primitives. Essentially, what we’re
after is a (lazy) infinite sequence that we obtain by iterating the same
process over and over again. Guess what, there’s a function called iterate
in the Clojure standard library that does exactly that. No for loops, no
mutation, so simple. Let’s implement this chaos game!
Starting off, feel free to remove everything that’s already in the core.clj
file, except for the first couple of lines, where we declare the namespace
and import Quil. We’ll be replacing the rest with our own code.
The first thing we’ll need, is define a function that, given two points, returns the point that is midway:
(defn middle [[x1 y1] [x2 y2]]
[(/ (+ x1 x2) 2) (/ (+ y1 y2) 2)])
Next, we need to define the three vertices we’ll be using. I’ll define them a
vector of three points, each point being its own two-component vector. For
good measure, I’ll add a scaling factor s
so we can tweak the size of our final
result.
(def vertices
(let [s 250]
[[0 (- s)]
[(* s (q/cos (/ Math/PI 6))) (* s (q/sin (/ Math/PI 6)))]
[(* s (q/cos (/ (* 5 Math/PI) 6))) (* s (q/sin (/ (* 5 Math/PI) 6)))]]))
That’s most of the heavy lifting already taken care of! Notice how we’re using
the trigonometric functions that Quil provides (q/sin
and q/cos
),
as well as the definition of PI from the Java Math library.
Let’s create our infinite sequence of points:
(def sierpinski
(let [seed [0 0]
next #(middle % (rand-nth vertices))]
(iterate next seed)))
That should do it! Take a moment to appreciate how this is a word-for-word
translation of our algorithm. Start with a seed point (I picked [0 0]
, and
the next point is obtained by taking the middle of the point and a random
vertex. Iterate that procedure to get the list of points.
Easy as that, oh how I love declarative languages.
Let’s quickly write up our Quil-specific functions
(setup
, draw
and defsketch
):
(defn setup []
(q/color-mode :hsb 360 100 100)
(q/stroke 217 65 71)
(q/background 60 5 100))
(defn draw []
(q/with-translation [(/ (q/width) 2) (/ (q/height) 2)]
(doseq [[x y] (take 50000 sierpinski)]
(q/point x y))))
(q/defsketch sierpinski
:title "Sierpinski"
:setup setup
:draw draw
:size [600 600]
:features [:keep-on-top])
Notice how, in the draw
function, I added a translation. The origin of a
Quil canvas is typically the upper-left corner, so in order to center the
sketch, we add a tranlation of half the width and height. doseq
is a handy
function for looping over a sequence and performing some side effects based
on each element (in this case, drawing the point to the canvas).
I arbitrarily picked 50000 points to get a close approximation to the actual
Sierpinski fractal.
The procedure we used to generate the Sierpinski triangle is actually one instance of a more general procedure called an iterated function system (IFS). Simply put, they consist of sequences $x_i$ that are obtained by repeatedly applying a function to the previous value $x_{i-1}$: $x_i = f (x_{i-1}).$
Another famous example is the so-called de Jong IFS. In this case, the generating function is slightly more complex than in the Sierpinski case: For any point $p_i = (x_i,y_i)$, we obtain a new point through the transformation
where $-\pi \leq \alpha, \beta, \gamma, \delta \lneq \pi$ For any choice of these parameters, we’ll get a completely different pattern. Again, Clojure lends itself so well to this kind of procedure that implementing it is trivial:
(def a 1.4)
(def b -2.3)
(def g 2.4)
(def d -2.1)
(defn dj-gen [[x y]]
[(- (q/sin (* a y)) (q/cos (* b x)))
(- (q/sin (* g x)) (q/cos (* d y)))])
(def dj-seq
(let [x0 (rand)
y0 (rand)]
(iterate dj-gen [x0 y0])))
We do the exact same thing: define some seed point, in this case a random point within the unit square $[0,1) \times [0, 1)$, and iteratively apply our de Jong transformation. I’ve picked a set of parameters I thought gave a nice picture, feel free to play around with them. Try it out, I bet you’ll be suprised of what cool results you can get out of such a simple procedure.
The world is your oyster! With these first steps, there’s already an infinity of things you could tinker with. Play around with the parameters in the above examples. What happens if you change the Sierpinski algorithm? What if you don’t pick the midway point, but some other fraction? What if, instead of choosing one vertex at random, you pick two and take the average of three points instead of two? Play around with the de Jong parameters, see where you end up. Or maybe implement some other simple IFS systems. Or, see how deep the rabbit hole goes and start creating your own pretty pictures. It’s not hard to get aesthetically pleasing results!
For getting used to the standard library, I can’t recommend 4clojure highly enough. After every exercise, make sure to compare with other people to learn about idioms and functions you might not have known about. ↩
though, I suppose, one shouldn’t be surprised that, from our starting condition, we’re ending up with something that has a triangular symmetry. ↩