什么是字符画

字符画,一种由字母、标点、汉字或其他字符组成的图画。简单的字符画是利用字符的形状代替图画的线条来构成简单的人物、事物等形象。
摘自百度百科

我的理解是,将像素点的rgb转化为灰度,再将灰度值映射到某个字符集合(集合不唯一,可以自己定义,只要相互的形态对比度和灰度相符合就行了)

目标

利用前端技术,具体说是canvas画布。实现下图的效果,将播放的视频动态的转化为对应的动态字符画。
这里是效果预览(bilibili源)

1.0

canvas实现

  1. 利用canvas从视频中间隔取样
  2. 得到的取样(图片)信息转化为字符串
  3. 字符串插入到textarea
  4. 重复123步骤,即动态字符画

完整的代码
主要的javascript代码(部分html和css不太重要):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 获取video 和 canvas 的引用
let video = document.getElementById('v');
let canvas = document.getElementById('c');
let textArea = document.getElementById('text-area');

// 获取canvas 2D上下文对象
let ctx = canvas.getContext('2d');

let vStyleWidth;
let vStyleHeight;

video.onloadedmetadata = function () {
canvas.width = video.videoWidth; // 视频宽度
canvas.height = video.videoHeight; // 视频高度
vStyleWidth = canvas.width;
vStyleHeight = canvas.height;
}

// 根据 rgb 值得到灰度值
let getGray = (r, g, b) => {
return 0.299 * r + 0.578 * g + 0.114 * b; // 灰度值的浮点算法
}

// 灰度值映射到字符
let mapToChar = (grayIndex) => {
const d = 16;
let index = Math.floor(grayIndex / d);
const chars = ['@', 'W', '#', '$', 'O', 'E', 'X', 'C', '[', '(', '/', '?', '=', '^', '~', '_', '.', '`'];
return chars[index];
}

// 填充到textarea
let image2char = (ctx, width, height) => {
// 获取canvas上的图像信息
let imageData = ctx.getImageData(0, 0, width, height);
let imageDataArr = imageData.data; // 图像信息数组
let imgDataWidth = imageData.width; // 矩阵纬度
let imgDataHeight = imageData.height;
let innerContext = '';
let p = document.createElement('p');
for (let h = 0; h < imgDataHeight; h += 10) {
for (let w = 0; w < imgDataWidth; w += 6) {
let index = (w + imgDataWidth * h) * 4; // r b g a = 4个宽度
let r = imageDataArr[index + 0];
let g = imageDataArr[index + 1];
let b = imageDataArr[index + 2];
const gray = getGray(r, g, b); // 得到灰度值
innerContext += `${mapToChar(gray)}`; // 灰度值映射到字符
}
innerContext += '\n';
}
textArea.value = innerContext;
}


let animationHook; // 暂停hook
let draw = () => {
animationHook = requestAnimationFrame(draw);
// 从视频等间隔取样
ctx.drawImage(video, 0, 0, vStyleWidth, vStyleHeight);
// 生成的字符串输出到textarea
image2char(ctx, vStyleWidth, vStyleHeight);
}

video.addEventListener('play', () => {
draw();
}, false);
video.addEventListener('pause', () => {
window.cancelAnimationFrame(animationHook);
}, false);
video.addEventListener('ended', () => {
window.clearInterval(animationHook);
}, false);

注意

  1. rgb转灰度算法,又去百度百科
  2. 注意字符集合中字符的相互的形态对比度
  3. 利用Image.data获取图片的像素信息