乱码这个问题,几乎每个用Pillow画中文的人都会撞上。明明代码写得没毛病,英文数字都正常,偏偏中文变成一个个小方框或者乱糟糟的符号。别急,这不是你写错了,而是Pillow默认用的字体根本不认识中文。本文直接告诉你到底怎么处理,从字体加载到编码细节,一步步来,保证你看完就能用。

一、问题现象:为什么Pillow画中文全是方块

先看一个最简单的画图例子。你用Pillow新建一张图片,然后调用 text 方法写几个中文,结果保存出来的图片上,中文全变成了“□□□□”或者类似乱码。原因特别简单:Pillow自带的默认字体是纯英文字体,里面没有中文字形。就好比你让一个只懂英文的人帮你念一段中文,他只能照着形状画框框。

所以,想让中文正常显示,必须要告诉Pillow:“你去用某个中文字体文件”。这个字体文件一般就在你的电脑系统里,只要找对路径,加载进来就解决了。

二、核心解决办法:加载系统字体

2.1 字体文件去哪找

不同操作系统,中文字体存放的位置不一样,但都有现成的文件。

  • Windows:字体一般在 C:\Windows\Fonts 目录,比如微软雅黑叫 msyh.ttc,宋体叫 simsun.ttc,黑体叫 simhei.ttf
  • macOS:字体一般在 /System/Library/Fonts,也有在 /Library/Fonts 的,比如苹方字体叫 PingFang.ttc,STHeiti 等。
  • Linux:不同发行版位置不一样,常见的是 /usr/share/fonts,中文字体可能是 wqy-microhei.ttc 或者 noto-sans-cjk.ttc

如果你嫌找路径麻烦,可以直接用系统命令查。比如在Windows上可以用 dir C:\Windows\Fonts 看看有哪些文件;在macOS或Linux上可以用 fc-list :lang=zh 命令列出所有支持中文的字体路径。这个方法很实用,后面我还会提到。

2.2 用ImageFont.truetype加载字体

Pillow里面用来加载字体文件的函数是 ImageFont.truetype,它接收两个关键参数:字体文件路径和字号大小。下面这个例子演示了如何正确加载中文字体并写中文。

# Python 3 + Pillow 10 示例
# 导入需要的库
from PIL import Image, ImageDraw, ImageFont

# 第一步:新建一张空白图片,尺寸宽600、高200,背景色为白色
image = Image.new("RGB", (600, 200), "white")

# 第二步:创建绘图对象,相当于拿到一支画笔
draw = ImageDraw.Draw(image)

# 第三步:加载中文字体
# 这里用的是Windows下的微软雅黑,如果你的系统是macOS或Linux,换成对应的路径即可
font_path = "C:/Windows/Fonts/msyh.ttc"  # 微软雅黑字体文件
font = ImageFont.truetype(font_path, 40)  # 加载字体,字号设置为40

# 第四步:写入中文
draw.text((50, 70), "Pillow中文正常显示", font=font, fill="black")

# 第五步:保存图片
image.save("zh_success.png")

注意路径里的斜杠,在Python字符串里用正斜杠 / 或双反斜杠 \\ 都可以。加载后,font 对象就能被 draw.text 使用。这里“Pillow中文正常显示”就能在图片上完美出现了。

三、编码适配:别让字符串变成“烫烫烫”

字体加载好了,但你发现读文件里的中文时还是会乱码?那多半是编码没对上。Python本身对Unicode支持很好,但你在读文件或写代码时,如果编码没处理好,中文就会变成乱码。

3.1 源文件编码声明

如果你用的是Python 3,默认源码编码就是UTF-8,所以一般不用特殊声明。但如果你在Windows下用记事本编辑代码,并且保存成了ANSI编码,那Python运行时就可能报错或者显示乱码。建议无论如何,代码文件一律用UTF-8保存。如果你用VS Code,右下角就能看到当前文件编码,点一下改成“UTF-8”就行。

3.2 从文件读取中文文本时的编码处理

大多数时候,中文文本是从外部文件读进来的,比如一个UTF-8编码的txt文件。这时要用 open 函数指定编码。下面这个例子完整展示了:读取一个UTF-8文本文件,把内容用中文字体画到图片上。

# Python 3 + Pillow 10 示例
# 准备一个名为 poem.txt 的文件,内容是中文,编码为UTF-8
from PIL import Image, ImageDraw, ImageFont

# 第一步:读取文本文件,显式指定UTF-8编码,避免乱码
with open("poem.txt", "r", encoding="utf-8") as f:
    text = f.read().strip()   # 去掉首尾换行和空格

# 第二步:新建一个宽800、高300的图片,米黄色背景
image = Image.new("RGB", (800, 300), "#fdf6e3")
draw = ImageDraw.Draw(image)

# 第三步:加载中文字体,这里用黑体,字号36
font = ImageFont.truetype("C:/Windows/Fonts/simhei.ttf", 36)

# 第四步:在图片左上角开始绘制文本,坐标为(40, 40)
draw.text((40, 40), text, font=font, fill="darkblue")

# 第五步:保存结果
image.save("file_text.png")

在这个例子中,encoding="utf-8" 是重点。如果文件是GBK编码,你就要写 encoding="gbk"。不知道怎么判断文件的编码?用记事本打开文件,点“另存为”,看右下角编码显示是什么。也可以用Python自动检测,但那是另一个话题了。

3.3 处理字符串中的特殊字符

有时候你从数据库或网络接口拿到一段中文文本,里面可能含有 \uXXXX 这种转义序列,比如 \u4e2d\u6587 其实是“中文”。如果你直接画,画出来就是一串反斜杠加数字。遇到这种情况,可以用 text.encode().decode('unicode_escape') 来还原,但更推荐确保数据源本身已经是正常的Unicode字符串。如果你自己写字符串,尽量别用转义,直接写中文本身,代码更清晰。

四、多字体场景与回退机制

很多开发者在不同环境跑同一个脚本,比如家里是Windows,服务器是Linux。Windows有微软雅黑,Linux不一定有。如果代码里写死了Windows字体路径,到Linux上就会报“文件不存在”的错误。这时候就需要一种“自动寻找可用中文字体”的方法。

4.1 常见中文字体列表

我整理了一个常见的字体路径表,虽然不同系统略有差异,但可以作为参考。

系统 常见中文字体路径
Windows C:/Windows/Fonts/msyh.ttc, simhei.ttf, simsun.ttc
macOS /System/Library/Fonts/PingFang.ttc, /System/Library/Fonts/STHeiti Medium.ttc
Linux /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc, /usr/share/fonts/truetype/wqy/wqy-microhei.ttc

4.2 自动检测可用字体并回退

我们可以写一个小函数,按照候选列表顺序查找,找到哪个就加载哪个。这样代码换到任何环境都能用。

# Python 3 + Pillow 10 示例
# 自动选择中文字体的函数
import os
from PIL import ImageFont

def load_chinese_font(size):
    """根据当前系统自动加载一个可用的中文字体,返回ImageFont对象"""
    # 候选字体路径,按推荐程度从高到低排列
    font_candidates = [
        "C:/Windows/Fonts/msyh.ttc",                        # Windows 微软雅黑
        "C:/Windows/Fonts/simhei.ttf",                      # Windows 黑体
        "/System/Library/Fonts/PingFang.ttc",               # macOS 苹方
        "/System/Library/Fonts/STHeiti Medium.ttc",         # macOS 黑体
        "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", # Linux Noto
        "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",   # Linux 文泉驿
    ]
    # 遍历所有候选路径,第一个存在的就加载它
    for path in font_candidates:
        if os.path.exists(path):               # 检查文件是否存在
            return ImageFont.truetype(path, size)  # 加载并返回
    # 如果全部不存在,就抛出异常,提示你安装字体
    raise FileNotFoundError("没有找到合适的中文字体,请安装一个中文字体文件")

# 使用示例
font = load_chinese_font(32)
print("字体加载成功,我可以用这个font画中文了")

这个函数就是备胎策略:第一个备胎不在,就换第二个,直到找到为止。在实际项目中,把这段代码放到你的工具模块里,以后调用 load_chinese_font(字号) 就再也不用担心缺字体了。

五、完整实战案例:给照片添加中文水印

现在综合应用一下,给一张照片加上半透明的中文水印,这是很常见的需求,比如给图片标记作者名。完整代码和注释如下。

# Python 3 + Pillow 10 示例
# 给照片添加一个半透明的中文水印
from PIL import Image, ImageDraw, ImageFont

# 第一步:打开一张照片,并转换为RGBA模式以便处理透明度
photo = Image.open("my_photo.jpg").convert("RGBA")

# 第二步:创建一个与照片同尺寸的透明图层,用于单独绘制水印
watermark = Image.new("RGBA", photo.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(watermark)

# 第三步:加载中文字体,字号根据照片宽度动态调整(取宽度的1/15)
font_size = max(30, photo.width // 15)
font = ImageFont.truetype("C:/Windows/Fonts/msyh.ttc", font_size)

# 第四步:定义水印文字
text = "我的摄影作品 @ 2025"

# 第五步:用 textbbox 计算文字的宽度和高度,方便调整居中位置
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]   # 文字宽度
text_height = bbox[3] - bbox[1]  # 文字高度
x = (photo.width - text_width) // 2   # 水平居中
y = (photo.height - text_height) // 2 # 垂直居中

# 第六步:在透明图层上绘制水印文字,填充白色,设定透明度alpha=180 (0~255)
draw.text((x, y), text, font=font, fill=(255, 255, 255, 180))

# 第七步:把透明水印层与照片合成
out = Image.alpha_composite(photo, watermark)

# 第八步:转回RGB模式并保存为JPG,品质设为85
out = out.convert("RGB")
out.save("photo_with_watermark.jpg", quality=85)

print("水印添加完成,文件已保存")

这个例子最值得一提的是,用 textbbox 来计算文字的实际尺寸,而不是靠猜。如果不用这个方法,水印位置可能会偏。另外,水印半透明的效果是通过RGBA通道实现的,透明图层里画文字时,第四个值180表示不透明度,越高越不透明。

六、应用场景与优缺点

6.1 适合做什么

这套方案适合所有需要在图片上显示中文的场景。比如生成带有中文标题的海报,给批量商品图打上中文标签,做数据可视化时把中文指标画在图表上,或者给视频截图添加字幕。只要是Pillow能画图的地方,都能用。

6.2 技术优点

  • 简单直接:不需要下载额外的软件包,Pillow本身就能搞定。
  • 跨平台:通过字体回退机制,写一次代码在Windows、macOS、Linux上都能运行。
  • 灵活可控:字体大小、颜色、位置、透明度都能精确设置。
  • 性能够用:对于中小尺寸图片,绘制速度很快。

6.3 局限与注意

  • 字体文件较大:如果打包成独立程序,需要把字体文件一起带上,否则目标机器没有中文字体就会失败。
  • 排版能力有限:Pillow只提供简单的 text 方法,不能实现自动换行、文字对齐、竖排等复杂排版。不过可以自己拆分文字并按行绘制,但需要手动处理。
  • 字体版权问题:商业使用要注意字体授权,比如微软雅黑在Windows上可以免费调用,但如果你把生成图片用于商业产品,最好确认授权范围。开源字体如文泉驿、Noto Sans CJK则更安全。

七、注意事项汇总

  1. 字体路径一定要确认好。最好先手动打开资源管理器或终端,验证路径是否真实存在。
  2. 文件名可能不带后缀,但Pillow支持 .ttc.ttf,一般没问题。
  3. 如果使用 image.text() 方法时遇到坐标对齐不准,优先用 textbbox 计算尺寸,不要凭感觉。
  4. 中文文本里如果有换行符,text 方法不会自动换行,只会显示成空格或乱码。你需要自己用 textwrap 或手动切分字符串。
  5. 读取外部文本时,encoding 参数必须正确。如果用了默认的 locale 编码,很可能在Linux下是UTF-8,Windows下是GBK,会出现乱码。强烈建议始终显式指定 encoding="utf-8"
  6. Pillow版本不同,字体渲染可能会有细微差异。建议使用较新版本,老版本没有 textbbox,可以用 textsize,但 textsize 在Pillow 10已经移除,要留意。
  7. 如果绘制出来的中文偏上或偏下,可以调整 y 坐标。字体本身有基线,视觉上微调几个像素很正常,不用慌。
  8. 如果需要同时绘制中英文混合文本,只要字体支持中文和英文,Pillow会自动处理,不用分割。
  9. 在服务器环境(比如Docker容器)里,可能没有安装字体,需要额外安装字体包。比如Ubuntu上可以执行 apt-get install fonts-wqy-microhei
  10. 从性能角度,每次绘制建议只加载一次字体对象,不要循环内反复加载,否则会拖慢速度。

八、总结

Pillow绘制中文乱码的根本原因是字体文件不支持中文,而不是Pillow本身有bug。解决思路就两步:先找到系统里的中文字体文件,再用 ImageFont.truetype 加载它。编码问题则要严格管理字符串来源,保证以UTF-8读写文本。真正让代码健壮起来的方法是建立一个字体回退机制,这样换到任何机器都不会因为字体缺失而崩溃。最后,在实战中添加水印和自动计算位置的技巧,可以让你的图片效果更专业。希望这篇博客能帮您彻底告别Pillow中文字乱码的烦恼。