把任意图片重绘成「一笔一笔画出来」的过程动画,内置 八种风格:油画、线描、色块、素描、水墨、点彩、霓虹、像素。 纯前端实现,图片不上传、无需后端,可直接托管到 GitHub Pages。
仓库:https://github.com/tyuecode/tyuecode.github.io
在线体验:https://tyuecode.github.io/
| 风格 | 工序 | 效果 |
|---|---|---|
| 油画 | 刷底色 → 起稿 → 单色底层 → 铺大色 → 塑造 → 塑形 → 罩染 → 干扫 → 细部 → 提白 | 十道古典工序,起收锋、笔毛分叉、颜料渐枯、厚涂浮雕、罩染透叠 |
| 线描 | 定形 → 勾主线 → 结构线 → 细描 → 点睛 | 近黑底上的细白线,只勾结构,像版画稿 |
| 色块 | 铺底色 → 暗部色块 → 中间调 → 亮部 → 点缀 | 分层平涂的硬边色块,限量配色的海报感 |
| 素描 | 起形 → 铺调子 → 排线 → 交叉线 → 提亮 | 纸底石墨排线,顺结构走向,暗部交叉线压深 |
| 水墨 | 铺水 → 勾线 → 渲染 → 皴擦 → 点苔 → 留白 | 宣纸墨色,浓淡枯湿靠叠墨,留白透气 |
| 点彩 | 底色 → 中层 → 密集 → 边界 → 高光 | 密集小色点堆成画面,颜色靠视觉混色 |
| 霓虹 | 铺底 → 主线 → 细线 → 光晕 → 亮点 | 深底发光线条,像通电的招牌 |
| 像素 | 低分辨率 → 中分辨率 → 细化 → 高光格 | 量化成方格逐格点亮,复古游戏感 |
每种风格都有自己的一套工序、笔触语言和底纹,不是同一套笔刷换配色: 线描的线贴着 Canny 脊线吸附;色块把分层区域抽成轮廓直接填充;素描按明暗分四遍排线; 水墨用叠墨做浓淡、把纹理压到最疏;点彩是六边形错排撒点、暗部点更大以免白纸透出; 霓虹用加色混合做辉光但主体走正常混合(否则密集处会叠成白);像素先铺大格再补细格。
- 🎨 八种风格:油画 / 线描 / 色块 / 素描 / 水墨 / 点彩 / 霓虹 / 像素,各有一套工序和笔触语言
- 🖌 物理笔触:起收锋、笔毛分叉、颜料渐枯、厚涂浮雕、罩染透叠
- 🔒 本地处理:图片只在你的浏览器里,绝不联网上传
- 📦 可下载:播放器打包成单个 HTML 文件,双击离线播放、随意分享
- 🖼 高清终帧:终帧按 1× / 2× / 3× 离屏重绘,不受屏幕尺寸限制
- 📱 响应式:桌面双栏、移动端单栏,自动适配触屏
- ⚡ 快:1400px 图约 1.5–3 秒,数万条笔触
- 打开 https://tyuecode.github.io/(或本地
python -m http.server后访问) - 拖入 / 选择图片(PNG / JPG / WEBP)
- 选一种风格
- 调「细腻程度」「动画时长」「处理精度」
- 点「开始生成」
- 页面内实时预览,或下载单文件播放器 / 导出终帧 PNG
「细腻程度」在每种风格下的含义不同,界面上会跟着所选风格给出说明:
| 风格 | 往左(更细) | 往右(更粗) |
|---|---|---|
| 油画 / 线描 / 素描 | 笔触更细更密,细节更清楚 | 笔触更粗犷,只留主干 |
| 色块 | 保留更多小色块,边缘贴原图 | 小色块被并掉,画面更概括 |
| 水墨 | 墨色更细碎 | 笔触更宽,更像大笔挥洒 |
| 点彩 | 点更密更小,接近照片 | 点更大更疏,更像修拉 |
| 霓虹 | 线条更多更密 | 只留最亮的主干 |
| 像素 | 格子更多更清晰 | 格子更大更复古 |
导出终帧时可以选 1×(原图尺寸)、2×、3×。终帧是在屏幕外的独立画布上 按所选倍数重新绘制的,不受预览窗口大小限制:
| 档位 | 输出尺寸(以 1527×1000 为例) | 大致文件大小 |
|---|---|---|
| 原图尺寸 | 1527×1000 | ~3.5 MB |
| 2 倍 | 3054×2000 | ~13 MB |
| 3 倍 | 4581×3000 | ~27 MB |
| 文件 | 说明 |
|---|---|
index.html |
主页面(上传、选风格、调参、实时预览、下载) |
core.js |
分析 + 八种风格的笔触生成 + 时间轴 |
render.js |
共用的笔触渲染器(网页预览与播放器共用,保证所见即所得) |
worker.js |
Web Worker,后台生成不卡界面 |
player-template.html |
下载用的单文件播放器模板(内联 render.js) |
build.js |
资源版本号(见下方「更新后请改版本号」) |
.gitattributes |
统一用 LF,避免 Windows 上自动转 CRLF 产生无意义 diff |
.github/workflows/deploy.yml |
GitHub Pages 自动部署 |
GitHub Pages 给静态资源发的是 Cache-Control: max-age=14400(4 小时)强缓存,
而且 URL 里没有文件指纹。所以改了 core.js / render.js / worker.js 之后,
老访客会在最长 4 小时内继续用旧脚本,表现就是「新页面 + 旧 worker」:
选了线描却还是出油画、风格一栏显示 undefined。
为此 index.html 里所有脚本引用都带 ?v= 查询串,由 build.js 的 OIL_BUILD 统一提供:
window.OIL_BUILD = '20261111-brushtrokes';每次改动这些脚本,就把 OIL_BUILD 换一个新值(如日期或递增序号),
浏览器会把它当成全新 URL 去取。worker.js 也会把这个查询串透传给 importScripts('core.js'),
保证 worker 和 core 版本一致。
生成完成时页面会校验 result.style 是否等于用户选的风格——旧版 worker 不认识
style 字段,不匹配就明确报错提示强制刷新,不会静默输出一张油画糊弄过去。
分析一次,八种风格共用。 先在降采样后的工作分辨率上算一张「结构图」:
- 分层:在 L*(亮度)× S(饱和度)平面上做 k-means(k-means++ 初始化),把画面按材质和明暗分开
- 边缘:两套 Canny(粗 σ=3.0 出主要轮廓,细 σ=1.2 出细节边缘),双阈值 + 滞后连接
- 方向场:结构张量(Sobel → 特征分解)求每个像素的主导走向;平坦区改用低频方向场,避免笔触乱指
- 显著性:梯度幅值 + 方向一致性 + 细边缘密度加权,告诉笔触「哪里值得多画几笔」
八种风格从同一张结构图出发,各取所需:
- 油画:网格播种 → 沿方向场行走 → 按显著性分配笔宽和密度,十道工序逐层加细
- 线描:种子只撒在 Canny 脊线上,行走时向法线方向吸附(
requireEdge),走偏即止;用maxTurn限制转角,防止笔画打卷成毛线团 - 色块:对每个分层跑 marching squares 抽闭合轮廓,用 RDP 简化(闭合轮廓要在离起点最远处切成两条开链,否则首尾重合会让 RDP 退化),最后用
evenodd填充规则一次平涂 - 素描:按局部亮度分四遍排线,
rot90让交叉线垂直正交;石墨色按原图明暗映射,亮部再用高亮笔擦回纸白 - 水墨:墨色不用纯黑而带暖调,浓淡靠同一色相不同透明度叠出来;笔触宽而短像墨团化开,皴擦刻意压到最疏
- 点彩:六边形错排撒点,每个点在原色附近做色相抖动靠眼睛混色;暗部点自动放大,因为纸是白的,暗处点太小会从缝里透出白点像噪点
- 霓虹:
lighter加色混合做辉光,但主体走正常混合——密集区域纯靠加色会亮度累加到饱和成白纸;亮处目标亮度用正弦收一点,避免高光区糊成一片 - 像素:先铺大格再补细格,每通道量化到 5 档做调色板感;格边长由「细腻程度」控制
笔触类型:render.js 里定义了 8 种笔触形态(NORMAL 普通、GLAZE 罩染、
SCUMBLE 干扫、IMPASTO 厚涂、FILL 闭合填充、DOT 圆点、GLOW 发光、PIXEL 方块),
各风格按需选用。
严格串行的时间轴:start[i] == start[i-1] + dur[i-1]。每道工序按权重分配时长,
工序内按 0.0035 + 0.0010×弧长 + 0.0006×笔宽(色块额外按面积开方)分配每笔时长,然后逐条校验零重叠零断点。
颜色取自原图:沿笔触取 5 个采样点的中位色,只做轻微抖动,绝不用调色板。 色块风格例外——它按设计就是要用量化后的限量配色。
网页与播放器共用渲染器:render.js 被 index.html 和下载的单文件播放器共同引用,
所以页面里看到的效果和下载后播放的完全一致。
笔触保底机制:大笔触风格(油画 / 线描 / 素描)有总量预算(约 13 万 / 10 万 / 9 万条), 超出时按工序权重等比稀释,保持各阶段比例而不是砍掉某一层。这样把「细腻程度」 拉到极端值时数据量不会失控(油画最细档原本 22.7MB,现在约 12.5MB,画质无可察觉损失)。
另外批量拼接笔触用的是分块追加而不是 arr.push(...big)——后者在笔触数量大时
会超出引擎的参数栈上限,抛 Maximum call stack size exceeded。
仓库本身就是 Pages 站点,main 分支 + GitHub Actions 自动部署,地址是
https://tyuecode.github.io/。
要绑自定义域名:在仓库 Settings → Pages → Custom domain 里填域名,
GitHub 会自动往仓库写一个 CNAME 文件(不用手动建)。另外还需要在你的 DNS
那边加一条 CNAME 记录指向 tyuecode.github.io。
注意:如果仓库里有 CNAME 文件、但 Pages 设置里的域名为空,两者会打架,
访问可能解析失败。换域名或撤域名时,建议两边一起改。
其他静态托管(Cloudflare Pages / Netlify / Vercel)同样适用——几个静态文件,无构建步骤。
python -m http.server 8000
# 访问 http://localhost:8000/注意:不要用
file://直接双击index.html,Web Worker 和 fetch 会被浏览器拦截。
MIT