Archiving Generative Art
2024-04-05Recently I was given the opportunity to display some of my older works in a large, printed coffee-table book. The publishers were eager to see some of the drafts leading up to these pieces and even wanted to include prints of them as well. Unfortunately I had only rendered these at roughly 2,000px per side, which at the requested 400dpi would print at a measly 5 × 5 inches.
Despite keeping my source code in version control, I had not been creating a commit (saving a snapshot of all the code) for each image I rendered. On top of that, I wasn’t storing the parameters I used for each render anywhere (things like the seed
to my random number generator). This makes it virtually impossible to recreate these images over a year later.
Determined not to let this happen again, I needed to find a solution that let me record exactly how to reproduce each of my generated images. Ironically, I had already been doing this for my physical, plotted works by annotating the back of each artwork.
Storing Metadata
The root of the problem is how to record the necessary metadata alongside an output. One could imagine generating a separate JSON document alongside each image, containing data like this:
{
"seed": "0x01234567...",
"git_commit": "01234567...",
// other parameters used during generation...
}
This quickly becomes cumbersome when you realize you need some way to associate an image with its JSON metadata document, and your Downloads folder looks like this:
$ ls ~/Downloads/
image.png metadata.json
image (1).png metadata (1).json
image (2).png metadata (2).json
image (3).png metadata (3).json
...
An approach I’ve often seen other generative artists use is to store metadata in the filename of the output. This is already much easier to manage than the approach above, as you can easily move/copy files without worrying about forgetting to move/copy the corresponding JSON metadata.
$ ls ~/Downloads/
image_0x0123456_01234567.png
image_0x89abcde_01234567.png
image_0xffffffe_01234567.png
...
This approach has a few serious drawbacks though:
- Many systems have a 255 character limit to filenames. This is easier to hit than you might think, especially once you start encoding more parameters.
- If you ever rename a file, you lose the metadata.
- The set of characters you can use within the filename is limited, so you need to take care encoding and decoding whatever you store there.
This leaves us with the solution I’m currently using: to store the metadata directly within the file.
PNG tEXt Chunks
Virtually every media file format has some room for metadata. PNG files allow for arbitrary ASCII key/value data to be stored alongside any image, known as tEXt chunks. This is clearly what I needed, but at the time there was a lack of library support in Javascript. So, I wrote and published small library under the MIT license to help fellow generative artists. You can find the code here: github.com/lwander/png-text.
Using this library is as simple as this:
import {writeTextToBlob} from 'png-text';
// when saving your canvas object...
canvas.toBlob(blob => {
blob = await writeTextToBlob(blob, {commit, seed});
// save/modify newBlob here...
}, 'image/png');
I also keep this script in my $PATH
for quickly reading entries (note it depends on Pillow, which already supports reading/writing PNG tEXt chunks):
#!/usr/bin/env python3
from argparse import ArgumentParser
from PIL import Image
parser = ArgumentParser(prog="pngi", description="Read PNG tEXt chunks")
parser.add_argument("input")
args = parser.parse_args()
image = Image.open(args.input)
info = image.text
keys = list(info.keys())
keys.sort()
for k in keys:
print(f'{k}: {info[k]}')
Invoking it on a saved image looks like this:
$ pngi image.png
app: 5.0 (X11; Linux x86_64) Chrome/122.0.0.0
commit: 5c670a1201fee8c0e8db0d25128335f25f1faa00
created: 2024-03-25T15:41:32:713
creator: Lars Wander (https://larswander.com)
href: http://localhost:9494/ski/?seed=0xfb94032f7cbc0d8103b9cf7df89b353d
license: CC BY-NC-SA 4.0
params.seed: 0xfb94032f7cbc0d8103b9cf7df89b353d
params.viewport: 0,0,-1
What to Record
You can see the full list of parameters I stuff into each image above, but there are a few I’d like to call attention to:
- app: Recording the browser version helps insulate you from potential incompatibilities or breaking changes. If you’re lucky enough to have someone interested in your art a few decades from now, who knows if web browsers (assuming we are even still using them) will still support your code.
- commit: For this to be meaningful, you need to create a commit each time you save an output (assuming the code has changes). This needs to be automated.
- href: Assuming your work is browser-based, placing all parameters that fully specify an output at a given commit into the URL makes it much easier to reproduce an artwork (simply paste the URL into the browser).
I hope this helps you archive and reproduce your own generative artwork! If anyone wants to port this script to work with P5.js it would be much appreciated.