Skip to content

painterly

A browser program that repaints a photograph with visible brush strokes: coarse brushes lay the ground, finer ones return only where the picture still disagrees with the photo, and every stroke runs along the image instead of across it.

2026TypeScriptCanvasGenerative ArtAnimationLibraryWeb
To a finished painting
45s
Brush strokes, one photo
108k
Runtime dependencies
0

In early 2020 I saw a post about someone automating the act of painting a picture in Microsoft Paint. I had just found Processing, so I wrote a desktop program in Python that dropped random brush strokes on a photograph until it looked like an expressionist painting. It took minutes to finish, and the best part was watching each stroke land. painterly is that idea rebuilt six years later, in a browser tab, and you can watch it paint your own photo.

The engine is a port of Aaron Hertzmann's Painterly Rendering with Curved Brush Strokes of Multiple Sizes (SIGGRAPH 1998). Five brushes, each half the width of the last. Every brush blurs the photo to its own scale, measures where the canvas still disagrees with it, and puts a stroke down only there, so the likeness converges instead of filling in at random. Each step of a stroke also turns perpendicular to the image gradient. That single rule is the difference between brushwork and a smear filter: strokes follow fur, edges, and the line of a ridge because the picture tells them where to go.

Everything is seeded, so a photo and a seed always produce the same painting. That one decision is what makes the rest possible. Strokes go down with alpha and can't be lifted, so there is no playing backwards, and the transport ended up a DVD player's rather than a scrubber: back a brush, play, on a brush. The player keeps a copy of the canvas at each handover and never spends more than a fraction of a frame painting, so a jump costs a few frames of catching up instead of freezing the page. Planning runs on a worker and hands its result back as packed typed arrays, 15 MB transferred instead of 110 MB of cloned objects.

My favorite part was an accident. Early on I watched the three-second frame where only the biggest brush had run, and liked it better than the finished painting: a few hundred ribbons of color, unmistakably the photograph and nothing like it. It was meant as a stage on the way to somewhere else, and it shipped as a style of its own.

The whole thing is 7.4 KB of TypeScript with nothing to install. The source is on GitHub, where the 2020 Processing sketches are still sitting in processing/.

More side projects

More side projects
Contact

Let’s build something

Have a project, a role, or a problem worth solving? I’d love to hear about it.

Get in touch

or email davemaynard24@gmail.com