Leopards in the Source Code

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.

Why this code?

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 parable 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.

The Code

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++;
}

Cleanliness

The first things that one notices when trying to understand this code is that there are some superfluous variables in the lines highlighted above:

  1. t is initialized and later incremented, but never read.
  2. member s in LightBar is initialized and incremented modulo 360, but never read.
  3. 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.

What is a LightBar?

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:

  1. There's no reason to store x on update, as it can be derived directly from y the one time it will be used before update is called again.
  2. 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 increment y either, as we can add the frame count to y modulo height / 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 thus do 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 put that code in the draw function. This, in turn, 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.

Clojure Version

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 here

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, like 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.

This entry is part of my journal, published October 25, 2013, in New York.