尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Watch a photo get painted: multi-size brush strokes that follow the image and converge on it. Deterministic, zero dependencies, in a browser canvas.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
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.查看完整 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.