Doop
Doop is an open-source multiplayer design canvas and design MCP for Claude Code, Codex, and any AI agent. Design side by side with agents — free in beta.
Watch a photo get painted: brush strokes of several sizes that follow the image and converge on it.
DesignTypeScript · JavaScriptGitHub @davemaynardOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Watch a photo get painted: multi-size brush strokes that follow the image and converge on it. Deterministic, zero dependencies, in a browser canvas.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
painterly (adjective): of a painting or its style, characterised by visible
brushwork and the rendering of form through colour and tone rather than by line.
This is a program that takes a photo and paints it that way, in your browser,
while you watch.
A big brush lays in the underpainting. Finer brushes come back only where the
picture still disagrees with the photo. Every stroke runs along the image
instead of across it, so brushwork follows fur, edges and the line of a roof.
It finishes in 45 seconds at any size — 20 for the underpainting — then stops.Read the full README ↗painterly (adjective): of a painting or its style, characterized by visible brushwork and the rendering of form through color and tone rather than by line. This is a program that takes a photo and paints it that way, in your browser, while you watch.
Live: davemaynard.github.io/painterly
A big brush lays in the underpainting. Finer brushes come back only where the picture still disagrees with the photo. Every stroke runs along the image instead of across it, so brushwork follows fur, edges and the line of a roof. It finishes in 45 seconds at any size — 20 for the underpainting — then stops.
Run npm run size to regenerate the first two.
| Library, gzipped | 7.7 KB |
| Demo page script and its worker, gzipped | 11.2 KB |
| Runtime dependencies | 0 (a Mersenne Twister is bundled, see NOTICE) |
| Planning a 1050 × 1400 photo | ~1.2 s on an M4 Mac mini, ~108,000 strokes |
| A plan crossing back from the worker | 15 MB packed into typed arrays, not 110 MB of objects |
| Longest frame CI will accept | 60 ms painting, 100 ms skipping to the end |
npm install github:davemaynard/painterly
import {brushes, createPainter, plan, rasterFromImageData} from 'painterly';
const context = canvas.getContext('2d');
if (!context) throw new Error('no 2d context');
context.drawImage(photo, 0, 0, canvas.width, canvas.height);
const source = rasterFromImageData(context.getImageData(0, 0, canvas.width, canvas.height));
// Pure data: every stroke, in painting order.
const painting = plan(source, {seed: 7});
// The hand: paints any prefix of the plan onto a 2D context.
const painter = createPainter(context, painting, brushes.bristle);
painter.paintTo(painting.strokes.length);
plan() takes options for the brush radii, the error threshold, stroke length
and curvature, and an onLayer callback that hears from it after each brush;
the defaults are tuned for photos around 1400 px. flow: 'tensor' trades the
1998 paper's swirls for calm: stroke directions are smoothed first, so a sky or
a lawn is laid in coherent runs that take their heading from the nearest real
edge. Plan and Stroke are plain
typed objects, so a plan can be serialized, replayed, or rendered by something
other than the bundled painter. packPlan() and unpackPlan() turn a plan into
a handful of typed arrays and back, exactly: the form to post from a worker or
keep around, at a seventh of the memory.
cli/ paints a photo on disk in one of three media and writes a JPEG, in a
few seconds at 1800 px. It is its own small package, so the library keeps no
runtime dependencies; it runs its TypeScript directly on Node 22.18 or later.
npm install # also builds the library the CLI paints with
(cd cli && npm install && npm link) # puts `painterly` on your PATH
painterly photo.jpg # matte oil, to ~/Downloads/photo-oil.jpg
painterly photo.heic --style gouache --size 2400 --out painting.jpg --open
All three plan with flow: 'tensor' and paint with the same hand; a style is
the rest of the recipe, in cli/src/styles.ts.
The canvas is cut into bands painted by one process each, which is what makes
it quick; the bands match a single canvas up to a few levels of antialiasing.
HEIC is read through macOS's sips.
src/image/ measures the photo: a float RGB raster, a linear-time Gaussian
blur (three box blurs), luminance, a Sobel gradient, and the same gradient
smoothed through its structure tensor for calmer strokes. No canvas, no DOM.src/plan/ decides the strokes, and names the two styles. Aaron Hertzmann's
Painterly Rendering with Curved Brush Strokes of Multiple Sizes
(SIGGRAPH 1998), ported and credited in the module header, with three habits
kept from the 2020 sketches: a canvas primed with the photo's dominant hue
family, a shuffled stroke order so the hand looks human, and seeded randomness.src/paint/ is the hand: a Brush says how many bristles a stroke becomes,
how translucent, how far each hair's color and weight wander, and how ragged
its start and finish are. bristle is the 2020 look, ribbon is this port's
first brush and the underpainting's default, round is the pointillist sketch
that never finished, flat is a house-painter's brush.src/demo/ is the page: photo picker, a layer-paced schedule, a player that
paints under a per-frame budget and jumps between the brushes' stages from
copies taken at the handovers, and a planner on its own thread that reports
each brush as it goes and plans the other photos behind the one playing. The
transport is a DVD player's rather than a scrubber on purpose: strokes go down
over one another and cannot be lifted, so there is no playing backwards, and
the moments worth returning to are where each brush begins. Back, play and on
are three drawn glyphs, the same geometry as the carets on the selects; the
stages beside them show where the painting has got to.cli/ is the command line: the three media, the finishes that sit the paint
on its support (relief, paper tooth, pigment pooling, ink), and the band
painters.processing/ holds the 2020 Processing sketches this grew out of, untouched.TypeScript, Canvas 2D, no framework. tsup builds the library to dist/ and
the page script and its worker straight into docs/, which GitHub Pages serves
as is; CI rebuilds docs/ and fails on a diff.
npm install
npm run check # Biome and tsc
npm test # the planner on synthetic images in Node, then the page in Chromium:
# determinism across loads, scrub-back equals paint-forward, phone width
npm run build
npm run gif # re-record docs/demo.gif from the page (needs ffmpeg)
npm run still # re-shoot docs/still.jpg, the photo-and-painting pair above
npm run size # re-measure the Numbers table
npm install --prefix cli
npm run check --prefix cli # tsc over the CLI (Biome covers it from the root)
npm test --prefix cli # bands against one canvas, the palette, the noise, and each style end to end
npm run styles --prefix cli # repaint docs/styles.jpg
The dog is mine. The others are from Unsplash, free to use under the Unsplash License and credited on the page: Jonny Gios, Cristina Anne Costello, Pietro De Grandi. All are shipped resized with their metadata stripped.
Dave Maynard, a front-end engineer in Atlanta. The dog is mine; everything else is credited below.
MIT. The bundled Mersenne Twister is BSD-3-Clause; see NOTICE.