图像与设计
需要制作 ASCII 动画、终端/CLI 开场或加载器…
在浏览器、终端或 Three.js 场景中,把亮度映射为字符并按帧刷新,制作轻量、复古的 ASCII 动画与视觉。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
需要制作 ASCII 动画、终端/CLI通用
解决思路
- 用文本字符渲染运动:在浏览器 `<pre>`/canvas、终端 ANSI、或 Three.js `AsciiEffect` 上输出 ASCII;输出极轻量、有辨识度,适合复古/黑客风格。
- 核心:建亮度 ramp,把 0..1 luminance 映射到墨色密度匹配的字符;字符按 dark-to-light 排序;索引用 `Math.round(lum * (ramp.length - 1))`。
- 常用 ramp:短 10 级 ` .:-=+*#%@`;中 16 级 ` .'\`^"`,:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$`;标准 70 级 Paul Bourke(照片最佳)`` $@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,"^`'. ``;浅底深字时反向使用。
- 从 sRGB 计算感知相对亮度:`const lum = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; // 0..1`。
- 在浅色背景上画深色文字时反转:`lum = 1 - lum`。
- 处理字符单元纵横比(第一大坑):等宽字符 cell 大约宽:高 = 0.5;用正方形像素网格采样会垂直拉伸。修正为采样更少行:目标 `cols` 字符宽时,`rows = Math.round(cols * (imgH / imgW) * fontAspect)`,`fontAspect ≈ 0.5`;等价地,离屏 canvas 高度设为 `cols * aspect * 0.5`。
- Web 渲染选 `<pre>` + `textContent`:最简单;一个字符串每行加 `\n`;约 120×60 字符、30fps 以内可用;设置 `white-space: pre; font-family: monospace; line-height: 1;`。
- Web 渲染选 canvas `fillText`:需要逐字符颜色、更大网格或 60fps 时用;每个字符画在 `x * cellW, y * cellH`;比成千上万 DOM 节点快。
- 生成式 `<pre>` plasma:用 `requestAnimationFrame`;遍历 `COLS`/`ROWS`;组合 `Math.sin(x * 0.2 + t * 0.001)`、`Math.sin(y * 0.3 + t * 0.0013)`、`Math.sin((x + y) * 0.15 + t * 0.0007)` 得 `v`;用 `(v + 3) / 6` 归一化到 0..1;按 ramp 取字符;每行末尾加 `\n`;`pre.textContent = out`。
- 图片/视频转 ASCII:把源画到 `cols × rows` 的小离屏 canvas,用 `getImageData` 读像素,再逐像素映射为字符;视频则每帧从 `<video>` 重复。
- 视频转 ASCII 实现:计算 `aspect = video.videoHeight / video.videoWidth`;`rows = Math.round(cols * aspect * 0.5)`;创建 canvas `width = cols, height = rows`;用 `getContext('2d', { willReadFrequently: true })`;每帧 `ctx.drawImage(video, 0, 0, cols, rows)`,读取 `getImageData`,按 `i += 4` 遍历 data,用 `lum = (0.2126*data[i] + 0.7152*data[i+1] + 0.0722*data[i+2]) / 255`,`out += ramp[Math.round(lum * (ramp.length - 1))]`;每 `cols` 个像素后加 `\n`;写回 `pre.textContent`;再 `requestAnimationFrame(tick)`。
- Node 图片转 ASCII:运行 `scripts/img-to-ascii.mjs`,用 `--cols`、`--invert`、`--ramp` 参数把 PNG/JPG 转为 ASCII 文本。
- 3D 转 ASCII:用 Three.js `AsciiEffect` 包裹 renderer,把任意 scene 渲染为 ASCII 到 DOM 元素;必须调用 `effect.render(scene, camera)`,而不是 `renderer.render`。
- Three.js 接线:`import { AsciiEffect } from 'three/addons/effects/AsciiEffect.js';`;`const effect = new AsciiEffect(renderer, ' .:-=+*#%@', { invert: true });`;`effect.setSize(innerWidth, innerHeight);`;`effect.domElement.style.color = '#0f0';`;`effect.domElement.style.backgroundColor = 'black';`;`document.body.appendChild(effect.domElement);`;循环中 `effect.render(scene, camera);`。
- 终端/CLI 动画:用 ANSI escape code 循环;隐藏光标、移动 home、打印帧、节流 12–24fps;退出时恢复光标。
- 终端实现:`const ESC = '\x1b[';`;`process.stdout.write(ESC + '?25l')` 隐藏光标;`function frame(t) { process.stdout.write(ESC + 'H'); /* build and write rows... */ }`;`const id = setInterval(() => frame(Date.now()), 1000 / 20);`;`process.on('SIGINT', () => { clearInterval(id); process.stdout.write(ESC + '?25h' + ESC + '2J'); process.exit(); });`。
- ANSI 细节:用 `ESC + '2J'` 清整屏,用 `ESC + 'H'` 回 home(每帧比清屏便宜);用 `\x1b[38;2;R;G;Bm` 真彩色,`\x1b[0m` 重置。
- 交付独立 HTML:自包含 Web ASCII 作品(生成场、图片/视频→ASCII、`AsciiEffect` 场景)交付一个可直接在浏览器打开的 HTML 文件;`<pre>`/canvas、ramp、rAF 循环内联;若用 Three.js 从 CDN 引入;无构建步骤;不要上 bundler;终端/CLI 作品用捕获 stdout 或截终端图验证。
- 输出契约:一个 `.html` 文件包含渲染目标、亮度 ramp 和一个内联 `<script>` 中的 `requestAnimationFrame` 循环。
- 可复现帧:帧时间必须来自可注入的 time(不要直接用 `Date.now()`/`performance.now()`),并给随机数固定 seed,使单帧可复现。
- Seek harness:用 `?t=N` 在模拟时间 `N` 只渲染一帧而不是循环,使截图可复现;把 `N` 喂给循环读取时间的地方并固定 seed。
- Seek harness 实现:`const t = new URLSearchParams(location.search).get("t");`;`frame(time)` 读取 `time` 而非 `Date.now()`;RNG 用固定 seed;`if (t !== null) { frame(parseFloat(t)); } else { (function loop(now){ frame(now); requestAnimationFrame(loop); })(0); }`;`window.__ready = true;`。
- 验证循环:在多个模拟时间渲染(`?t=0`、`?t=1000`、`?t=2000`),分别截图,检查保真度(ramp dark→light 正确、运动有演化)和伪影(缺少 cell-aspect 修正导致垂直拉伸、浅底反色错误、网格裁切、等宽字体加载前的 FOUC;字体决定 cell 尺寸,所以要等待)。
- Headless 截图:任意 headless 工具均可,例如 `npx playwright screenshot --wait-for-timeout=500 "file://$PWD/ascii.html?t=1000" frame-mid.png`。
- 完成前检查:能独立打开——无 console error、CDN(若用 Three.js)加载、monospace 字体生效。
- 完成前检查:`?t=N` 渲染一个确定性单帧(注入时间 + 固定 seed),没有 live loop。
- 完成前检查:在 3 个模拟时间截图——符合需求,无垂直拉伸或反色错误。
- 完成前检查:尊重 `prefers-reduced-motion`——停止/减慢 rAF 循环,显示静态帧。
- 完成前检查:缓动是有意的——帧率有意图地节流(复古感用 12–24fps),ramp 排序有意设计。
- 快速参考:简单生成场用 `<pre>` + `textContent`,sine/noise → ramp。
- 快速参考:照片保真用 70 级 Bourke ramp,luminance 用 sRGB 权重。
- 快速参考:逐字符颜色/60fps 用 canvas `fillText` per cell。
- 快速参考:视频用 `<video>` → offscreen canvas → 每帧 `getImageData`。
- 快速参考:3D 场景用 Three.js `AsciiEffect.render(scene, camera)`。
- 快速参考:终端用 ANSI `\x1b[H` home + 节流 12–24fps,隐藏光标。
- 快速参考:纵横比修正 `rows = cols * imgAspect * 0.5`。
- 参考文件:`references/rendering.md` 含完整 ramp 表(10/70/extended)、像素采样数学与 cell aspect correction、`<pre>` vs canvas 取舍与代码、ANSI 终端帧循环与 truecolor、完整 `AsciiEffect` 接线。
- 参考文件:`scripts/img-to-ascii.mjs` 是可运行 Node 脚本,把 PNG/JPG 转 ASCII 文本,支持 `--cols`、`--invert`、`--ramp`。
- 打包辅助脚本:`scripts/seek-shot.sh anim.html 0 1.5 3` 冻结 `?t=N` harness 并截取每个时刻;`scripts/contact-sheet.sh sheet.png frame-*.png` 平铺截图以便一眼审阅;见 `scripts/README.md`。
适用边界 · 注意事项
- 需要高保真图像、精确颜色或清晰可读的细小文字时,ASCII 会丢失细节,不适用。
- 终端环境不支持 ANSI 转义码或不能控制光标时,终端动画方案不适用。
- 目标环境已适合用常规图形/文本渲染且不需要字符美学时,不必引入 ASCII 的复杂度。