seyshee.dev

Sunday plotting

Some weekends I write small programs whose only output is a picture. The results live in the gallery. A few people asked how they are made, so here's the short version.

The toolbox

Everything is Python and Pillow. No numpy, no Processing, no GPU. Rendering one 3000×2000 image takes between five seconds and a minute, which is a perfectly good speed for something you look at for a week.

Pillow's ImageDraw does not antialias lines, so I draw at twice the size and downscale with LANCZOS. That's the single biggest quality improvement per line of code.

Noise

Most of the sketches start from 2D gradient noise. The whole implementation is about 25 lines: a shuffled permutation table, a table of random unit vectors, and a smoothstep between the four corners of a cell. Layer a few octaves (fractal Brownian motion) and you get something that looks like terrain:

def fbm(self, x, y, octaves=4):
    s, amp, f = 0.0, 0.5, 1.0
    for _ in range(octaves):
        s += amp * self(x * f, y * f)
        amp *= 0.5; f *= 2.0
    return s

From there:

  • Flow field — treat noise as an angle, drop thousands of particles, let each walk along the angle for a while and draw its path.
  • Contours — sample noise on a grid and run marching squares at a few dozen levels. Every fifth line is brighter, like on a real map.
  • Two peaks — the same noise as a horizon line, plus two gaussian bumps. If you've been to Yerevan you know which mountain it is.

I've cleaned up the noise and drawing helpers into noisepad in case anyone wants to play.

Why not numpy

I started with numpy and it was faster. But half the fun is reading the code a year later and still understanding it, and loops over pixels are easier to read than broadcasting tricks. Speed has never been the bottleneck — deciding which of the twenty variations to keep is.