p5-paint-animation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesep5 Paint Animation
p5 绘画动画
Three flows, one engine. Pick by input:
| Input | Flow | Output |
|---|---|---|
| Text prompt / phrase | A: Write-on | Handwriting animates onto ruled paper, pen-tracked for camera |
| Photo | B: Repaint + paint-on | Brushstroke painting, still or painting-itself film, camera pull |
| Video / Live Photo | C: Living painting | Every frame repainted over a locked stroke lattice |
三种流程,一个引擎。根据输入类型选择:
| 输入 | 流程 | 输出 |
|---|---|---|
| 文本提示/短语 | A:书写动画 | 手写文字在格纸上逐笔呈现,镜头追踪笔的轨迹 |
| 照片 | B:重绘+绘画动画 | 笔触风格画作,静态图或逐步绘制的影片,镜头拉远效果 |
| 视频/Live Photos | C:动态画作 | 每一帧都基于固定笔触网格重绘 |
When to use / when not
适用场景与不适用场景
Use for: hand-made-looking motion — handwriting that writes itself, photos as
brushstroke paintings (still or animating), short clips as living paintings.
Do NOT use for: crisp typography or brand-accurate text (use real fonts),
photoreal output, footage longer than ~10s (render cost scales per frame ≈
5–20s each), or live/interactive pages (this renders offline MP4/PNG).
适用场景:需要手绘风格动态效果的场景——自动书写的手写文字、笔触风格的画作(静态或动态绘制)、短视频转动态画作。
不适用场景:需要清晰排版或品牌精准文字的场景(请使用真实字体)、照片级写实输出、时长超过约10秒的素材(渲染成本随帧数增长,每帧约5-20秒)、实时/交互式页面(本工具仅生成离线MP4/PNG文件)。
Requirements and side effects
运行要求与副作用
- Requires: Node 22.12+, ffmpeg on PATH, ~500MB disk (Chromium).
- (run manually, once) uses the committed lockfile to download pinned dependencies from the npm registry: puppeteer 25.9.0, p5 2.3.2 (LGPL-2.1), and p5.brush 2.2.1 (MIT). Puppeteer downloads its pinned Chrome for Testing build from Google's download service. Read-only downloads; no credentials, no cost, nothing uploaded.
sh scripts/setup.sh - Renders launch sandboxed headless Chromium on a blank page. Network and local file requests from sketches are blocked; sketches receive inputs as data URLs.
- Writes only inside paths you pass (/
out.png, plus a siblingout.mp4and, with.track.json, a--keep-frames 1directory)._frames_* - Optional, only for flow C subject matting: (downloads the HyperFrames CLI from npm; local CoreML inference, no data leaves the machine).
npx hyperframes remove-background
- 运行要求:Node 22.12+、ffmpeg已添加至PATH、约500MB磁盘空间(用于Chromium)。
- 执行(手动运行一次)会使用已提交的锁文件从npm注册表下载固定版本的依赖:puppeteer 25.9.0、p5 2.3.2(LGPL-2.1协议)、p5.brush 2.2.1(MIT协议)。Puppeteer会从谷歌下载服务下载其固定版本的Chrome for Testing构建包。所有下载均为只读;无需凭证、无费用、无数据上传。
sh scripts/setup.sh - 渲染时会启动沙箱化的无头Chromium,在空白页面运行。草图的网络和本地文件请求会被阻止;草图通过数据URL接收输入。
- 仅在指定路径内写入文件(/
out.png,以及同级的out.mp4文件,若指定.track.json则会生成--keep-frames 1目录)。_frames_* - 可选功能(仅适用于流程C的主体抠图):(从npm下载HyperFrames CLI;本地CoreML推理,数据不会离开设备)。
npx hyperframes remove-background
Verify success
验证运行成功
Render any command below, then open the output: the last frame must show the
complete artwork (text fully written / painting fully covered), and re-running
with the same must produce identical bytes on the canvas. The smoke
test used for review: .
--seednode render-anim.mjs sketches/handwriting.anim.js /tmp/t.mp4 --size 1600x900 --seed 7 --inject 'PHRASE="go make something"'运行以下任意命令,然后打开输出文件:最后一帧必须显示完整作品(文字完全书写完毕/画作完全覆盖画布),且使用相同重新运行必须生成完全相同的画布字节数据。审核用冒烟测试命令:。
--seednode render-anim.mjs sketches/handwriting.anim.js /tmp/t.mp4 --size 1600x900 --seed 7 --inject 'PHRASE="go make something"'Setup (once)
首次配置
sh scripts/setup.shInstalls the exact dependency tree recorded in .
All commands below run from .
scripts/package-lock.jsonscripts/sh scripts/setup.sh该命令会安装中记录的精确依赖树。以下所有命令均需在目录下运行。
scripts/package-lock.jsonscripts/Flow A — prompt → write-on animation
流程A——文本提示→书写动画
node render-anim.mjs sketches/handwriting.anim.js out.mp4 \
--size 1600x900 --fps 24 --seed 7 --hold 12 \
--inject 'PHRASE="your text here";SIZE=170'- Full alphabet: lowercase, caps, digits, . Wraps automatically.
? . , - / @ - Dials via :
--inject,SIZE(hex),INK(BRUSHpencil,"2B","crayon"),"charcoal"for blank paper.PAPER=1 - The run writes — per-frame pen position. For a camera-follows-pen move, crop each frame along the smoothed track (exponential follow, gain ~0.14, camera y LOCKED to the baseline — never follow the pen tip's y or the camera bounces on every ascender).
out.track.json - For scraggly/messy writing raise the per-letter jitter in the sketch; for word-sync to a voiceover, transcribe first and start each word's strokes at its spoken time.
node render-anim.mjs sketches/handwriting.anim.js out.mp4 \
--size 1600x900 --fps 24 --seed 7 --hold 12 \
--inject 'PHRASE="your text here";SIZE=170'- 支持全字符集:小写字母、大写字母、数字、。自动换行。
? . , - / @ - 通过参数调整:
--inject(字号)、SIZE(十六进制颜色值)、INK(画笔类型,如BRUSH铅笔、"2B"蜡笔、"crayon"炭笔)、"charcoal"(使用空白纸张)。PAPER=1 - 运行后会生成文件——记录每帧的笔位置。若要实现镜头跟随笔移动的效果,需沿平滑后的轨迹裁剪每一帧(指数跟随,增益约0.14,镜头Y轴锁定至基线——切勿跟随笔尖Y轴,否则镜头会在每个字母的上升部分跳动)。
out.track.json - 若要生成潦草/随性的字迹,可提高草图中每个字母的抖动值;若要实现文字与旁白同步,需先转录旁白,再在对应时间点开始每个单词的笔画渲染。
Flow B — photo → painting (+ paint-on film + camera)
流程B——照片→绘画(含绘画动画+镜头效果)
Still:
node render.mjs sketches/repaint.anim.js out.png \
--size 720x900 --seed 3 --image photo.jpg --inject "STYLE=0;DETAIL=3"Paint-on film (the painting paints itself, coarse-to-fine, painter-ordered —
calm regions first, the face last):
node render-anim.mjs sketches/repaint.anim.js out.mp4 \
--size 720x900 --seed 3 --image photo.jpg --fps 14 --hold 40 \
--inject 'STYLE=0;DETAIL=3;SUBJECT="0.5,0.5,0.44,0.55"'- Crop to the subject first. Face size in frame is destiny: selfie-distance faces resolve beautifully; small faces stay figures. Portrait 4:5 or 9:16.
- Dials: 0 pastel / 1 gouache / 2 charcoal-sketch.
STYLE1 painterly, 2 faces resolve, 3 soft features (noses), 5 portrait-grade (adds a second fine lap).DETAIL0.7–1.5 stroke wildness.LOOSEcolor compensation (~1.1).SAT - (image fractions) paints everything inside that ellipse through all passes FIRST and drops a
SUBJECT="cx,cy,rx,ry"frame marker. Camera pull-out recipe: hold tight on the subject, then at the marker frame ease wide with ffmpeg zoompan (cosine ease, ~2s):subject-done(upscale 2x lanczos first so the tight phase stays sharp).zoompan=z='if(lte(in,M),1.9,1.85-0.85*(0.5-0.5*cos(PI*min((in-M)/28,1))))'
静态图:
node render.mjs sketches/repaint.anim.js out.png \
--size 720x900 --seed 3 --image photo.jpg --inject "STYLE=0;DETAIL=3"绘画动画(画作逐步完成,从粗糙到精细,按绘画顺序——先绘制平静区域,最后绘制面部):
node render-anim.mjs sketches/repaint.anim.js out.mp4 \
--size 720x900 --seed 3 --image photo.jpg --fps 14 --hold 40 \
--inject 'STYLE=0;DETAIL=3;SUBJECT="0.5,0.5,0.44,0.55"'- 先裁剪主体区域。画面中面部的大小决定最终效果:自拍距离的面部会呈现极佳细节;小面部仅能保留轮廓。建议使用4:5或9:16的竖版比例。
- 可调参数:0=淡彩/1=水粉/2=炭笔素描;
STYLE1=写意风格、2=面部可识别、3=柔和五官(如鼻子)、5=人像级精度(添加第二层精细笔触);DETAIL0.7–1.5=笔触随性程度;LOOSE≈1.1=色彩补偿。SAT - (图像比例值)会优先绘制椭圆区域内的所有内容,并标记
SUBJECT="cx,cy,rx,ry"帧。镜头拉远方案:先聚焦主体,在标记帧处通过ffmpeg的zoompan实现平滑拉远(余弦缓动,约2秒):subject-done(先使用lanczos算法放大2倍,确保聚焦阶段画面清晰)。zoompan=z='if(lte(in,M),1.9,1.85-0.85*(0.5-0.5*cos(PI*min((in-M)/28,1))))'
Flow C — video / Live Photo → living painting
流程C——视频/Live Photos→动态画作
node render-video.mjs clip.mov out.mp4 \
--size 720x900 --fps 12 --seed 7 --inject "STYLE=0;DETAIL=2"- The stroke lattice is built ONCE from frame 1 and locked; only colors and orientations resample per frame. Strokes hold still — the painting never "boils" while the subject moves.
- Live Photos: get the paired (share-sheet Options → Live Photo ON, or Photos.app → Export Unmodified Original). If it arrives as bare JPEG the motion was stripped in transit — ask for a 2–3s normal video instead; that's better input anyway.
.mov - Motion triage first. Live Photos include the phone swinging. Measure
per-frame motion (→ YDIF), paint only the calm window with
ffprobe -f lavfi "movie=in.mov,signalstats", and ping-pong the result (forward+reverse concat) for a seamless breathing loop.--start/--dur - Big camera motion? tracks the camera by image correlation and lets strokes live in world coordinates — the camera pans across one growing painting. Translation-only; fast zooms drift.
WORLD=1 - Subject-only painting: matte frames first (), pass the matted PNGs with
npx hyperframes remove-backgroundand--frames DIR, and setMASKED=1to your paper color.BG - Dark footage: grade with gamma only (). Never boost saturation before painting — shadow chroma noise becomes literal teal paint.
eq=gamma=1.2
node render-video.mjs clip.mov out.mp4 \
--size 720x900 --fps 12 --seed 7 --inject "STYLE=0;DETAIL=2"- 笔触网格仅基于第一帧构建并锁定;仅每帧重新采样颜色和笔触方向。笔触保持静止——主体移动时画作不会出现“抖动”。
- Live Photos:获取配对的文件(分享选项→开启Live Photos,或通过照片应用→导出未修改的原始文件)。若仅收到JPEG文件,说明传输过程中丢失了动态信息——请改用2-3秒的普通视频作为输入,效果更佳。
.mov - 先筛选动态片段。Live Photos通常包含手机晃动的画面。使用测量每帧运动幅度(查看YDIF值),仅使用
ffprobe -f lavfi "movie=in.mov,signalstats"参数渲染平稳片段,再将结果正反向拼接形成无缝循环的呼吸效果。--start/--dur - 镜头大幅移动?设置可通过图像关联追踪镜头,让笔触处于世界坐标系——镜头在一幅不断生成的画作上平移。仅支持平移;快速缩放会出现偏移。
WORLD=1 - 仅绘制主体:先对帧进行抠图(),通过
npx hyperframes remove-background和--frames DIR传入抠图后的PNG文件,并设置MASKED=1为纸张颜色。BG - 深色素材:仅调整gamma值()。切勿在绘画前提高饱和度——阴影中的色度噪点会变成明显的蓝绿色笔触。
eq=gamma=1.2
QC gates (all flows)
质量检查标准(所有流程)
- Read the actual output frames; never ship on intent. Check: faces resolve, no bare-canvas gaps, first/mid/last frames coherent.
- Same seed = identical render. Iterate one dial at a time against a fixed seed.
- For anything joining a larger film, verify seams numerically per motion-doctrine — this skill produces material, not final grammar.
references/flows.mdreferences/gotchas.md- 查看实际输出帧;切勿仅凭预期交付。检查要点:面部细节清晰、画布无空白区域、首尾及中间帧连贯。
- 相同seed值应生成完全相同的渲染结果。每次仅调整一个参数并固定seed值进行迭代。
- 若要将生成内容整合到更长影片中,请根据运动规则验证衔接处的数值一致性——本工具生成素材,不提供最终剪辑逻辑。
references/flows.mdreferences/gotchas.md