May 2021
When I started making generative art, I was excited to share the art I made. I took screenshots and manually cropped them to only the part of the desktop containing the art. As you might imagine, this process was completely foolproof, not to mention quick and easy.
No, wait. It was error-prone, boring and repetitive. But computers can do boring repetitive tasks for us! And whenever my computer starts to complain about having to play the same video over and over, I just unplug it.
Point being: I'm using Quil, a Clojure library based on Processing. Quil has a function save-frame:
We could call that function at the end of generating the art. That would work, but I render my art a few times with different random values, and manually pick some to keep. Like a child's drawings, some art is better thrown away. Luckily, Quil's sketch
function has a parameter that runs a passed-in function any time a key is pressed.
So we write a function that saves the current artwork as a picture when the s
key is pressed.
(fn [] (when (= \s (q/raw-key))
(q/save-frame "/path/to/save/to.png")))
And use it in a sketch:
(q/sketch
:size [width height]
:draw my-draw-function
:key-pressed (fn [] (when (= \s (q/raw-key))
(q/save-frame "/path/to/save/to.png"))))
Now, anytime the s
key is pressed, the current sketch is saved to the path /path/to/save/to.png
.
That's fine, but we can do better. Let's make a function to generate this function. This way, we can call it from different artworks without having to copy and paste the body around. This is a common pattern I have with helper methods – start by making them in one work, then move them to a common library. It's satisfying to build your own package of useful functions.
(defn key-press-fn-creator
[]
(fn []
(when (= \s (q/raw-key))
(q/save-frame "/path/to/save/to.png"))))
Now, rather than always saving to the same path, let's save to a better location.
Save pictures inside a base folder specified in a variable
base-output-folder
.Inside that folder, use a folder for the current year. This means that once we've been making art for a while, we'll have an idea when we created each specific file.
Take the name of the work as an argument to the function, and use that as the next folder.
Explicitly save the random seed at the beginning of my work. We can reuse the seed to regenerate the same artwork, and lets us save more than one screenshot from the same code. We can use the seed as the filename to differentiate different saved versions of the same work.
(def base-output-folder "/path/containing/art/screenshots")
(defn key-press-fn-creator
[work-name]
(fn []
(when (= \s (q/raw-key))
(let [file-path (format "%s/%s/%s/opus-%s.png"
base-output-folder
(q/year)
work-name
(q/state :seed))]
(q/save-frame file-path)))))
Because Quil doesn't expose the seed it uses, we generate a new one, store it in the state atom, and tell Quil to use it instead of the default seed.
;; in the setup function.
(swap! (q/state-atom)
assoc
:seed
(q/random (Math/pow 10 9)))
(q/random-seed (q/state :seed))
This function is used in the sketch
call:
(q/sketch
;;; other code elided
:key-pressed (key-press-fn-creator "bubbling-brook"))
Finally, pressing ESC to quit the sketch is too much work. Let's close the sketch when q
is pressed.
(defn key-press-fn-creator
[work-name]
(let [actions {\s (fn []
(let [file-path (format "%s/%s/%s/opus-%s.png"
base-output-folder
(q/year)
work-name
(q/state :seed))]
(q/save-frame file-path)))
\q (fn []
(q/exit))}]
(fn []
(when-let [action (actions (q/raw-key))]
(action)))))
There's more that could be done, but this post is long enough. Maybe I'll make a tool so I don't have to automatically crop blog posts.