终端显示二维码或图片,本质上是在用字符(包括空格、字母、符号)或像素块来“画”出图形。由于传统命令行是纯文本设备,无法直接渲染图片文件,所以需要将图片的像素信息映射为终端能理解的表现形式。
一、两种主流的实现原理
1. 字符画(ASCII Art)
这是最古老且兼容性最好的方式。原理是将图片降采样(降低分辨率),把每个小区域的像素灰度值映射为一个字符(比如 @ 表示深色,. 表示浅色)。
- 特点:在任何终端(包括最老的黑白终端)都能显示,但细节丢失严重,看起来像由字符组成的马赛克。
- 常见应用:
cowsay命令、早期 BBS 的欢迎界面。
2. 块元素与半角字符
比纯字符画更精细。利用 Unicode 字符集中的块元素(如 █、▄、▀)或半角字符(如 ▌、▐)来构建图像。
- 原理:利用
▄(下半块)和▀(上半块)这样的字符,配合终端颜色,可以在一个字符位置内表现两种颜色,从而提升“分辨率”。 - 特点:比纯字母画清晰,但依然受限于字符网格。
3. 六分之一块字符(Sixel)
维基百科:Sixel
一种较新的终端图形协议。它将像素分组(通常 6x1 或 6x2),用特殊的控制序列告诉终端如何渲染这些小色块。
- 原理:通过转义序列(Escape Sequence)传输图形数据,终端解析后直接绘制。
- 特点:支持颜色和更高分辨率,但需要终端支持(如 iTerm2、WezTerm)。
4. 现代终端图形协议(Kitty / iTerm2)
现代终端(如 Kitty、iTerm2、WezTerm)支持更高级的图形协议。
- 原理:通过特定的转义序列,直接将图片数据(PNG/JPEG)传输给终端,由终端原生渲染。
- 特点:最清晰,能显示真彩图片,甚至支持动画。这是目前显示二维码最理想的方式。
二、二维码在终端的具体实现
终端显示二维码通常采用字符画或块元素方式,因为二维码本身就是二值化(黑白)图像,非常适合用字符模拟。
技术细节
- 二值化:将二维码图片转换为纯黑白。
- 降采样:将二维码的模块(Module)映射到终端的字符网格。通常一个二维码模块对应终端的一个字符位置(或半个字符位置)。
- 映射规则:
- 黑色模块 →
██或## - 白色模块 →
(两个空格) - 或者使用
▄、▀等块字符来提升密度。
- 黑色模块 →
为什么能扫出来?
- 二维码的容错能力很强。只要模块的相对位置和Finder Pattern(三个角上的回字框)大致正确,扫码软件就能识别。
- 终端显示的“粗糙”二维码,在扫码摄像头看来,依然是清晰的黑白块结构。
Python 实现
- 生成二维码矩阵:使用
qrcode库生成一个二值矩阵。 - 遍历矩阵:逐行读取矩阵中的 0 和 1。
- 输出映射:
- 遇到 1(黑色):打印
██ - 遇到 0(白色):打印
(两个空格)
- 遇到 1(黑色):打印
- 换行:每行结束打印
\n。
代码示例(Python)
import qrcode
# 生成二维码矩阵
qr = qrcode.QRCode()
qr.add_data("https://yuanbao.ai")
matrix = qr.get_matrix()
# 终端渲染
for row in matrix:
line = ""
for cell in row:
line += "██" if cell else " "
print(line)
三、关键支撑技术:转义序列
无论哪种方式,核心都是ANSI/VT 转义序列(Escape Sequences)。这是终端与应用程序之间的“暗号”。
- 控制颜色:
\033[31m表示设置前景色为红色。 - 控制光标:
\033[2J表示清屏。 - 显示图片:现代协议通过
\033]1337;File=...这样的序列传输图片数据。
简单来说:程序向终端输出一串包含控制码的文本,终端解析这些控制码,决定在屏幕的哪个位置显示什么字符或颜色,从而“拼”出图像。