Into the Land of Quil

A Great and Valiant Journey of Derring-do

Good day, traveler. Today you embark upon a journey into Quil. May your vision be keen, for there are sights to behold.

If you're new to the realms of Clojure, click here to start from scratch. →

This is a tutorial for Quil, a visual programming system that combines the powers of ClojureScript and Processing.js.

A Quil program is called a sketch. A sketch can be a simple drawing, but it can also be a rich animation or an interactive visualization.

You'll find many sketches in this tutorial. All of them are "live" — change the code and you should immediately see the sketch change. Hover over a Quil function name to see what it does.

A Taste of Quil

These first few sketches will give you a taste of things to come. They are presented without much explanation. We'll dive into the details later. Play around with them, there's nothing you can break. Should you happen to make a mess, simply hit the red "revert" button, and you'll get the original code back.

The Pine Forest

The road to Quil starts in the Pine Forest. Try changing some numbers and see what happens!

x
 
;; This is the draw function which Quil will run
(defn draw-pine-forest []
  ;; First we set the stage: a background color, and no borders around shapes
  (background 20 200 151)
  (no-stroke)
  ;; Set a fill color for shapes. The numbers correspond with
  ;; red - green - blue, and go up to 255
  (fill 34 95 215)
  ;; Fill the width and height of the canvas with pine-forest
  (doseq [x (range 0 (width) 50)
          y (range 0 (height) 60)]
    (triangle (+ x 25) y
              x (+ y 50)
              (+ x 50) (+ y 50))
    (rect (+ x 16) (+ y 50) 16 10)))
(sketch
  :host "pine-forest"
  :size [300 300]
  :draw #'draw-pine-forest)
revert code

That's already a lovely pattern we've got going. Knit it into a turtleneck and aunt Juliet will envy you forever. But there's more. How about we get things moving?

The Carousel

Time to make your head spin! This is an example of an animation. Think of it like an old fashioned film projector, spinning through a sequence of images. Quil calls your draw function many times in rapid succession to draw the individual images, called frames.

Quil keeps a count of how many frames have passed. This sketch uses that frame-count to determine the position of the circle.

xxxxxxxxxx
 
(defn draw-carousel []
  (background 255)
  (no-stroke)
  (fill 252 90 44)
  (let [radians (/ (frame-count) 20)
        x (+ 150 (* 100 (cos radians)))
        y (+ 150 (* 100 (sin radians)))
        width 30
        height 30]
      (ellipse x y, width height)))
(sketch
  :host "carousel"
  :size [300 300]
  :draw #'draw-carousel)
revert code

Paint Blotches

Quil also lets you make interactive sketches. Move your mouse over the canvas to smear it with thick blotches of paint that run down the screen.

This is an example of a sketch that has state. This means that how it looks depends on its history, in this case, the past positions of the mouse.

xxxxxxxxxx
 
(def colors [[249 202 216]
             [232 181 188]
             [240 155 150]
             [209 188 219]
             [204 199 241]
             [198 221 245]
             [202 216 247]])
(defn move-dots-down [state]
  (->> state
    (map #(update-in % [:pos 1] inc))
    (filter #(< (get-in % [:pos 1]) (+ 25 (height))))))
(defn paint-blotch-update [state]
  (move-dots-down
    (let [x (mouse-x) y (mouse-y)]
      (if (and (> x 0) (> y 0) (< x (width)) (< y (height)))
        (conj state {:pos [x y] :color (rand-nth colors)})
        state))))
(defn paint-blotch-draw [state]
  (stroke-weight 25)
  (doseq [{[x1 y1] :pos color :color} state]
    (apply stroke color)
    (point x1 y1)))
(defn paint-blotch-setup []
  (background 255)
  (fill 0)
  (text "paint me!" 120 145)
  ;; The initial state is just an empty list
  (list))
(sketch
  :host "paint-blotch"
  :size [300 300]
  :setup #'paint-blotch-setup
  :update #'paint-blotch-update
  :draw #'paint-blotch-draw
  :middleware [quil.middleware/fun-mode])
revert code

Creating a sketch

To get Quil to draw something on the screen, you need a :draw function. You pass this function on to Quil when you call (sketch ...). Quil accepts other functions as well, like a :setup function, which is called once when your sketch starts to run.

The :host is the HTML id of the canvas element, which is where Quil will draw your sketch. In this tutorial, all the canvases are already set up for you, so no need to worry about that.

The Quil API

Inside the :draw function you have access to a long list of functions provided by Quil. Keep the Quil API reference close to you at all times. Hang it above your bed, keep it under your pillow. It's a magical toolbox providing endless opportunities for your creations.

Drawing shapes

When you draw a shape, it stays on the canvas until you draw something on top of it. You can see this well in the last example, where the new "paint" is drawn on top of the old..

With (background ...) you can get it a clean slate, since it will refill the whole canvas with a solid color. You can use it in your draw function to clear the canvas before drawing the new frame.

One of the easiest things you can draw is a rectangle. Quil's rect function takes four parameters, x, y, width and height. The first two arguments represent the location of the top left corner of rectangle on the canvas. Remember that in Quil, as in computer graphics in general, the origin (0, 0) of the coordinate system represents the top left corner of the drawing surface. In this example, we're drawing a centered equilateral rectangle or square.

xxxxxxxxxx
 
(defn draw-rect []
  ;; Draw the rectangle
  (rect 50 50 200 200))
(sketch
  :host "plain-rect"
  :size [300 300]
  :draw #'draw-rect)
revert code

By default Quil draws shapes with a light gray fill color, and a thin black border, on a dark gray background. Unless normcore becomes even more... norm, this color scheme won't win many prizes.

To spice things up let's add some color! We already talked about background, which is really a lot like a draw function in its own right, since it refills the whole canvas. Shapes like rectangles or triangles on the other hand depend on a fill color, and a stroke. The stroke has a color and a stroke-weight, the width of the stroke in pixels.

By default all colors are specified in the RGB color space, meaning red - green - blue, with values between 0 and 255. Try and see how the colors change as you modify the three numbers!

xxxxxxxxxx
 
(defn draw-rect []
  ;; Fill the background with a solid color
  (background 20 200 151)
  ;; Set up drawing properties for the shapes that follow
  (stroke 214 240 248)
  (stroke-weight 10)
  (fill 34 95 215)
  ;; Draw the rectangle
  (rect 50 50 200 200))
(sketch
  :host "rect"
  :size [300 300]
  :draw #'draw-rect)
revert code

Drawing a circle isn't any harder. While Quil does not come with a special function for drawing circles, it supplies a function for the general case of drawing ovals (or ellipses). That will work well — after all, a circle is just an ellipse that is as high as it is wide.

The ellipse function takes the same arguments as rect: x, y, width and height. However, whereas in the case of rect the point (x,y) reprents the top-left corner of the figure, in ellipse it represents its center. This decision of Quil's authors makes sense: an ellipse doesn't have corners.

xxxxxxxxxx
 
(defn draw-circle []
  (background 20 200 151)
  (no-stroke) ;; use this if you don't want a border around your shapes
  (fill 34 95 215)
  (ellipse 150 150 200 200))
(sketch
  :host "circle"
  :size [300 300]
  :draw #'draw-circle)
revert code

Animation and State

Don't just draw something, make it move! — Winston Churchill

Slider

There are a few different ways you can create animations in Quil. You already saw the Carrousel example above, which used the current (frame-count) and based the rendering of each frame on that.

You can change the frame rate, the number of frames Quil renders per second, with (frame-rate n). This will change the speed of animations based on frame-count.

Another approach is the use the current time in milliseconds, this way we have a way to measure passing time independent of the the current frame rate. Quil provides the handy (millis) function for that.

In this next example we use (millis) to calculate a horizontal position. With Clojure's mod function (modulo, in other words the remainder after division), we let the animation wrap around again.

xxxxxxxxxx
 
(def shape-size 20)
(defn draw-state []
  (background 255)
  (fill 100 50 250)
  (let [pos (* 0.2 (millis)) ;; use a factor to slow things down
        x (mod pos (+ (width) shape-size))] ;; let the shapes go off the screen, then wrap
     (doseq [y (range 0 (height) shape-size)]
       (triangle x y
                 x (+ y shape-size)
                 (+ x shape-size) (+ y shape-size)))))
(sketch
  :host "slider"
  :size [300 300]
  :draw #'draw-state)
revert code

Hyper

This next sketch is an adaptation of a sketch by by Erik Svedäng.

This is an example of a sketch that uses state. The initial state is returned by setup And update-state continually updates that state, in this case growing the radius, or changing the color. Draw in turn can then use that state.

xxxxxxxxxx
 
(defn new-state []
  {:radius 0.0
   :color [(random 200 255) (random 0 150) (random 100 255)]})
(defn setup []
  (frame-rate 100)
  (background 255)
  (rect-mode :center)
  (new-state))
(defn tick [state]
  (update-in state [:radius] + 5.0))
(defn update-state [state]
  (if (< (:radius state) 300)
    (tick state)
    (new-state)))
(defn draw-state [state]
  (apply stroke (:color state))
  (let [hw (* 0.5 (width))
        hh (* 0.5 (height))]
    (dotimes [_ (quot (width) 10)]
      (let [rand-ang (random 0 q/TWO-PI)
            r (:radius state)]
        (line hh hw
              (+ hh (* (sin rand-ang) r))
              (+ hw (* (cos rand-ang) r)))))))
(sketch
  :host "hyper"
  :size [300 300]
  :setup #'setup
  :update #'update-state
  :draw #'draw-state
  :middleware [m/fun-mode])
revert code

Advanced Examples

These examples are taken from the Quil site. Can you find out how they work?

Tailspin by Erik Svedäng

Tailspin by Erik Svedäng on Quil.info

xxxxxxxxxx
 
(defn setup []
  (frame-rate 30)
  (let [max-r (/ (width) 2)
              n (int (map-range (width)
                                200 500
                                20 50))]
    {:dots (into [] (for [r (map #(* max-r %)
                                 (range 0 1 (/ n)))]
                         [r 0]))}))
(def speed 0.0003)
(defn move [dot]
  (let [[r a] dot]
    [r (+ a (* r speed))]))
(defn update-state [state]
  (update-in state [:dots] #(map move %)))
(defn dot->coord [[r a]]
  [(+ (/ (width) 2) (* r (cos a)))
   (+ (/ (height) 2) (* r (sin a)))])
(defn draw-state [state]
  (background 255)
  (fill 0)
  (let [dots (:dots state)]
    (loop [curr (first dots)
                tail (rest dots)
                prev nil]
          (let [[x y] (dot->coord curr)]
            (ellipse x y 5 5)
            (when prev
              (let [[x2 y2] (dot->coord prev)]
                (line x y x2 y2))))
          (when (seq tail)
            (recur (first tail)
                   (rest tail)
                   curr)))))
(sketch
 :host "tailspin"
 :size [300 300]
 :setup #'setup
 :update #'update-state
 :draw #'draw-state
 :middleware [m/fun-mode])
revert code

Dry Paint by Erik Svedäng

Dry Paint by Erik Svedäng on Quil.info

xxxxxxxxxx
 
(defn pulse [low high rate millis]
  (let [diff (- high low)
        half (/ diff 2)
        mid (+ low half)
        s (/ millis 1000.0)
        x (sin (* s (/ 1.0 rate)))]
    (+ mid (* x half))))
(def letter-Q [[3 2] [2 3] [2 4] [2 5] [2 6] [3 7]
               [4 8] [5 8]
               [5 6] [6 7] [7 8]
               [7 6] [7 5] [7 4] [7 3] [6 2]
               [5 1] [4 1]])
(def step 20)
(defn create-color [millis]
  [(pulse 0 255 3.0 millis)
   (pulse 0 255 5.0 millis)
   (pulse 0 255 7.0 millis)])
(defn setup []
  (frame-rate 100)
  (background 255)
  (zipmap letter-Q
          (map create-color (iterate #(+ % 200) 10000))
          ))
(defn get-lamp-index []
  (if (and (< 0 (mouse-x) (width))
           (< 0 (mouse-y) (height)))
      [(quot (mouse-x) step)
       (quot (mouse-y) step)]
    nil))
(defn update-state [state]
  (if-let [index (get-lamp-index)]
      (assoc state index (create-color (millis)))
    state))
(defn draw-state [state]
  (no-stroke)
  (background 255)
  (no-stroke)
  (let [w (width)
          h (height)
          hw (/ w 2)
          hh (/ h 2)]
    (doseq [[ind col] state]
           (let [x (* step (first ind))
                   y (* step (second ind))
                   col-mod (-> (+ x y (millis))
                               (* 0.01)
                               (sin)
                               (* 5))]
             (apply fill (map + col (repeat 3 col-mod)))
             (rect x y step step)))))
(sketch
 :host "dry-paint"
 :size [300 300]
 :setup #'setup
 :update #'update-state
 :draw #'draw-state
 :middleware [m/fun-mode])
revert code

About Us

This interactive tutorial was created in a weekend by BERLIN!! for ClojureCup 2015.

We are Arne Brasseur, Chelsey Mitchell, Jelle Akkerman, and Paulus Esterhazy.

All code is available under the Eclipe Public License and can be found on Github. Tutorial text and code samples are available under Creative Commons-Attribution-Share Alike 4.0. Quil examples taken from the Quil site are © their respective authors, and are EPL like the Quil site.