0. 背景
春节期间,小伙伴分享了一个视频并实名吐槽我……
作为一名敬 (kǔ) 业 (bī) 的码农,这个有意思的玩意儿那是必须得了解一下的,经过奇奇怪怪的搜索找到原来这玩意儿叫 ASCII art,网上已经有很多关于 ASCII art 的文章,然后经历了各种查资料 + 瞎折腾,于是就有了这篇笔记,本文仅记录一下图片的玩法,视频暂不涉及 (~ ̄▽ ̄)~。
我们现在常见的一些 ASCII art 实例:
1. 简介
ASCII艺术是一种图形设计技术,它使用计算机进行演示,由 1963 年 ASCII 标准定义的95个可打印(总共 128 个)字符和具有专有扩展字符的 ASCII 兼容字符集(超过 128个 的标准 7 位 ASCII 字符)拼接而成。该术语也通常用于指代基于文本的视觉艺术。ASCII艺术可以使用任何文本编辑器创建,并且通常与自由格式语言一起使用。大多数 ASCII 艺术示例都需要固定宽度的字体(非传统打字机上的非比例字体)。
最古老的 ASCII 艺术案例是当时为贝尔实验室工作的计算机艺术先驱肯尼斯·诺尔顿于1966年左右创造的。
在很大程度上,发明 ASCII 艺术是因为早期的打印机通常缺乏图形能力,因此使用字符代替图形标记。此外,为了标记来自不同用户的不同打印作业之间的划分,批量打印机通常使用 ASCII 艺术来打印大海报,使得划分更容易,从而可以更容易地由计算机操作员分离结果。早期的电子邮件中无法嵌入图片也使用了 ASCII 艺术。
上面几段直接摘抄自维基百科 -_-|| 简而言之,ASCII art 就是用 ASCII 字符绘制一幅图画,这里就不赘述了。下面主要记录一下图像转成 ASCII 文本的原理和 JavaScript 的实现方式。
2. 原理
位图是由像素点组成的,每个像素点用 RGB 值表示,一幅图像的颜色数据是由一个 RGB 像素点矩阵构成,也即对应 R、G、B 三个颜色分量矩阵。ASCII art 是由字符粗略表示颜色的二维形式,三个颜色值矩阵无法直接表示一个二维数组,所以要对图像进行灰度化。
2.1 灰度值和灰度化
在计算机领域中,灰度(Gray scale)数字图像是每个像素只有一个采样颜色的图像。这类图像通常显示为从最暗黑色到最亮的白色的灰度,值一般为 0-255。
在图像处理中,图像的灰度化的就是把由 RGB 三通道的数据的彩色图像变为单通道的数据的灰度图像,在 RGB 模型中如果 R=G=B 时,则彩色表示一种灰度颜色,灰度值就是 R、G、B 的值,即三者相等时只需要存储该灰度值,而对于 R、G、B 不同的像素值也可以进行灰度化。
根据重要性及其它指标,将三个分量以不同的权值进行加权平均。由于人眼对绿色的敏感最高,对蓝色敏感最低,因此,按下式对 RGB 三分量进行加权平均能得到较合理的灰度图像。
2.2 “灰度”字符
我们可以用字符表示不同的灰度,比如 0 用 @
表示,255 用 (空格)表示等等,分别代表颜色的深浅;当然没必要用 256 个字符表示 256 个灰度等级也不容易找齐这么多字符,所以设置灰度范围对应一个字符,比如 0~24 这 25 个灰度都用
@
表示,以此类推。
2.3 像素精度
假设一张图片的像素是 800*600,则像素点有 480000 个,对于一个 ASCII art 来说全部转为字符其实没有必要(当然要全部转掉也是可以的),所以可以设置像素精度,比如每 10*10 个像素点用一个字符表示。
“近看爱因斯坦远看梦露”这张图挺有意思,人眼看 ASCII art 效果其实和这张图类似。
3. 实现
这次用了 nodejs 来尝试一下,获取图片像素信息用了get-pixels 这个库(get-pixels 支持 PNG、JPEG、GIF 格式)。因为存在动画 GIF,所以这里实现暂时只支持 PNG 和 JPEG 格式的图片,也还没有具体研究图片的数据结构 0.0,步骤其实很简单:
读取图片文件,获取图片宽高和像素点数据
构造 灰度/字符 哈希表,设置灰度精度
1
2
3
4
5
6
7
8
9
10
11
12
13
14function getCharMap() {
const chars = [].slice.call(arguments);
const grayScaleRange = 256;
let grayScaleBlockCount = ~~(grayScaleRange / chars.length);
if (grayScaleRange % chars.length !== 0) {
grayScaleBlockCount += 1;
}
const map = {};
for (let i = 0; i < grayScaleRange; i++) {
map[i] = chars[~~(i / grayScaleBlockCount)];
}
return map;
}
const CHAR_MAP = getCharMap('@', '#', '$', '=', '*', '!', ';', ':', '~', '-', ',', '.', ' ');设置 ASCII art 矩阵精度
转换图片像素数据
- 根据矩阵精度设置矩阵块
- 计算每个矩阵块的平均灰度
1
2
3
4
5
6
7
8
9
10
11
12
13function getGrayScale(r, g, b) {
return ~~(0.3 * r + 0.59 * g + 0.11 * b);
}
function getMatrixAverageGrayScale(x, y, width, height, pixels) {
let grayScaleAmount = 0;
for (let h = 0; h < height; h++) {
for (let w = 0; w < width; w++) {
let [r, g, b] = [0, 1, 2].map(index => pixels.get(x + w, y + h, index));
grayScaleAmount += getGrayScale(r, g, b);
}
}
return ~~(grayScaleAmount / (width * height));
}输出字符矩阵文本
* 在 Web 显示设置等宽字体或字间距样式来防止“图片变形”
效果截图
具体实现代码在这里 ヾ(・ε・`*)