# Whiteboard Mask Animation

> 将中文文章或分镜制作成暖米黄纸张底的白板手绘遮罩动画。适用于需要“文章拆图、生成线稿、自动识别绘制模块、在预览界面调整区域与方向、确认后生成 MP4”的任务，或用户提到白板手绘、遮罩揭示、绘制区域、手部轨迹、动画预览。

- Skill: `geeklee/whiteboard-mask-animation` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add geeklee/whiteboard-mask-animation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/geeklee/whiteboard-mask-animation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: geeklee (https://skillmd.com/u/geeklee)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/geeklee/whiteboard-mask-animation

---


# 白板遮罩动画

使用遮罩逐步揭示静态配图，并让指定手部素材的笔尖跟随遮罩边缘，形成手绘动画效果。所有面向用户的说明、分镜、配置和界面文字必须使用中文。

## 默认实现参数

| 项目 | 默认要求 |
|---|---|
| 纸张背景 | 生成图使用暖米黄旧纸色（建议 `#F5EBD7`）；预览和视频都取原图距四角内缩 2 像素的平均色。 |
| 绘制速度 | `150 像素/秒`；横向按宽度、纵向按高度自动换算时长。 |
| 未绘制区域 | 通过离屏遮罩的 `destination-out` 擦除后续区域与保护区，不得提前露出。 |
| 编辑框 | 默认显示全部编号编辑框；编辑框不属于动画画面内容。 |
| 视频底色 | 使用纸张底色取样，禁止白底。 |

## 统一出图视觉规范（强制）

所有场景的源图必须遵循同一套视觉语言；在生成图片前，将以下要求完整写入出图提示词，并在生成后检查是否满足：

- **风格与构图：** 极简手绘插图、纯素描草图风格、类似 Notion 的克制涂鸦美学。以概念表达为主，不追求写实；构图简洁、背景干净、大量留白，整体情感平和、清晰，系列内的线条、人物和配色保持一致。
- **颜色与材质：** 使用米色纸张背景 `#F5EBD7`、深灰色草图线条；仅可用橙色 `#FFA500` 作为少量概念性点缀色。不得使用其他强调色、高饱和度配色或复杂纹理。
- **人物与对象：** 人物统一使用无脸圆形头的人像；对象以简洁轮廓、少量线条和留白表达，强调关系、变化或核心概念，而非真实比例、材质与细节。
- **绝对禁止：** 任何文字、词语、字母、数字、字体或标签；写实感、摄影细节、3D 效果、绘画质感；复杂场景、密集背景、繁复装饰和高饱和度画面。

## 工作流程

1. 读取文章，先输出配图策略，不生成图片。每幕只表达一个核心意思，建议每张图承载 25-35 秒口播。
2. 用户确认策略后，按“统一出图视觉规范”生成 16:9 暖米黄色旧纸张底线稿图。背景使用 `#F5EBD7`，主体之间保留充足留白，便于自动拆分；不得生成文字、复杂照片、重叠对象或与该规范冲突的视觉元素。
3. 在标注前，必须先阅读图片对应的文章/分镜，再实际查看图片，并获取原图的像素宽高；不得只根据文章臆测画面，也不得只按画面位置机械排序。先提炼文章的叙事事件，再将图片中的可见主体对应到事件，按“场景铺垫 → 关键人物/物体 → 动作冲突或变化 → 反应/结果”的语义顺序安排绘制。随后创建 `<图片名>.annotation.json`。
4. 生成区域预览图。预览台与 MP4 渲染器必须使用同一套遮罩规则：在任意时间点，只允许已开始绘制的模块在其当前揭示范围内显示；尚未开始的模块必须完全隐藏。若背景与前景元素互相遮挡，给前一模块添加 `protectedRegions`，并从其遮罩中扣除这些区域，避免提前露线。
5. 启动预览台，让用户播放、拖动时间轴、调整区域四边和四角、修改方向与时序。
6. 只有用户确认后才运行渲染器输出 MP4，并抽查开场、中段和结尾帧。

## 目录约定

在用户项目中创建：

```text
assets/whiteboard/<项目名>/
  scene-01-<名称>.png
  scene-01-<名称>.annotation.json
  scene-01-<名称>-whiteboard.mp4
```

图片与配置必须同名：`foo.png` 对应 `foo.annotation.json`。预览台据此自动加载配置。

## 标注规则

## 语义排序与像素级标注（必须执行）

1. **阅读依据：** 标注前必须同时具备文章/分镜与已查看的原图。若缺少任一项，先向用户索取，不得生成标注。
2. **顺序依据：** `sequence`、`startMs` 和 `label` 必须反映文章中的事件先后，而不是仅按从左到右、从上到下或视觉显眼程度排序。例如“猴子山抢香蕉”应先揭示假山场景，再揭示小猴与香蕉，随后揭示大猴抢夺，最后揭示围观小朋友的反应。
3. **坐标依据：** 每个模块必须输出原图坐标系中的整数像素 `x`、`y`、`width`、`height`；坐标原点为左上角，禁止百分比、比例坐标、估算坐标或省略尺寸。`canvas.width` 与 `canvas.height` 必须等于实际原图像素尺寸。
4. **模块字段：** 每个元素必须包含 `sequence`、`narrativeRole`、`region`、`reveal` 和 `handPath`。其中 `narrativeRole` 用中文说明它在文章中的叙事作用，`sequence` 为从 1 开始且连续的整数。
5. **校验：** 生成预览前检查每个区域是否在原图画布内、是否覆盖对应可见主体、是否与文章事件相符；重叠主体要使用 `protectedRegions` 保护后绘制模块，避免语义靠后的元素提前露出。

## 按路径长度保持匀速

- 默认绘制速度为 `150 像素/秒`。`durationMs = round(绘制距离像素 ÷ 150 × 1000)`；纵向使用 `region.height`，横向使用 `region.width`。方向改变时必须重新计算。
- 预览台在用户拖动区域大小或切换方向后，自动重算时长；时长字段仅展示计算结果，不能保留旧的固定时长。
- 首个模块从 `300-500ms` 开始；下一个模块从前一模块的 `startMs + durationMs + 100-200ms` 开始。这样不论区域大小和方向如何变化，笔尖的视觉移动速度一致。

## 笔尖抖动（绘制感）

匀速不变量只约束遮罩揭示，不约束手的装饰移动。笔尖位置 = 揭示边缘位置 + 垂直方向微抖。预览台与渲染器必须使用完全相同的公式，否则成片与预览错位：

- 行笔主轴严格跟随遮罩边缘（线性 `amount`），保证 150 像素/秒不变量不被破坏。
- 垂直方向叠加两层正弦抖动，并用 `sin(π·amount)` 包络，使起笔、收笔时抖动归零、笔尖干净落在揭示边缘，行笔中段有轻微左右晃动。
- 抖动相位 `seed = region.x * 0.13 + region.y * 0.27`，由模块坐标决定，保证每个模块抖动不同步、且逐帧可复现。
- 横向绘制（`left_to_right` / `right_to_left`）时抖动加在 `y`；纵向绘制（`top_to_bottom` / `bottom_to_top`）时抖动加在 `x`。

## 预览与渲染遮罩不变量（必须执行）

- 在时间 `t`，模块仅可显示其 `reveal.startMs ≤ t` 后、且不超过当前揭示进度的像素；未开始模块的任何线条、填充或图像内容都不得出现。
- 前一模块的遮罩必须扣除全部后续模块的 `region`，并额外扣除其 `reveal.protectedRegions`。后者用于处理矩形区域过大、主体交叠或背景线条可能泄露的情况。
- `protectedRegions` 采用与 `region` 相同的原图整数像素坐标。每个保护区属于较早模块，但只可由对应的后续模块在其自身 `startMs` 后揭示。
- 白板动画预览台和 MP4 渲染器必须实现完全相同的“绘制当前区域 → 扣除后续区域 → 扣除 protectedRegions → 显示源图”顺序；两者任一处缺失都视为未通过预览检查。
- **浏览器预览实现：** 在离屏 `mask` 画布中先按当前揭示进度绘制源图，再以 `globalCompositeOperation='destination-out'` 逐个擦除全部后续 `region` 与 `protectedRegions`，最后将该离屏画布合成到主画布。不得将“当前区域 + 多个待扣除矩形”放进同一个 `clip('evenodd')` 路径：后续矩形彼此重叠时，奇偶规则会把重叠部分反转为可见，导致第 3、4 个区域提前出现。
- **编辑框与画面分离：** 编号区域框是编辑辅助层，可默认显示全部模块并允许用户隐藏；它不属于源图揭示结果，也不得改变遮罩、时间顺序或 MP4 内容。
- **纸张底色取样：** 预览台和 MP4 渲染器都从原图距四角内缩 2 像素的位置取平均色，作为未绘制区域的底色；禁止填充纯白。两端必须使用同一组取样坐标，避免预览与视频出现色差。

- `character`、`object`、`structure`：优先 `top_to_bottom`。
- 标题、道路、箭头、时间线：优先 `left_to_right`。
- 数字增长、柱状图、向上箭头：优先 `bottom_to_top`。
- 反向关系或向左箭头：使用 `right_to_left`。
- 复杂主体拆成 2-4 个模块；不要整张图片一次揭示。
- 遮罩区域比元素边界多留 18-24 像素，防止首帧漏线。

## 配置示例

```json
{
  "sceneId": "scene-01",
  "canvas": { "width": 1920, "height": 1080 },
  "storyBasis": "文章中该画面的事件摘要",
  "sceneDurationMs": 6000,
  "elements": [
    {
      "id": "character",
      "label": "人物",
      "sequence": 1,
      "narrativeRole": "文章中首先出现的主体",
      "type": "character",
      "region": { "x": 240, "y": 180, "width": 360, "height": 620 },
      "reveal": { "direction": "top_to_bottom", "startMs": 400, "durationMs": 4133, "maskPaddingPx": 22 },
      "handPath": { "start": [420, 190], "end": [420, 790], "easing": "easeInOut" }
    }
  ]
}
```

## 使用脚本

- 使用 `scripts/render_annotation_preview.py` 生成区域编号与方向预览图。
- 使用 `scripts/render_mask_whiteboard.py` 渲染单幕 MP4。必须传入图片、同名配置、输出视频和 `assets/drawing-hand.png`。
- 使用 `scripts/preview_server.py` 配合 `assets/preview.html` 启动预览台。预览台可从项目素材库选择图片，也可通过“从文件选择”打开系统文件对话框。选中本地图片后，复制图片和其同目录的同名 `.annotation.json` 到项目导入区，再自动加载配置。

## 质量检查

渲染前确认：

- 首帧为干净的暖米黄色旧纸张底，没有提前露出线条。
- 已阅读对应文章/分镜并实际查看原图；`canvas` 与原图像素尺寸一致，所有 `region` 均为整数像素坐标。
- `sequence`、`startMs` 与文章事件顺序一致；预览图中的编号、标签和区域来自同一份标注 JSON。
- 在开场、任意重叠模块的中段和所有模块完成后三个时间点检查：未绘制模块均不可见，重叠保护区不会漏出，最终帧显示完整原图。
- 所有模块均在画布内，绘制顺序与口播一致。
- 笔尖贴近正在推进的遮罩边缘。
- 所有模块结束后，停留至少 0.5 秒完整原图。
- 复杂遮挡采用保护区，结尾仍显示完整原图。
- 两个模块区域重叠时，后绘制模块拥有重叠部分的显示权；前一个模块的遮罩必须扣除该重叠区，不能提前露出后一个模块。

如用户要求修改效果，先回到预览台调整配置，不要直接反复渲染视频。

