Quil is a thin Clojure wrapper over the Processing framework. I love using Clojure for my artwork, because Lisps give you the power to do some pretty wild stuff. It's also much less tedious to write than Java.
Setting up your environment for working with Quil can be a little tricky. Over the past few months, I've slowly built my preferred environment and workflow. If you're looking to get started with Quil, this should help you out.
I should note that I only work with static images, not animations, so my setup is best suited for that. With that disclaimer in mind, let's dig in.
THE BASIC SETUP
I use leiningen to manage my projects and provide a REPL. This is a pretty standard component for Clojure projects. Follow the link for instructions on how to install lein.
PROJECT.CLJ
The main file for any leiningen-managed project is project.clj:
(defproject sketch "1.0" :description "My example sketch." :dependencies [[org.clojure/clojure "1.6.0"] [org.clojure/clojure-contrib "1.2.0"] [quil "2.2.4" :exclusions [org.clojure/clojure]] [org.apache.commons/commons-math3 "3.3"] [incanter "1.5.5"]] :jvm-opts ["-Xms1100m" "-Xmx1100M" "-server"] :source-paths ["src/clj"] :java-source-paths ["src/java"] :aot [sketch.dynamic])
This is my default setup. The Apache commons-math dependency is for some useful probability distributions, most notably the Pareto distribution. I also use Incanter to write infix arithmetic sometimes, as prefix notation can get a little harder to write and parse, so I'm more prone to make mistakes.
I typically only write Clojure, but if I really need to optimize something that's well suited for Java, I'll use src/java for that.
The :aot directive enables ahead-of-time compilation. This lets me catch compilation errors before firing up the REPL (which is slow to start).
CORE.CLJ
The next file to set up is src/clj/sketch/core.clj This file is pretty slim. It only sets up the environment:
(ns sketch.core (:require [quil.core :as q]) (:require [sketch.dynamic :as dynamic]) (:gen-class)) (q/defsketch example :title "Sketch" :setup dynamic/setup :draw dynamic/draw :size [900 900]) (defn refresh [] (use :reload 'sketch.dynamic) (.loop example))
I set up my environment in this particular way so that I can generate a new image after changing the source code by running a single function in leiningen's REPL: (refresh). This allows me to avoid slow startup times (Clojure, Java, and Processing together all take a while) so that I can iterate quickly on my programs.
When I'm still refining my image, I use a resolution of 900x900, because it's quicker to generate. For the final images, I'll dramatically increase this.
DYNAMIC.CLJ
The src/clj/sketch/dynamic.clj file is where the drawing actually happens.
Here's what my namespace and imports typically look like:
(ns sketch.dynamic (:require [quil.core :refer :all]) (:use [incanter.core :only [$=]]) (:use [clojure.math.combinatorics :only [combinations cartesian-product]]) (:use [clojure.pprint]) (:use [clojure.set :only [union]]) (:use [clojure.contrib.map-utils :only [deep-merge-with]]) (:import [org.apache.commons.math3.distribution ParetoDistribution]) (:import [processing.core PShape PGraphics]))
I've got some utility functions, probability distributions, Processing classes, the $= macro from Incanter (which lets me do infix arithmetic) and, of course, all of the Quil functions.
(defn draw [] (no-loop)
The draw function is the main function. Since I'm working with static images, I use (no-loop) to ensure that it's only called once.
Instead of working with RGB colors, I find it more natural to think in terms of HSB:
(color-mode :hsb 360 100 100 1.0)
Here I'm setting the range of values for the hue to [0, 360], the range of saturation to [0, 100], the range of brightness to [0, 100], and the range of alpha to [0, 1.0]. Those ranges just happen to match the ranges for a color wheel/picker that I like to use.
Now that everything is set up, I can actually start drawing:
(background 220 49 66) (rect 100 100 400 400)
I usually automatically save every image:
(save "sketch.tif")
I do this so that I can keep anything that turns out well. These are low-resolution images, so I couldn't print them, but they're fine for reviewing or posting online. I save in the TIFF format partially because that's what my printshop prefers, and partially because I have problems with transparency when I generate PNGs with Processing.
WORKFLOW
I like to be able to make code changes and quickly test them out by generating a new image. So far, the best workflow for that involves the REPL. First I compile, then start the REPL:
~/mysketch $ lein compile Compiling 1 source files to /home/thobbs/mysketch/target/classes Compiling sketch.dynamic ~/mysketch $ lein repl nREPL server started on port 50163 on host 127.0.0.1 - nrepl://127.0.0.1:50163 REPL-y 0.3.2, nREPL 0.2.3 Clojure 1.6.0 OpenJDK 64-Bit Server VM 1.7.0_65-b32 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e user=>
Besides being able to test out arbitrary code, I can start the image generator like so:
user=> (use 'sketch.core) nil user=>
Assuming there are no errors in the program, you should see the Processing display window load and show something. When you've made changes to the code and want to regenerate the image, you can do the following:
user=> (refresh) nil user=>
I usually repeat this process until I'm satisfied with the output and want to generate large, printable images.
SCALING IMAGES
I generally print at 300ppi (pixels per inch), which means if I want to be able to print a 36" x 36" print, I need an image that's 10800 x 10800 pixels. Although I could potentially work with vectors instead of creating a huge pixel-based image, I prefer the huge pixel-based image for now. To avoid having to change a lot of values when I scale up the image, I prefer to work with percentages of the height and width instead of absolute pixel numbers or sizes.
To make this easy, I define a couple of functions:
(defn h ([] (h 1.0)) ([value] (* (height) value))) (defn w ([] (w 1.0)) ([value] (* (width) value)))
For example, if I want to draw a rectangle that starts 1/10th of the way down the image and 1/10th of the way across the image, spanning half the length and height of the image, I can do:
For example, if I want to draw a rectangle that starts 1/10th of the way down the image and 1/10th of the way across the image, spanning half the length and height of the image, I can do:
(rect (w 0.1) (h 0.1) (w 0.5) (h 0.5))
I got used to working this way pretty quickly, and I actually find it nicer than working with pixels. It's sort of a hybrid between vectors and pixels.
GENERATING LARGE PRINTABLE IMAGES
For my own artwork, I like to generate dozens or hundreds of images and then pick the best ones. Even if you only want to generate one large image, this setup should work well.
RUNCORE.CLJ
The first thing we'll do is swap out core.clj for runcore.clj:
(ns sketch.runcore (:require [quil.core :as q]) (:require [sketch.dynamic :as dynamic]) (:gen-class)) (defn -main [& args] (q/sketch :title "Big Image" :setup dynamic/setup :draw dynamic/draw :size [10800 10800] :features [:exit-on-close]))
This is similar to core.clj, but is designed to run standalone. The (-main) function is all that will be executed.
PROJECT.CLJ ADJUSTMENTS
Next, adjust project.clj to increase the size and add the main function:
(defproject sketch "1.0" :description "Big Image" :dependencies [[org.clojure/clojure "1.6.0"] [org.clojure/clojure-contrib "1.2.0"] [org.apache.commons/commons-math3 "3.3"] [quil "2.2.4" :exclusions [org.clojure/clojure]] [incanter "1.5.5"]] :jvm-opts ["-Xms5000m" "-Xmx5000M" "-server"] :source-paths ["src/clj"] :java-source-paths ["src/java"] :aot [sketch.dynamic sketch.runcore] :main sketch.runcore)
I've increased the heap to 5000m (5 GB). You may need to adjust this up or down depending on the size of your image and how much RAM you have available. Unfortunately, Processing likes to do things like load the entire pixel array onto the heap before saving the image, which means you need to have a pretty large heap for large images.
I've also added sketch.runcore to the :aot directive and set sketch.runcore as the main entry point. This will allow you to compile and run a single jar later.
GENERATING LOTS OF IMAGES
Optionally, if you're like me and you want to generate a lot of images, I suggest modifying dynamic.clj to generate and save images in a loop:
(defn draw [] (no-loop) (color-mode :hsb 360 100 100 1.0) (doseq [img-num (range 10)] (background 220 49 66) ; do drawing here ... (save (str "sketch-" img-num ".tif")) (println "Done with image" img-num)))
Since the images are quite large (100+ MB), I compress and then scale them down for easy viewing. I use imagemagick for this.
On the commandline, I would do something like this:
convert -compress LZW sketch-0.tif sketch-0.tif convert -scale 1000x1000 sketch-0.tif sketch-0-1000.tif
To automate this, I just use clojure's sh function:
(let [filename (str "sketch-" img-num ".tif") thumbnail (str "sketch-" img-num "-1000.tif")] (save filename) (sh "convert" "-LZW" filename filename) (sh "convert" "-scale" "1000x1000" filename thumbnail) (println "Done with image" img-num))
COMPILING A JAR
To create an executable standalone jar, use lein uberjar:
~/mysketch $ lein uberjar Created /home/thobbs/mysketch/target/sketch-1.0-SNAPSHOT.jar Created /home/thobbs/mysketch/target/sketch-1.0-SNAPSHOT-standalone.jar
DEALING WITH A HEADLESS SERVER
Since I'm executing the program on a headless server (to avoid sending my laptop into a RAM-starved death-spiral), I need to set up a fake framebuffer. (If you're not running headless, you can skip this part). I found a guide to using Processing with xvfb that was quite helpful. The instructions there can be somewhat simplified for the workflow suggested here.
To begin, install xvfb (X Virtual FrameBuffer) like so:
apt-get install xvfb
I then start xvfb:
Xvfb :1 -screen 0 1152x900x24+32 -fbdir /tmp &
This starts the process and backgrounds it. The "1152x900x24+32" argument sets the resolution and color depth. In my experience, the resolution (the first two numbers) doesn't matter, but the color depth does.
With the jar compiled and xvfb running, I can now execute the program:
~/mysketch $ DISPLAY=localhost:1.0 java -jar target/sketch-1.0-standalone.jar
As it runs, you should see any print statements you've added. For long-running jobs, I usually use nohup and backgrounding to avoid killing the process if I lose my connection to the server:
~/mysketch $ DISPLAY=localhost:1.0 nohup java -jar target/sketch-1.0-standalone.jar &
WRAPPING UP
That's pretty much all there is to my current workflow. If you have any tips or suggestions, please feel free to contact me and let me know. I'll update the post and give you credit.
Cheers!