~/ me art writing

Archiving Generative Art

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

One of the lower-resolution WIP images
One of the images meant to be printed. However, the exact commit, seed, and other parameters needed to regenerate this image were never saved. This image was generated with a spectral raytracer I wrote in GLSL last year.

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:

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:

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.