Home / All Skills / Content & Media Creation / p5-paint-animation
Content & Media Creation · heygen-com/hyperframes-community-skills

p5-paint-animation

> Turn a text prompt, a photo, or a live video clip into hand-made-looking p5.js animation — pencil handwriting that writes itself, photos repainted as brushstroke art with paint-on animation and camera moves, and short videos (iPhone Live Photos included) repainted frame-by-frame as a living painting. Trigger on: "handwritten text animation", "paint this photo", "painted video", "sketch effect", "turn my live photo into a painting", "write-on effect". Everything is 100% code: p5.brush strokes rendered headlessly, no image models.

L4 High-Privilege · Graded auth & full auditV1 Commit-PinnedExecute CommandNetwork AccessWrite FileDual-pass Audit
Author heygen-comGitHub heygen-com/hyperframes-community-skills ↗Stars 156License Apache-2.0(仓库根 LICENSE)Commit ba7a0bb6d3
Control levels describe skill capabilities and suggested control measures; they do not represent quality or safety certifications, nor do they dictate usability. Usage decisions rest entirely with users and administrators. Different agent hosts apply varying constraints; do not assume hosts provide automatic protection. Third-party skill copyrights belong to their original authors. Use at your own risk.

1How It Works & Mechanism

Three workflows share a single engine, dispatched based on input: text → write letters, photo → repaint (still frame or self-painting animation), video/Live Photo → frame-by-frame repainted living painting; all purely code-generated without using any image models.

skills/p5-paint-animation/SKILL.md
Everything is 100% code: p5.brush strokes rendered headlessly, no image models.
What it is doing: Delegating the 'handmade feel' to brush simulation (p5.brush) + stroke-by-stroke drawing sequence rather than diffusion models; therefore reproducible (same seed yields identical result) and completely offline.

The rendering architecture is 'generator + frame-by-frame screenshot': Node spawns a headless Chromium instance and injects the harness and sketch; the sketch's paint() is a generator where each yield corresponds to drawing one stroke, Node captures a screenshot at each step, and ffmpeg stitches them into an MP4.

skills/p5-paint-animation/scripts/render-anim.mjs
// draw() advances the paint() generator spf steps, then pauses; the node side
Stitching is in the same file: execFileSync("ffmpeg", [ "-y", "-framerate", String(fps), "-i", resolve(framesDir, "%05d.png"), …]); while the still frame pipeline (render.mjs) captures the canvas screenshot directly.

Network and filesystem access during rendering are **hard isolated**: Each page installs request interception before opening, allowing only about:/blob:/data: while aborting everything else; photos/videos are injected as data URLs (IMG_SRC), so sketches can neither read local files nor access the network.

skills/p5-paint-animation/scripts/lib/page-safety.mjs
const allowedProtocols = new Set(["about:", "blob:", "data:"]);
Interception implementation in the same file: request.abort("blockedbyclient"); injection point in render.mjs: '// --image path -> global IMG_SRC data URI (load with loadImage(IMG_SRC) in setup)'.

Parameter injection is a whitelisted variable declaration, not arbitrary code evaluation: --inject "NAME=value" is only translated to `var NAME = <JSON>`; variable names must match /^[A-Z][A-Z0-9_]*$/, and values only permit JSON strings/numbers/booleans, throwing errors otherwise.

skills/p5-paint-animation/scripts/lib/inject.mjs
const NAME = /^[A-Z][A-Z0-9_]*$/;
Value parsing and errors in the same file: throw new Error("--inject values must be JSON strings, numbers, or booleans"); as well as error for unterminated string '--inject contains an unterminated string'.

Reproducibility is an explicit goal: When --seed is provided, mulberry32 overrides Math.random before libraries load (ensuring internal noise and scattering of p5.brush are reproducible); documentation sets 'same seed must be byte-for-byte identical' as an acceptance criterion and requires tuning one dial at a time.

skills/p5-paint-animation/scripts/render.mjs
// Seeded PRNG (mulberry32) installed over Math.random BEFORE libs load, so
Corresponding discipline in SKILL.md: 'Same seed = identical render. Iterate one dial at a time against a fixed seed.'

The video pipeline trades for stability using a 'locked stroke grid': The grid is created only once on frame 1 and locked down; subsequent frames only resample color and orientation—preventing brushstrokes from jittering when the subject moves.

skills/p5-paint-animation/SKILL.md
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.
Contrast: day-in-my-life in this repo deliberately lets strokes 'boil' at 8 fps, whereas here it does the opposite—because a living painting needs 'one painting that moves', not 'swapping to a new painting every second'.

Input quality is documented as a prerequisite for success: Photos must be cropped to the subject first (face size in frame determines success), Live Photos undergo motion triage to paint only calm windows, and dark footage uses gamma-only grading without increasing saturation.

skills/p5-paint-animation/SKILL.md
**Crop to the subject first.** Face size in frame is destiny: selfie-distance faces resolve beautifully; small faces stay figures.
Other quotes from the same section: '**Motion triage first.** Live Photos include the phone swinging.' and 'Dark footage: grade with **gamma only** (`eq=gamma=1.2`). Never boost saturation before painting — shadow chroma noise becomes literal teal paint.'

Disk write scope is restricted to caller-specified locations with automatic cleanup: Only writes out.png / out.mp4 (plus co-located .track.json; _frames_* directory is retained only when --keep-frames 1).

skills/p5-paint-animation/SKILL.md
Writes only inside paths you pass (`out.png` / `out.mp4`, plus a sibling `.track.json` and, with `--keep-frames 1`, a `_frames_*` directory).
Cleanup implemented in render-anim.mjs: if (flags["keep-frames"] !== "1") await rm(framesDir, { recursive: true, force: true }); video pipeline also uses out/_src_*/_dst_* staging directories, similarly deleted during teardown.

2Core Capabilities

01Flow A: Text → handwriting animation. Built-in full alphabet (lowercase/uppercase/numbers/punctuation), auto-wrapping, written out stroke by stroke
02Flow A adjustable dials: Font size, ink color, pen type (2B pencil/crayon/charcoal), blank paper texture, all passed via --inject
03Camera follow recipe: Rendering outputs per-frame pen tip positions (out.track.json), enabling cropping for 'camera tracking the pen' motion
04Flow B: Photo → brushstroke painting. Outputs still frames (PNG) or 'self-painting' videos (coarse to fine, layered in painter's order, subject first)
05Flow B composition discipline and camera recipe: Crop to subject first (face size determines success), use SUBJECT ellipse to let subject complete all passes and leave a subject-done marker, then zoom out along the marker
06Flow C: Video (including iPhone Live Photo) → living painting. Locked grid maintains stability, WORLD=1 tracks camera motion, MASKED=1 paints only the subject (paired with pre-segmented frames)
07Three output forms handled by three harnesses: Still PNG (render.mjs), painting process MP4 (render-anim.mjs), video repainting MP4 (render-video.mjs)
08QC threshold: Must inspect actual output frames (last frame fully rendered, no holes, start/mid/end continuity), same seed must be byte-for-byte identical, and cuts must be numerically verified against motion disciplines

3External Dependencies

KindDependency / Endpoint
packagepuppeteer 25.9.0(Apache-2.0)、p5 2.3.2(LGPL-2.1)、p5.brush 2.2.1(MIT),由 lockfile + package.json 双钉
cliffmpeg(渲染链路:把逐帧 PNG 缝成 MP4、视频流程抽帧与拼回)
packageNode 22.12+ 与约 500MB 磁盘(Chromium)
cli可选:npx hyperframes remove-background(仅 Flow C 主体抠像用;CLI 从 npm 取)
network无运行期外发:渲染时页面被请求拦截(只放行 about:/blob:/data:),素材以 data URL 注入
package素材来源:调用方自备的文字/照片/视频(Live Photo 需其配对的 .mov)
clisetup.sh 的一次性下载(npm ci + Puppeteer 拉 Chrome for Testing),自述无凭证、无成本、不上传

4Recommended Governance & Evidence

Suggested Controls for Safe Operation
For Agent Runtime
Graded/per-use authorization, sandbox recommended
For Administrators
Approval workflow, rollback support, full audit
Evidence & Triggers (File Paths & Excerpts)
  • scripts/: Execute local scripts or commands: node scripts/render.mjs <sketch.js> [out.png] [--size WxH] [--seed N] [--im
  • 网络端点: Declare external network access: registry.npmjs.org: npm ci installs puppeteer 25.9.0 / p5 2.3.2 / p5.brush 2.2.1 (p
  • 本地工作目录: File I/O and artifact output: Output paths specified by caller: out.png / out.mp4; render-anim also writes co-located <out>.track.json (per-frame pen tip position and mark
Audit Notes: The reason for evaluating as Yellow rather than Blue/Green is the one-time third-party fetching during the setup phase: npm ci pulls puppeteer/p5/p5.brush from registry.npmjs.org (strictly pinned versions, but still third-party packages), and Puppeteer downloads its pinned Chrome for Testing from Google's download services; there is also optional npx hyperframes remove-background (fetching a CLI from npm for subject matting). This matches the pattern of embedded-captions evaluated as Yellow in the same batch (pulling third-party packages + repository model weights on first run). Why not Orange: The entire directory contains **no credential access whatsoever** (no env/cookie/keychain), runtime **actively cuts off network**—rendered pages are intercepted down to only about:/blob:/data:, assets are injected as data URLs, the page can neither read local files nor access the network, and setup states 'no credentials, no cost, nothing is uploaded'. Why not Red: Chromium flags across the three launchers only include --enable-unsafe-swiftshader (software rendering fallback for non-GPU environments), with no security downgrades like --no-sandbox or --disable-web-security, nor anti-scraping bypasses or arbitrary code execution surfaces (--inject is not eval). What users should be aware of is that sketch files are provided by the caller and injected for execution—the trust boundary of this skill includes 'the party writing the sketch'.

5Second-Pass Verification

  • [ok] 『渲染期网络与本地文件访问被挡住』是否真成立 — render.mjs / render-anim.mjs / render-video.mjs 都在 const page = await browser.newPage(); 之后立即 await hardenPage(page);page-safety.mjs 用 page.setRequestInterception(true) 后只放行 about:/blob:/data:,其余 request.abort('blockedbyclient')。素材以 `var IMG_SRC = "data:…;base64,…"` 注入,故 sketch 拿不到 file:// 也发不出请求。
  • [ok] 有没有关闭沙箱或安全降级的 flag(红档的关键判据) — 三个 launcher 的 launch 参数只有 headless: "new" 与 args: ["--enable-unsafe-swiftshader"];无 --no-sandbox、--disable-web-security、--allow-file-access-from-files、--single-process、--ignore-certificate-errors。--enable-unsafe-swiftshader 是为无 GPU 环境启用软件渲染,不属安全降级面。
  • [ok] 『无凭证』是否成立 — 全目录 grep process.env / os.environ 零命中;三个 harness 无 token/key 参数;setup.sh 自述 'No credentials, no cost, nothing is uploaded.';无 cookie/keychain/.env 访问。
  • [ok] --inject 是否等于任意代码执行 — lib/inject.mjs 的 buildInjectDeclarations 只产出 `var NAME = <JSON.stringify(value)>;`;NAME 必须匹配 /^[A-Z][A-Z0-9_]*$/(否则 'invalid --inject name'),值非 JSON 字符串/数字/布尔即抛错,未闭合字符串亦抛错。全文件无 eval / new Function。
  • [ok] seed 可复现是否真落在代码(而非文档口号) — render.mjs 与 render-anim.mjs 都构造 seedShim,并在 addScriptTag 注入 p5.min.js / p5.brush.js **之前**先注入 mulberry32 覆盖 Math.random(注释:'Seeded PRNG (mulberry32) installed over Math.random BEFORE libs load')。
  • [ok] 文档声明的旋钮是否与 sketch 实现一致 — handwriting.anim.js 有 typeof PHRASE / INK / BRUSH / PAPER 守卫(const TEXT = typeof PHRASE !== "undefined" ? PHRASE : "hello world"; 等);repaint.anim.js 有 STYLE / DETAIL / LOOSE / SUBJECT(subject-first 段);repaint-video.js 有 WORLD / MASKED / BG 且 render-video.mjs 支持 --frames DIR 传已抠像帧。与 SKILL.md 的 dial 列表逐项对得上。
  • [discrepancy] 『Requires: Node 22.12+』是否被运行时校验 — SKILL.md 的 Requirements 写 'Requires: Node 22.12+, ffmpeg on PATH, ~500MB disk (Chromium).',但 scripts/package.json 没有 engines 字段,setup.sh 与三个 harness 也都不做 Node 版本检查;低版本 Node 会照常尝试执行,只在运行期以别的错误暴露。因此该版本要求是文档级而非技术门槛(对 pin commit 有效的真实出入)。
  • [unlocatable] Flow C 抠像『local CoreML inference, no data leaves the machine』 — remove-background 属于 HyperFrames CLI,该 CLI 不在本仓库内,其后端实现(是否 CoreML、是否在非 macOS 平台改走别的推理路径、是否会下载权重)无法在本 skill 目录内复核;本次为只读侦查且不运行该命令,故按文档声明记录,不作为已核事实。

6Verdict & Scenarios

  • Actively narrows execution surface during render: Request interception permits only about:/blob:/data:, assets injected via data URLs, zero network or local file access for pages
  • 100% local code generation with no image models, enabling byte-for-byte reproducibility with the same seed (seed overrides Math.random before library loading)
  • --inject is strictly constrained to whitelisted variable declarations rather than eval, separating parameter tuning from arbitrary code execution
  • Documents failure modes instead of leaving them to debugging: crop subject first, triage motion first, gamma-only on dark footage, locked grid for video
  • Strictly pinned dependency versions with reproducible smoke test commands; minimal disk write footprint with automatic temporary frame cleanup
  • Best for: Best for: Creators and frontend/graphics engineers needing 'handmade' assets—turning a short phrase into self-writing handwriting animation, turning photos into brushstroke art (stills or self-painting process), turning short clips (including iPhone Live Photos) into frame-by-frame repainted living paintings; also suitable as an asset pipeline step for larger films (outputting MP4/PNG to downstream editing). Because it is fully code-generated locally, offline during render, and reproducible with identical seeds, it is particularly suited for pipelines requiring repeatability.
    Not for: Not suitable for: ① Scenarios requiring crisp typography or brand-exact letterforms (real fonts should be used); ② Projects demanding photorealism; ③ Footage longer than ~10 seconds (rendering cost scales linearly per frame); ④ Real-time/interactive web pages (this skill only produces offline MP4/PNG); ⑤ Single-prompt turnkey expectations (provides three specific workflows + dials requiring recipe following); ⑥ Environments unwilling to download npm packages and browsers, or with less than ~500MB free disk.
    Install Agent-Ready
    ① Verified Mirror 2026-09-28
    Option A · Direct DownloadDownload p5-paint-animation.tar.gz
    sha256: 1f0478d5e5ca2ab0…
    Option B · Copy Guide for Agent
    install.json
    Agents automatically download and verify mirrors from this JSON guide.
    ② Upstream GitHub
    GitHub Repo ↗
    Pinned to commit ba7a0bb6d3.