Back

浏览器和终端中的 ASCII 艺术

浏览器和终端中的 ASCII 艺术

这个想法很简单:获取一张图像,测量每个像素的亮度,然后用字符替换它。像 @# 这样密集的字符代表暗区,而像 . 这样较轻的字符代表亮区。将足够多的这些替换在网格中串联起来,你就能得到一个完全由文本组成的可识别图像。

这个核心思想同时支撑着终端中的 ASCII 艺术浏览器中的 ASCII 艺术,而现代实现已经将其推向了远超经典 7 位 ASCII 的境界。

核心要点

  • ASCII 艺术将像素亮度映射到排序字符调色板中的字符,其中像 @ 这样的密集字形代表暗区,像 . 这样的稀疏字形代表亮区
  • Unicode 块元素和盲文图案提供了比传统 ASCII 字符调色板显著更精细的分辨率
  • 浏览器实现依赖于 <canvas> 像素数据和等宽 CSS,而终端渲染器使用 ANSI 转义序列来实现颜色
  • 终端能力差异很大——从基本的 16 色 ANSI 到 24 位真彩色、SIXEL 以及 Kitty 图形协议

像素到字符的映射工作原理

每个 ASCII 渲染器都遵循大致相同的流程:

  1. 将源图像或视频帧采样到单元格网格中
  2. 计算每个单元格的平均亮度(luminance)
  3. 将该亮度值映射到排序调色板中的字符
  4. 输出生成的字符网格,可选择性地添加颜色

一个基本的 JavaScript 实现如下所示:

const palette = ' .:-=+*#%@';

function brightnessToChar(brightness) {
  const index = Math.floor((brightness / 255) * (palette.length - 1));
  return palette[index];
}

这里,brightness 预期是一个介于 0(黑色)和 255(白色)之间的值。该函数将其标准化为调色板字符串中的索引,其中较早的字符代表较浅的色调,较后的字符代表较深的色调。

输出质量在很大程度上取决于你的字符调色板。经典 ASCII 能提供大约 10-15 个有用的阴影级别。现代实现做得更好。

超越 ASCII:使用 Unicode 的文本模式图形

使用 Unicode 的文本模式图形解锁了显著更精细的分辨率。你可以使用以下字符,而不是稀疏的 ASCII 调色板:

  • 块元素(█ ▓ ▒ ░)为每个单元格提供四个阴影级别
  • 盲文图案(⣿ ⠿ ⠛)在单个字符中编码最多 8 个点,允许比传统 ASCII 调色板更高的有效分辨率
  • 制表符用于结构化布局和边框

Chafa 是一个终端图像查看器,它使用 Unicode 块和盲文字符在终端中以接近照片级的质量渲染图像。与纯 ASCII 相比,差异非常显著。

浏览器中的 ASCII 艺术:Canvas 和 WebGL 方法

基于浏览器的 JavaScript ASCII 渲染通常使用 <canvas> 元素作为图像源。流程如下:

  1. 将图像或视频帧绘制到离屏 canvas
  2. 使用 ctx.getImageData() 读取像素数据
  3. 采样像素网格并将每个单元格映射到字符
  4. 将结果渲染到 <pre> 元素中,或使用 fillText() 绘制回 canvas

p5.js 这样的库使这一过程变得简单,让你能够实时处理实时网络摄像头源或视频帧。为了获得更高的性能,WebGL 着色器可以在 GPU 上处理亮度采样和字符查找,这在以 30 fps 渲染完整视频时很重要。

对于静态显示,HTML/CSS 设置很简单但很重要:

.ascii-art {
  font-family: monospace;
  white-space: pre;
  line-height: 1;
  letter-spacing: 0;
}

没有 white-space: pre,浏览器会折叠空格并破坏布局。没有等宽字体,字符宽度不等,网格就会崩溃。设置 letter-spacing: 0 可以防止某些浏览器默认引入的细微水平间隙。

终端 ASCII 图形:ANSI 颜色和协议差异性

在终端环境中,ASCII 或 Unicode 字符被写入标准输出。通过 ANSI 转义序列添加颜色。现代终端支持 24 位(真彩色)输出:

printf '\033[38;2;255;100;0m%s\033[0m\n' "orange text"

这使用 38;2;R;G;B 序列将前景色设置为 RGB(255, 100, 0),然后使用 \033[0m 重置格式。

jp2aascii-image-converter 这样的工具使用这种方法从图像文件生成彩色终端 ASCII 图形。

然而,终端能力差异很大。一些终端仅支持基本的 16 色 ANSI。其他终端支持 256 色模式、完整的 24 位真彩色、SIXEL 图形或 Kitty 图形协议,后者可以在终端内渲染实际的像素图像,而不是字符近似。Unicode 宽度处理在不同终端之间也有所不同,因此盲文或宽字符可能会因环境而错位。

结论

无论你是为浏览器编写 JavaScript ASCII 渲染器,还是通过终端工具传输图像数据,底层逻辑都是相同的:像素变成字符,亮度变成密度,颜色变成转义码或 CSS。浏览器为你提供对字体和布局的精确控制,而终端则提供速度和可组合性。环境的选择决定了工具,但转换流程——采样、测量、映射、渲染——保持不变。

常见问题

大多数等宽字体字符的高度大于宽度,因此每个字符单元格不是完美的正方形。这种垂直拉伸会扭曲图像。你可以通过在 CSS 中调整 line-height、在转换前将源图像缩放为非正方形纵横比,或相对于列采样更少的行来进行补偿。

按视觉密度从浅到深对字符进行排序。更长的调色板可以提供更多的阴影级别和更平滑的渐变。通过渲染渐变图像并检查可见的条带来测试你的调色板。Unicode 块元素和盲文图案比标准 ASCII 字符提供更精细的粒度。

可以。在浏览器中,将每个视频帧绘制到离屏 canvas,读取像素数据,并在每个动画帧上将单元格映射到字符。为了在 30 fps 下获得可接受的性能,保持字符网格分辨率适中,或将亮度计算卸载到 WebGL 着色器。像 Chafa 这样的终端工具也支持视频输入。

盲文字符是 Unicode,其渲染宽度取决于终端模拟器和活动字体。一些终端将它们视为宽字符,其他终端则视为窄字符。如果你的输出看起来有问题,请尝试使用不同的终端或字体。像 Chafa 这样的工具会检测终端能力并相应地调整其输出模式。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay