乱码这个问题,几乎每个用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则更安全。
七、注意事项汇总
- 字体路径一定要确认好。最好先手动打开资源管理器或终端,验证路径是否真实存在。
- 文件名可能不带后缀,但Pillow支持
.ttc和.ttf,一般没问题。 - 如果使用
image.text()方法时遇到坐标对齐不准,优先用textbbox计算尺寸,不要凭感觉。 - 中文文本里如果有换行符,
text方法不会自动换行,只会显示成空格或乱码。你需要自己用textwrap或手动切分字符串。 - 读取外部文本时,
encoding参数必须正确。如果用了默认的locale编码,很可能在Linux下是UTF-8,Windows下是GBK,会出现乱码。强烈建议始终显式指定encoding="utf-8"。 - Pillow版本不同,字体渲染可能会有细微差异。建议使用较新版本,老版本没有
textbbox,可以用textsize,但textsize在Pillow 10已经移除,要留意。 - 如果绘制出来的中文偏上或偏下,可以调整
y坐标。字体本身有基线,视觉上微调几个像素很正常,不用慌。 - 如果需要同时绘制中英文混合文本,只要字体支持中文和英文,Pillow会自动处理,不用分割。
- 在服务器环境(比如Docker容器)里,可能没有安装字体,需要额外安装字体包。比如Ubuntu上可以执行
apt-get install fonts-wqy-microhei。 - 从性能角度,每次绘制建议只加载一次字体对象,不要循环内反复加载,否则会拖慢速度。
八、总结
Pillow绘制中文乱码的根本原因是字体文件不支持中文,而不是Pillow本身有bug。解决思路就两步:先找到系统里的中文字体文件,再用 ImageFont.truetype 加载它。编码问题则要严格管理字符串来源,保证以UTF-8读写文本。真正让代码健壮起来的方法是建立一个字体回退机制,这样换到任何机器都不会因为字体缺失而崩溃。最后,在实战中添加水印和自动计算位置的技巧,可以让你的图片效果更专业。希望这篇博客能帮您彻底告别Pillow中文字乱码的烦恼。
评论
围绕“Pillow中文字绘制乱码问题的完整解决方案:字体加载与编码适配”参与讨论