I've been having some fun recently making visual art with Clojure and Quil, especially animated GIFs. In the course of doing this, I've come into contact with many GIFs made by other artist-programmers, some of which have included source code. I'm republishing one of them, called rainbow kiss, with a refactoring of the code from the Processing language to Clojure and Quil as an example of how one might approach this sort of thing.
Before I get started, I want to make it clear that my purpose here is not to criticize Mr Virdee. He's made a lovely GIF and published the source code as a help to other artists, not as an example of production code. In fact, one expects code exactly like this from creative coding endeavors because of how they're made: a sketch is incrementally modified until a good aesthetic effect is reached, after which victory is declared and the code is abandoned.
At first glance, this creative coding process probably sounds far removed from industry engineering practices, but quite often developers follow an approach where they modify the code until all tests pass and then back slowly away from the keyboard before anything breaks, which ends up with surprisingly similar results. I attribute this not to a failure of virtue on the part of programmers, but to the tremendous time pressure they face in fast-paced environments.
Also, once all the tests pass, no one wants to change or rethink an unfortunate piece of code because it might cause a regression that leads down a rabbit hole of doom. This line of thinking always reminds me of Kafka's Leopards in the Temple:
Leopards break into the temple and drink to the dregs what is in the sacrificial pitchers; this is repeated over and over again; finally it can be calculated in advance, and it becomes a part of the ceremony.
Unfortunately, accommodating the leopards leads to a situation in which developers never take the time to notice that what they're doing is much simpler than the code they've written to do it.
I've removed some code that allowed saving frames and rearranged the order of the original source, but otherwise it was found like this:
int t=0;
ArrayList lights;
class LightBar {
float x;
float y;
float s;
LightBar(float _x, float _y) {
x=_x;
y=_y;
s=0;
}
void update() {
s++;
s%=360;
y++;
y%=height/2;
x= height/2 - y;
}
void display() {
noStroke();
noFill();
strokeWeight(2);
stroke(height/2-y, 100, 100, 40);
pushMatrix();
float xside = (width-2*x);
float yside = height-2*y;
translate(x+xside/2, y+yside/2);
ellipse(0, 0, xside, yside);
popMatrix();
}
}
void setup() {
size(600, 600);
colorMode(HSB, 360, 100, 100, 100);
lights = new ArrayList();
int rad =150;
for (int i=0; i<height/2; i+=20) {
lights.add(new LightBar(height/2 - i, i));
}
}
void draw() {
noStroke();
fill(240, 96, 10);
rect(0, 0, width, height);
for (int i=0; i<lights.size(); i++) {
LightBar r = (LightBar) lights.get(i);
r.display();
r.update();
}
t++;
}
The first things that one notices when trying to understand this code is that there are some superfluous variables:
- Line 1:
t
is initialized and later incremented, but never read. - Line 11: member
s
inLightBar
is initialized and incremented modulo 360, but never read. - Line 40:
rad
is initialized but never used.
These are vestigial organs left over from previous evolutionary stages in the development of the program. They don't break it, but they do make it harder to read and understand when someone else comes along (even if that someone else is a future version of the original author).
We'll remove these variables, then continue with a closer look at the
LightBar
class.
The member variables of LightBar
make it clear that it's a 2D
Cartesian point, and one must initialize both values when calling the
constructor:
class LightBar {
float x;
float y;
LightBar(float _x, float _y) {
x=_x;
y=_y;
}
However, when we look at the class's methods, we see that the update
method, which is called on every frame, is first monotonically
incrementing the y
value modulo height / 2
and then deriving x
from it:
void update() {
y++;
y%=height/2;
x= height/2 - y;
}
This immediately tells us two important things:
-
There's no reason to store
x
on update, as it can be derived directly fromy
the one time it will be used beforeupdate
is called again. -
Because Processing provides a variable called
frameCount
that's bound to the number of frames that have thus far been rendered, we don't need to store and manually incrementy
either, as we can add the frame count toy
moduloheight / 2
in the draw loop.
Moving on to the display
method, we see a bit of display code then
some calculations to determine the dimensions of the ellipse:
void display() {
noStroke();
noFill();
strokeWeight(2);
stroke(height/2-y, 100, 100, 40);
pushMatrix();
float xside = (width-2*x);
float yside = height-2*y;
translate(x+xside/2, y+yside/2);
ellipse(0, 0, xside, yside);
popMatrix();
}
These calculations repeatedly multiply and divide the same values by a
constant factor of 2. This sort of thing should cause one's algebraic
Spidey Sense™ to tingle. If we factor out the redundant operations, we
no longer need the xside
and yside
variables, we operate on
height
rather than height / 2
later multiplied by 2, and lines of
code and complexity melt away:
translate(width/2, height/2);
ellipse(0, 0, width - (height - y), height - y);
At this point it becomes clear that the display
method can be
rendered as a pure function of the frame count, and that it's small
enough that we might as well just call it from the draw
function. This means we can jettison the LightBar
class altogether
and render the whole sketch as:
void setup() {
size(600, 600);
colorMode(HSB, 360, 100, 100, 100);
}
void draw() {
noStroke();
fill(240, 96, 10);
rect(0, 0, width, height);
noStroke();
noFill();
strokeWeight(2);
for (int i=0; i<height; i+=40) {
int y = (i + frameCount) % height;
pushMatrix();
stroke((height/2)-(y/2), 100, 100, 40);
translate(width/2, height/2);
ellipse(0, 0, width - (height - y), height - y);
popMatrix();
}
}
This terse, clarified, leopard-free version is much easier to understand and will thus be much easier to convert to Clojure/Quil.
It would be easy enough to do a line by line rendering of this code to
Clojure, but we can do slightly better than that by taking advantage
of a few Clojure features to eliminate the explicit for
loop and the
named variable i
. We'll do this by generating the initial sequence
of intervals with the range
function, which will return a sequence
of numbers from 0
to height
in increments of 40
:
(range 0 (height) 40)
;; => (0 40 80 120 160 200 240 280 320 360 400 440 480 520 560)
;; (at height 600)
We will then map
a function over this sequence to add the frame
count and take the modulus, thus giving us a sequence of y
values:
(map #(mod (+ (frame-count) %) (height)) (range 0 (height) 40))
;; => (34 74 114 154 194 234 274 314 354 394 434 474 514 554 594)
;; (at frame count 1234)
Using doseq
we can then execute a block of code once for each value
in the sequence:
(doseq [y (map #(mod (+ (frame-count) %) (height)) (range 0 (height) 40))]
...do some things with y...)
Adding in the calculations, this gives us a draw
function like this:
(defn draw []
(no-stroke)
(fill 240 96 10)
(rect 0 0 (width) (height))
(no-fill)
(push-matrix)
(translate (/ (width) 2) (/ (height) 2))
(doseq [y (map #(mod (+ (frame-count) %) (height)) (range 0 (height) 40))]
(stroke (- (/ (height) 2) (/ y 2)) 100 100 40)
(ellipse 0 0 (- (width) (- (height) y)) (- (height) y)))
(pop-matrix))
One last thing: we can replace pushMatrix
, popMatrix
and
translate
with the with-translation
macro, which wraps a block of
code in a pair of push/pop matrix calls and sets the translation, thus
simultaneously saving a few lines of code and making it impossible to
forget to pop the matrix. (This is an old Lisp technique that's used
for all sorts of things — with-open-file
, and so on.)
(ns quil-sketches.rainbow-kiss
(:use quil.core))
(defn setup []
(smooth)
(stroke-weight 2)
(color-mode :hsb 360 100 100 100))
(defn draw []
(no-stroke)
(fill 240 96 10)
(rect 0 0 (width) (height))
(no-fill)
(with-translation [(/ (width) 2) (/ (height) 2)]
(doseq [y (map #(mod (+ (frame-count) %) (height)) (range 0 (height) 40))]
(stroke (- (/ (height) 2) (/ y 2)) 100 100 40)
(ellipse 0 0 (- (width) (- (height) y)) (- (height) y)))))
(defsketch rainbow-kiss
:title "rainbow-kiss"
:setup setup
:draw draw
:size [600 600])
And with that, we have this final brief, clear Quil sketch.