Python+Pillow实现cua命令行字符画工具的完整代码

 更新时间:2026年11月08日 10:20:19   作者:菩提风  
这篇文章主要为大家详细介绍了如何基于Python与Pillow实现的命令行字符画工具cua,涵盖图片缩放与宽高比校正,灰度转换等内容,感兴趣的小编可以了解下

我最早拿到“cua”这个标题的时候,其实愣了一下。它太短了,短到没有任何上下文,但又是那种很适合做命令行工具的名字:三个字母、发音干脆、看起来像个缩写。于是我把这个标题落地成了一个真实可跑的项目——一个把图片转成终端字符画的命令行工具,名字就叫 cua。全称我随手定为 Character Unicode Art,既能解释缩写来源,听起来也不算牵强。

说实话,字符画这个方向一点都不新鲜,老玩家可能都玩过用 ASCII 字符拼出图像的网站或脚本。但真正自己动手写一个完整的、支持真彩色的命令行版本,和想象中完全不是一回事:里面涉及灰度公式、字符密度表、终端宽高比、ANSI 转义序列,还有一堆跨平台编码的坑。这篇文章就把我完整实现 cua 的过程、核心原理、踩过的坑都写出来,代码贴上,步骤拆开,保证你跟着做也能跑出一个属于自己的字符画工具。

适合看这篇文章的人,大概有这么几类:想在终端里搞点视觉输出但不知道从哪入手的命令行爱好者;刚学 Python 想拿一个真实项目练手的同学;还有那些已经在用图像处理库(比如 Pillow)但没想过还能这样玩的人。内容不会太难,但最后做出来的东西挺有意思。

1. 内容整体设计与思路拆解

1.1 cua 到底要做什么

在动手写代码之前,我需要先把自己要做的事描述清楚。cua 的核心功能并不复杂:输入一张图片,输出一段文本,这段文本在终端里看起来像原图。就这么简单。但“看起来像”三个字背后包含了很多细节。

举个例子,我随便找了一张 1920x1080 的彩色 图片,如果直接把它转成终端输出,那就有 1920 列,任何一个终端都放不下。即便强制压缩成 80 列宽,1080 行的高度也远远超出屏幕范围,所以必须同时处理宽度和高度。而且终端里每个字符占据的空间是矩形的,不是正方形的,如果不做宽高比校正,图片就会被拉得很奇怪——这块我在很多现成脚本里都见过,缩放时只算了宽度,没算高度,导致输出的字符画像一个被压扁的面饼。

除了尺寸问题,颜色也是一个必须考虑的点。最简单的字符画是把图片转成灰度,然后用不同字符的疏密程度来模拟明暗。但如果只是灰度,那张图的视觉信息会损失很大。现代终端大多支持真彩色(24 位颜色),完全可以在输出时把图片的 RGB 值直接通过 ANSI 转义序列传给终端,让字符自带颜色。cua 的设计目标从一开始就包含了两种模式:灰度模式和彩色模式,不能让用户只能玩黑白。

再往下拆,还有一些功能层面的期望:支持调整输出宽度、支持反转灰度映射、支持透明背景处理、性能够看。最后一点容易被忽略,很多人写脚本处理一张 4000x3000 的图,转个字符画要卡好几秒,体验很差,原因基本是没做像素总量限制。

1.2 方案选型:为什么是 Python + Pillow

选什么语言和库来实现 cua,我基本没有纠结过。Python 一直是做这类快速工具的首选,原因有两个:一是 Pillow 库对图片读写的支持非常成熟,几十行代码就能完成图片打开、缩放、灰度转换、像素遍历这些操作;二是 Python 脚本天然跨平台,在 Windows、macOS、Linux 的终端里都能跑,最多就是编码上做点处理。

这里要特别说一句,实现字符画有三条常见技术路线。第一条是纯前端路线,用 Canvas 拿到图片像素数据,在浏览器里渲染成 HTML 字符,好处是交互性强,但和“命令行工具”这个定位不符。第二条是 C/C++ 或 Rust 路线,性能确实更好,但开发和调试周期长,对一个练手项目来说有点杀鸡用牛刀。第三条就是 Python 路线,开发快、可读性好,还能顺手把 Pillow 的图像处理能力用起来,cua 选的就是这条路线。

如果你只是想在极短时间内跑出一个效果,也可以直接去装现成的字符画工具,比如 jp2a、chafa 这些。但自己写一遍的意义在于:你会真正理解字符映射的运作方式,并且能按自己的需求定制输出效果。chafa 功能很强,但它的代码是 C 写的,读起来门槛不低;cua 用 Python 写,整个核心逻辑一百行以内,任何人打开代码都能看懂。

1.3 设计上的两个关键决策

第一个决策是支持 Unicode 半角字符而不是只用传统 ASCII 字符。传统 ASCII 字符画喜欢用 M , @ , # , = , + , - , : , . , 这组字符,只用了 9 到 10 个等级。如果再加上 Unicode 中的块状字符(Block Elements),比如 █ , ▓ , ▒ , ░ ,灰度层次会丰富很多,特别是在显示人物、风景这类图片时,过渡会更细腻。代价是某些老字号终端可能不支持这些字符的渲染,但实测下来,Windows Terminal、iTerm2、GNOME Terminal 都没有问题。

第二个决策是输出彩色模式时不丢弃灰度信息。很多人实现彩色字符画时,只是简单地把每个字符涂成原像素的颜色,这样确实有颜色,但字符本身几乎都用了同一个密度等级,结果就是一团花里胡哨的模糊色块,根本看不清轮廓。我的做法是:用像素的亮度(灰度值)决定字符密度,用像素的 RGB 值决定字符颜色。这样颜色和轮廓都能保留,效果比单纯的彩色版好不少。

2. 核心细节解析与实操要点

2.1 图片缩放与宽高比校正

图片转字符画的第一步,绝不是直接遍历像素,而是先把图片缩放到一个适合终端展示的尺寸。这个过程有两个关键点:宽度怎么定,高度怎么算。

宽度本质上取决于终端能显示多少个字符。老的终端默认 80 列,现代终端设置成全屏后普遍能到 180 到 240 列。cua 的做法是让用户通过 -w 参数指定目标宽度,默认值是 120。这个数值在绝大多数终端里都能完整显示,而且字符画不会因为字符太多变得过于细碎。

高度就涉及到宽高比问题了。终端里一个字符的显示区域,宽度和高度并不相等。常见字体下,一个字符的宽度大约是高度的 0.4 到 0.6 倍,换句话说字符是“竖长”的。如果直接把图片按像素等比缩小,比如把 1920x1080 缩到 120 列宽,那高度就是 67.5 行,但每个字符实际占的显示区域是宽度窄、高度高,导致整体视觉效果被纵向拉伸。

解决办法很简单:在计算目标高度时,把像素高度乘上一个压缩系数。cua 里默认系数是 0.5,也就是 target_height = int(target_width * height / width * 0.5) 。这个系数的含义大致是“终端字符的高宽比约为 2:1,所以高度减半”。但这只是一个经验值,如果用户用的是非等宽字体或者终端行距特别大,还可以通过 -r 参数手动调整,后面再细说。

还有一个不太起眼但直接影响输出质量的细节:缩放算法。Pillow 的 resize 方法默认使用 BICUBIC 插值,这个在大多数情况下是合适的选择。图片尺寸远大于目标尺寸时,用 LANCZOS 效果更好,但计算量稍大;图片尺寸小于目标尺寸时,任何插值都无法补出细节,最好的办法是提前提醒用户换一张大图。cua 默认用 LANCZOS,并且把图片质量放在第一位。

2.2 灰度转换公式与字符密度映射

把彩色 图片变成灰度图,第一步要搞清楚灰度值怎么算。很多人第一反应是 (R + G + B) / 3 ,也就是三个通道直接平均。这个算法简单是简单,但不符合人眼对颜色的感知:人眼对绿色最敏感,对蓝色最不敏感。如果用平均法,原本视觉上觉得挺亮的一块蓝色区域,灰度值算出来可能偏暗,输出效果不自然。

工业界和图像处理库普遍采用一个带权重的公式,最常见的是 ITU-R BT.601 标准:

Gray = 0.299 * R + 0.587 * G + 0.114 * B

这个公式把绿色权重放到最高,蓝色最低,和人眼的感知权重一致。Pillow 的 convert("L") 方法内部用的就是这个系数,所以直接用库函数就可以了,不用自己实现。

拿到灰度值之后,就到了字符映射环节。这一步的原理可以理解成:把灰度从 0(黑色)到 255(白色)的范围,划分成 N 个区间,每个区间对应一个字符。字符的选择有讲究,要按视觉密度排序。常见的字符密度序列是从密到疏:

"$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. "

这行字符串头部是 $ 和 @ ,视觉上铺得很满,感觉是“实心”的;尾部是空格,感觉是空白。cua 默认用了一组简化但成像质量足够的序列,并且允许用户按需替换。映射逻辑如下:灰度值 0 对应密度最大的字符(也就是视觉上最“黑”的那个),灰度值 255 对应空格。这样越暗的区域字符越密集,越亮的区域字符越稀疏,整体看起来就有明暗层次了。

如果你想把效果反转,也就是让暗区用稀疏字符、亮区用密集字符,也可以加一个 -i 参数,把映射索引翻转即可,代码上就是 index = len(chars) - 1 - index 的事。

2.3 ANSI 真彩色输出原理

灰度模式输出的只是纯文本,做起来快,但要谈到观赏性,必须上彩色。终端里的颜色显示依赖 ANSI 转义序列(ANSI escape code),它本质上是一段特殊的控制字符序列,终端收到之后不会直接显示文字,而是解析并执行相应动作,比如改变前景色、背景色、移动光标。

彩色字符画最关键的是设置前景色的转义序列。现代终端普遍支持 24 位真彩色,格式如下:

\x1b[38;2;R;G;Bm内容\x1b[0m

其中 \x1b 是 ESC 字符, 38;2 表示使用 RGB 方式设置前景色,后面依次放红、绿、蓝三个通道的值(0-255), m 结束控制序列。实际打印完成后,还需要用 \x1b[0m 重置所有样式,否则后面的字符也会带上颜色。

这可能听起来有点抽象,举个例子就清楚了。假设某个像素的 RGB 值是 (255, 128, 0),对应字符是 # ,那实际输出到终端的就是:

\x1b[38;2;255;128;0m#\x1b[0m

在终端里会显示成一个橙色的 # 字符。

cua 的彩色模式策略是上一节提到的组合方案:先用亮度决定字符密度,再用原始 RGB 决定颜色。这种做法的优势是:即使终端不支持真彩色,降级到 256 色或终端自带的 16 色时,字符的亮暗层次依然存在,至少也能看出轮廓,不至于完全垮掉。

这里有一个很容易踩的坑:如果图片是 RGBA 模式并且存在全透明的像素(比如一张带圆角的 PNG 图标),直接读像素值, R , G , B 可能是 0,打印出来就是一坨黑色字符。后面我专门写了一条问题排查,这里先预告一下。

3. 实操过程与核心环节实现

3.1 环境准备与工程结构

整个 cua 项目只有一个 Python 文件,加上一个最小化的依赖:Pillow。我假设你的机器上已经装了 Python 3.7 以上版本,因为后续代码里用了比较新的类型注解语法,如果你用的是更老的版本,把类型注解去掉也能跑。

安装依赖的命令很简单:

pip install pillow

如果你的 Python 环境里有多个版本,建议建一个虚拟环境来装,避免和系统其他包冲突。macOS 和 Linux 下直接跑 python3 -m venv venv 再激活即可,Windows 下命令略有不同,但思路一样。

项目目录结构我建议这样保留:

cua/
├── cua.py
├── demo.jpg
└── README.md

cua.py 是主程序,demo.jpg 是一张测试图片。整个项目核心代码在 120 行左右,完全不臃肿。

3.2 主程序代码实现

我直接把 cua.py 的完整代码贴出来,然后分段讲解关键部分。代码整体不复杂,但如果把参数解析、图像处理、终端输出逻辑混在一起,新手看起来还是会有点晕。所以我特意用函数把不同职责分开了。

#!/usr/bin/env python3
"""cua - Character Unicode Art, convert image to terminal character art."""

import argparse
from math import floor
from PIL import Image

# 字符密度从高到低
CHAR_DENSITY = " .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"

# 默认输出宽度字符数
DEFAULT_WIDTH = 120

# 终端字符宽高比补偿系数(经验值)
ASPECT_RATIO = 0.5


def resize_image(img: Image.Image, target_width: int, aspect_ratio: float) -> Image.Image:
    """按目标宽度缩放图像,并根据终端字符宽高比修正高度。"""
    width, height = img.size
    target_height = int(target_width * height / width * aspect_ratio)
    target_height = max(1, target_height)
    return img.resize((target_width, target_height), Image.LANCZOS)


def rgb_to_gray(r: int, g: int, b: int) -> int:
    """按人眼感知权重计算灰度值。"""
    return int(0.299 * r + 0.587 * g + 0.114 * b)


def gray_to_char(gray: int, invert: bool) -> str:
    """灰度值映射到字符。灰度 0(黑)对应密度最高的字符。"""
    max_index = len(CHAR_DENSITY) - 1
    index = int(gray / 255 * max_index)
    if invert:
        index = max_index - index
    index = max(0, min(index, max_index))
    return CHAR_DENSITY[index]


def image_to_ascii(
    img: Image.Image,
    target_width: int = DEFAULT_WIDTH,
    color: bool = False,
    invert: bool = False,
    aspect_ratio: float = ASPECT_RATIO,
) -> str:
    """将 PIL Image 转换为终端字符画字符串。"""
    img = resize_image(img, target_width, aspect_ratio)
    img = img.convert("RGBA")

    lines = []
    for y in range(img.height):
        line_parts = []
        for x in range(img.width):
            r, g, b, a = img.getpixel((x, y))
            # 处理透明像素:用白色背景模拟,避免黑色块
            if a < 128:
                r, g, b = 255, 255, 255
            gray = rgb_to_gray(r, g, b)
            ch = gray_to_char(gray, invert)
            if color:
                line_parts.append(f"\x1b[38;2;{r};{g};{b}m{ch}\x1b[0m")
            else:
                line_parts.append(ch)
        lines.append("".join(line_parts))
    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(description="cua - Character Unicode Art")
    parser.add_argument("image", help="输入图片路径")
    parser.add_argument("-w", "--width", type=int, default=DEFAULT_WIDTH,
                        help="输出宽度(字符数),默认 120")
    parser.add_argument("-c", "--color", action="store_true",
                        help="启用 ANSI 真彩色输出")
    parser.add_argument("-i", "--invert", action="store_true",
                        help="反转灰度到字符的映射")
    parser.add_argument("-r", "--ratio", type=float, default=ASPECT_RATIO,
                        help="终端字符宽高比补偿系数,默认 0.5")
    args = parser.parse_args()

    try:
        img = Image.open(args.image)
    except Exception as exc:
        print(f"无法打开图片: {exc}")
        return

    # 限制像素总量,避免超大图导致卡顿
    width, height = img.size
    if width * height > 20_000_000:
        print("警告: 图片像素总量超过 2000 万,可能处理较慢")
        # 直接先缩到一个可控范围
        ratio = (20_000_000 / (width * height)) ** 0.5
        img = img.resize((int(width * ratio), int(height * ratio)), Image.LANCZOS)

    output = image_to_ascii(img, args.width, args.color, args.invert, args.ratio)
    print(output)


if __name__ == "__main__":
    main()

3.3 关键函数与参数选择解析

代码贴上来了,重点讲几个容易出问题的环节。

resize_image 里的 aspect_ratio = 0.5 这句,是整个输出不变形的关键。前面讲过,终端字符高宽比大约 2:1,所以目标高度要打五折。但这个值不是绝对真理:它取决于你终端里当前的字体。比如我在 iTerm2 里用 Menlo 字体, 0.5 非常合适;换到等宽字体 Cascadia Code,行距变大,字符实际高度更高, 0.5 就会让画被“压扁”一点点。所以 cua 提供了 -r 参数,你可以边看边调,比如 0.55 、 0.45 ,直到图形看起来接近正方形。

rgb_to_gray 里用的三个权重系数是 BT.601 标准,这是图像处理领域的通用标准,不是我自己拍脑袋编的。如果你处理的图是纯色、对比度不高的,这个公式的视觉表现基本是最好的。 convert("RGBA") 这一步是必须的:有些图片(比如 GIF、PNG)可能没有 Alpha 通道,或者本来就能回归成 RGB 三通道,统一转成 RGBA 之后, getpixel 返回的元组就一定是四个元素,读取时不会出 ValueError 。

透明像素那一段也值得展开说。PNG 圆角图标很常见,四个角落的像素完全透明,但 RGB 值未必是 0。有的 PNG 透明像素的 RGB 就是 0,有的则是预乘了背景色,还有的 R、G、B 保留了原图的颜色信息但 A 为 0。如果我直接按原 RGB 输出,透明区域可能会显示成意外的颜色块。cua 的做法是把透明度低于 128 的像素统一当成白色处理,这样在黑色终端背景上显示成白色或浅色,视觉上最接近“透明区域应该看到终端背景”的效果。如果你更想让透明区域显示为纯黑,把 255 改成 0 就行。

main 函数里有一个像素总量限制的预处理。因为有人可能会拿一张 8000x6000 的超大图直接塞进来,如果不事先缩小,光 resize 到目标宽度其实还好,但如果用户忘了加 -w 参数, Image.open 之后到 resize 之前,整张大图的像素数据还没完全展开,真正的压力在后面。加一个 2000 万像素的判断,一旦超标就先等比缩到 2000 万像素以内,既能保证处理流畅,也不会让用户等待太久。

3.4 跑一个实际例子

找了一张 800x600 的风景图来测试,命令行输入:

python cua.py demo.jpg -w 100

这是灰度模式,输出宽度 100。因为默认高度约等于 100 * 600 / 800 * 0.5 = 37 行,屏幕能完整放下。整体风景轮廓、天空和地面的分界线都能看出来,但树叶、建筑的细节基本变成了一片噪点,这个现象正常,字符画本来就是舍弃细节、保留轮廓的艺术。

试试彩色模式:

python cua.py demo.jpg -w 100 -c

输出里多了许多带 ANSI 转义序列的字符,终端里看是彩色的。由于每个字符都带颜色,字符本身又保留了密度差异,整体效果比纯灰度版好很多。这里有个小结论:彩色模式下,即使字符密度选得不那么精确,画面也能通过颜色救回来不少,所以彩色模式的默认字符密度序列不需要太长。

如果是白色背景的终端,建议在图片加载后先做一个亮度反转,也就是 -i :

python cua.py demo.jpg -w 100 -i

否则白色背景下,空格(灰度最高)画出来的部分会全部变成背景的白色,亮部细节全丢了。这种调整虽然简单,但实际使用频率很高。

运行完这几个例子,你会发现字符画的效果和很多变量强相关:字体、终端背景色、字符密度序列、宽高比系数。这也是为什么我坚持要设置 -w 、 -r 、 -i 、 -c 这些可调节的参数,而不是写死一套配置。

4. 常见问题与排查技巧实录

4.1 Windows 终端输出乱码或中文变问号

这个坑几乎每个 Windows 上跑 Python 的脚本都会遇到。Windows 的命令行默认编码是 GBK(中文系统下),如果代码里输出了 ANSI 转义序列, \x1b 在 GBK 里可能会被吞掉或者解析异常,更常见的是输出带颜色的字符后,终端把后续字节按 GBK 解码,搞出一堆乱码。

解决方式有两个,任选一种即可。

第一种:在终端里手动切换到 UTF-8:

chcp 65001

然后再执行 python cua.py ... 。

第二种:在 Python 代码里强制标准输出流使用 UTF-8。在 main() 开头加这一段:

import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")

这段代码的作用是把标准输出重新包装成 UTF-8 编码的文本流。这样即便终端本身是 GBK,只要终端字体支持 Unicode 字符,输出基本不会乱。推荐用第二种,省心。另外,如果你用的是 Windows Terminal,它已经默认支持 UTF-8,这个问题其实会少很多。

4.2 输出的字符画变形明显,像被压扁或拉长

这个问题的根源就是前面反复提到的终端字符宽高比补偿系数。不同终端、不同字体、不同缩放比例下,字符的实际宽高比会变。

判断方法:先输出一个包含 @ 字符的 20x10 网格,用尺子比一比,或者直接目测一下行高和列宽。如果明显“扁”了(横行比较宽,竖列比较窄),说明高度偏小,把 -r 调大,比如 0.55 ;如果明显“瘦高”了,说明高度偏大,把 -r 调小,比如 0.45 。

另外,如果图片本身是横向构图的(比如 1920x1080),默认 0.5 系数基本没问题。但如果是竖构图(比如手机拍的 1080x1920),压缩后高度只有 目标宽度 * 1920 / 1080 * 0.5 ,行数会很多,如果你的终端窗口高度不够,画面会被截断。这时候有两个方向:降低宽度、增大压缩系数。或者干脆换一个思路,把竖图单独做一个适合窄终端展示的阈值,比如宽度设为 60 或者 70,一屏能完整看到整个画面。

有一个额外技巧:如果你的终端支持调整行距(line spacing),改小行距之后,字符的实际高度会变小,这时候可以适当把 -r 调回 0.45 甚至更小,画面会更精细。总之这个参数没有标准答案,多跑几次就有感觉了。

4.3 彩色模式下颜色不对或一片灰

这个问题的原因基本出在终端对 ANSI 真彩色转义序列的支持上。老一代终端(比如 Windows 自带的 conhost 旧版、部分远程终端的默认配色方案)只支持 16 色或 256 色,遇到 38;2;R;G;B 这种真彩色序列时不认,可能直接忽略样式,全部字符变成默认颜色,看起来就像“一片灰”。

解决办法有几种,按推荐排序:

  1. 换终端。Windows 用 Windows Terminal,macOS 用 iTerm2 或 VS Code 内置终端,Linux 用 GNOME Terminal、Konsole 或 Alacritty,这些终端都对真彩色支持得很好。
  2. 如果你的终端只支持 256 色,可以把输出改成 256 色模式。大致原理是,先把 RGB 映射到最近的 256 色调色板,再用 \x1b[38;5;Nm 输出。Pillow 或 colorama 库里都有现成的取整函数可以参考,不过这个优化不在 cua 默认代码里,需要自己扩展。
  3. 检查是否启用了终端主题的自定义 ANSI 颜色映射。有些终端(比如带有 color scheme 的 SSH 客户端)会把 ANSI 的 16 色重映射成自定义颜色,但这种重映射一般只影响 30-37 、 90-97 这些基础色号,不影响 38;2 真彩色。

如果换了终端还是不对,还有一个排查方向:确认你的输出没有在终端解析前被吞掉。某些日志重定向工具、管道命令(比如 python cua.py demo.jpg -c | less )会过滤掉转义序列。这种时候重定向到文件再 cat 出来,如果是正常颜色,说明问题出在管道那一段。

4.4 输出结果全是同样密度的字符,画面一片模糊

这个情况在调试时很常见。字符画显示模糊、没有层次,第一波怀疑对象应该是灰度映射。检查一下代码里 gray_to_char 的 index 计算:如果因为数据范围问题,所有像素的灰度都落在同一个区间,那就会出现一片字符全部相同。

具体来说,如果图片偏亮(比如大部分区域灰度值在 200 以上),映射到字符密度表的尾部(空格和 . ),确实会显得很淡;如果图片偏暗,又全部挤在 @ 、 # 这些高密度字符上,画面会黑糊糊一片。

解决办法是做一个简单的直方图拉伸(contrast stretching)。核心思路是统计图片灰度值的实际最小值和最大值,再把灰度范围线性映射到 0 到 255。这个转化在 Pillow 里很容易实现:

from PIL import ImageOps
img = ImageOps.autocontrast(img.convert("L"))

或者干脆只取灰度图,然后做 CLAHE 增强。不过对字符画来说, autocontrast 已经足够用。你可以在 image_to_ascii 中加一个 -e 参数启用这个增强逻辑,就能解决大部分“画面太灰”的问题。

另一个容易被忽略的原因是:目标宽度设置得太大。宽度超过 180 甚至 200 时,字符画里每个字符对应的原图区域就只是一个很小的局部,灰度变化变得非常缓慢,输出效果会很像一张“很淡的马赛克”。这时候调低 -w 到 80 到 120 之间,效果往往立刻改善。

4.5 透明 PNG 图标输出成黑块

这个我前面在代码解析里提过。PNG 透明区域的 RGB 值,在文件里的存储方式并不统一:有的文件里是 0,有的是背景色预乘之后的结果,还有的是残留颜色信息。如果 cua 直接使用 getpixel 得到的 RGB,那么透明区域会按照它的原始 RGB 值着色,很可能出现黑色块。

cua 里的处理策略是:如果 Alpha 通道值小于 128,就当成透明,统一改成白色(RGB 255, 255, 255)。这样在黑色终端背景上透明区域显示为白色,像打了底光;在白色终端背景上看起来就是背景色,不会突兀。你也可以按自己的需求改:把白色改成终端背景色,或者改成灰色,效果各有差异。

另外,如果图标本身有半透明边缘(Alpha 值在 128 左右),直接硬切会损失边缘的柔和度。更精细的做法是做一个简单的 alpha 混合,比如把 RGB 按 alpha 权重混合到终端背景色上,但 cua 目前保持简单逻辑,够用为先。

4.6 参数速查表

参数作用建议值/说明
-w 输出宽度(字符数)80-120,全屏终端可到 150-180
-c 启用彩色输出兼容真彩色终端,建议开启
-i 反转灰度映射白色终端背景时很有用
-r 终端宽高比补偿系数默认 0.5,按字体微调
-e 对比度自动拉伸(扩展)画面发灰时启用

这套参数设计是我在实测各种图片后总结出来的,基本覆盖了 90% 的使用场景。真正高频的其实是前三个:宽度、颜色、反转。

5. 一点实际使用体会

cua 这个工具写完之后,我实际用得最多的地方并不是朋友圈炫技,而是在我自己的一台云服务器上做登录欢迎信息。SSH 登上去之后,终端先打印出一张字符画格式的服务器 Logo,下面再跟上系统负载、内存占用、最近登录记录,比一堆干巴巴的文字好看太多了。这个玩法成本很低,但每次登录都会增加一点点仪式感,属于那种“做了之后回不去”的小改变。

另外一个有意思的用法是把 cua 输出的结果重定向到文本文件里,保存成 .txt ,然后在 README 里贴一段字符画展示项目名称。GitHub 不支持直接渲染 ANSI 颜色,所以带颜色的模式没法在 README 里展示,但灰度模式的字符画完全兼容。我试过把自己项目名字转成字符画放进 README 顶部,效果还挺有 Geek 范儿的。

如果后续你想继续扩展,还有几个方向我觉得挺有趣:把 cua 的输出从静态图片升级成 GIF 逐帧转换,让动画在终端里放出来;或者做一个 watch 模式,每隔几秒重新读取一张图片并刷新终端显示;再或者把输出转成 HTML,这样在浏览器里打开也能看到带颜色的字符画。原理都不复杂,核心字符映射逻辑完全不用改,只需要在输入输出层面做文章。

最后再分享一个调试小技巧:每次调整完 -r 系数或者字符密度表之后,最好固定一张内容简单、明暗对比强的测试图(我用的是黑白棋盘格加一个圆形渐变),输出后对比一下,这样参数对不对一目了然。单靠“感觉”调参,容易调到根本不知道哪里出了问题。字符画这个领域,看起来是门玄学,实际上全是参数。参数扣得细,效果就能好。

以上就是Python+Pillow实现cua命令行字符画工具的完整代码的详细内容,更多关于Python字符画的资料请关注脚本之家其它相关文章!

相关文章

  • pandas 按日期范围筛选数据的实现

    pandas 按日期范围筛选数据的实现

    这篇文章主要介绍了pandas 按日期范围筛选数据的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • numpy中meshgrid和mgrid的区别和使用详解

    numpy中meshgrid和mgrid的区别和使用详解

    本文主要介绍了numpy中meshgrid和mgrid的区别和使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Django REST framework 单元测试实例解析

    Django REST framework 单元测试实例解析

    这篇文章主要介绍了Django REST framework 单元测试实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Python二进制数据结构Struct的具体使用

    Python二进制数据结构Struct的具体使用

    在C/C++语言中,struct被称为结构体。而在Python中,struct是一个专门的库,用于处理字节串与原生Python数据结构类型之间的转换。本文就详细介绍struct的使用方式
    2021-06-06
  • Python爬虫实战演练之采集拉钩网招聘信息数据

    Python爬虫实战演练之采集拉钩网招聘信息数据

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用Python采集拉钩网招聘信息数据,大家可以在过程中查缺补漏,提升水平
    2021-10-10
  • Python Django项目和应用的创建详解

    Python Django项目和应用的创建详解

    这篇文章主要为大家介绍了Python Django项目和应用的创建,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-11-11
  • Python爬虫实现抓取京东店铺信息及下载图片功能示例

    Python爬虫实现抓取京东店铺信息及下载图片功能示例

    这篇文章主要介绍了Python爬虫实现抓取京东店铺信息及下载图片功能,涉及Python页面请求、响应、解析等相关操作技巧,需要的朋友可以参考下
    2018-08-08
  • python类方法和静态方法详解

    python类方法和静态方法详解

    这篇文章主要为大家介绍了python类方法和静态方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • Pygame与OpenCV联合播放视频并保证音画同步

    Pygame与OpenCV联合播放视频并保证音画同步

    Pygame的Movie模块已经废弃多年,本文主要介绍了Pygame与OpenCV联合播放视频并保证音画同步,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • 把JSON数据格式转换为Python的类对象方法详解(两种方法)

    把JSON数据格式转换为Python的类对象方法详解(两种方法)

    本文通过两种方法给大家介绍了把JSON数据格式转换为Python的类对象,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-06-06

最新评论