4. Pillow 实战:在图片上绘制文字、插入 Logo、添加边框
好了,到了真正动手的时候了。
前面几章我们聊了证书的模板设计、数据准备,但那些都还停留在「规划」阶段。这一章,我们要用 Python 的 Pillow 库,把文字、Logo、边框这些东西,真正画到图片上去。
说白了,就是让一张空白的底图,变成一张能拿得出手的证书。
4.1 准备工作:安装与基础概念
Pillow 是 Python 里最成熟的图像处理库。我最早接触它是在做一个电商图片批量加水印的项目,那时候还是 PIL,后来才升级到 Pillow。
pip install Pillow
建议使用 9.0.0 以上版本,对字体渲染支持更好。
在开始之前,先理清几个核心概念:
- Image 对象:Pillow 里的一切操作都围绕它展开。打开图片、新建图片,返回的都是 Image 对象。
- Draw 对象:负责在 Image 上「画」东西。文字、线条、形状,都通过它来绘制。
- 坐标系:左上角是 (0, 0),x 轴向右,y 轴向下。这个和很多图形库一致。
- 字体对象:Pillow 需要加载一个具体的字体文件(.ttf 或 .otf),才能绘制文字。
核心流程:
- 打开或新建一张图片 → Image.open() 或 Image.new()
- 创建一个 Draw 对象 → ImageDraw.Draw(image)
- 加载字体 → ImageFont.truetype(font_path, size)
- 绘制文字/图片/形状 → draw.text() / image.paste() / draw.rectangle()
- 保存或输出 → image.save()
4.2 在图片上绘制文字
这是证书生成里最核心的操作。你想想看,证书上那些「姓名」、「奖项名称」、「日期」,本质上都是文字。
4.2.1 基础文字绘制
from PIL import Image, ImageDraw, ImageFont
# 打开一张背景图
img = Image.open("certificate_template.png")
draw = ImageDraw.Draw(img)
# 加载字体(注意路径要换成你自己的)
font = ImageFont.truetype("fonts/SimSun.ttf", 48)
# 绘制文字
draw.text((200, 300), "张三", fill="#333333", font=font)
img.save("output.png")
这段代码看起来简单,但我在项目中踩过坑——文字位置不对。你给的坐标是文字的左上角,但不同字体的基线不一样,导致文字看起来偏上或偏下。
避坑指南:我曾经在生成一批证书时,发现「一等奖」三个字总是偏左,后来才发现是中文字体里「一」字太窄,导致整体视觉重心偏移。解决办法是用 draw.textbbox() 先计算文字的实际占位,再做居中处理。
4.2.2 文字居中与对齐
证书上的文字,大部分需要居中。比如姓名要放在证书中间,日期要右对齐。怎么做?
def draw_centered_text(draw, text, font, center_x, center_y, fill="#333333"):
# 获取文字边界框
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
# 计算左上角坐标
x = center_x - text_width // 2
y = center_y - text_height // 2
draw.text((x, y), text, fill=fill, font=font)
# 使用示例
font_title = ImageFont.truetype("fonts/SimHei.ttf", 72)
draw_centered_text(draw, "荣誉证书", font_title, 600, 200)
这里有个细节:textbbox() 返回的是 (left, top, right, bottom),注意它和 textsize() 的区别。textsize 已经废弃了,新版本建议用 textbbox。
4.2.3 多行文字处理
有时候证书上需要写一段话,比如「特发此证,以资鼓励」。一行放不下怎么办?
def draw_multiline_text(draw, text, font, start_x, start_y, line_height, fill="#333333"):
lines = text.split("\n")
for i, line in enumerate(lines):
y = start_y + i * line_height
draw.text((start_x, y), line, fill=fill, font=font)
# 使用示例
cert_text = "该同学在2024年度表现优异\n特发此证,以资鼓励"
draw_multiline_text(draw, cert_text, font_body, 200, 500, 60)
我的习惯:多行文字我一般用 textwrap 模块先做自动换行,再逐行绘制。这样即使文字长度变化,也不会溢出边界。
4.3 插入 Logo 与图片
证书上通常会有公司 Logo、学校校徽、或者一些装饰图案。这些需要用 image.paste() 来合成。
4.3.1 基础图片粘贴
logo = Image.open("logo.png")
# 粘贴到左上角,留边距20像素
img.paste(logo, (20, 20))
嗯,这里要注意一个问题。如果 Logo 是 PNG 格式,带有透明通道,直接 paste 会把透明部分变成黑色块。
避坑指南:我曾经在给客户做证书时,Logo 周围出现了一圈黑边,排查了半天才发现是 paste 时没有传 mask 参数。带透明通道的图片,必须用自身作为蒙版。
# 正确处理带透明通道的图片
if logo.mode == 'RGBA':
img.paste(logo, (20, 20), logo) # 第三个参数是 mask
else:
img.paste(logo, (20, 20))
4.3.2 图片缩放与居中
Logo 的大小通常需要控制。太大显得突兀,太小看不清。我一般控制在证书宽度的 10%-15%。
def paste_logo_centered(img, logo_path, target_width):
logo = Image.open(logo_path)
# 按比例缩放
ratio = target_width / logo.width
new_height = int(logo.height * ratio)
logo_resized = logo.resize((target_width, new_height), Image.LANCZOS)
# 计算居中位置(假设放在顶部)
x = (img.width - target_width) // 2
y = 30 # 顶部留30像素边距
if logo_resized.mode == 'RGBA':
img.paste(logo_resized, (x, y), logo_resized)
else:
img.paste(logo_resized, (x, y))
# 使用示例
paste_logo_centered(img, "logo.png", 120)
4.4 添加边框与装饰线
证书的边框,说白了就是画几条线或者矩形。Pillow 的 Draw 对象提供了 rectangle() 和 line() 方法。
4.4.1 矩形边框
# 绘制外边框
draw.rectangle(
[(30, 30), (img.width - 30, img.height - 30)],
outline="#8B4513", # 棕色边框
width=4
)
# 绘制内边框(双线效果)
draw.rectangle(
[(40, 40), (img.width - 40, img.height - 40)],
outline="#DAA520", # 金色边框
width=2
)
你想想看,很多正式证书都有这种双线边框,外粗内细,显得庄重。
4.4.2 装饰线
有时候需要在标题下方加一条横线,或者用花纹做分隔。
# 在标题下方画一条金色横线
line_y = 250 # 标题下方位置
draw.line(
[(200, line_y), (1000, line_y)],
fill="#DAA520",
width=3
)
# 画两条平行线(更精致的效果)
draw.line([(200, line_y), (1000, line_y)], fill="#8B4513", width=1)
draw.line([(200, line_y + 5), (1000, line_y + 5)], fill="#8B4513", width=1)
4.5 本章知识体系
下面这张图,把本章的核心操作和它们之间的关系梳理清楚了。
4.6 完整示例:合成一张证书
把上面所有的知识点串起来,写一个完整的函数。这个函数我实际用在过好几个项目里,稍微改改就能用。
from PIL import Image, ImageDraw, ImageFont
import os
def generate_certificate(template_path, output_path, name, title, date, logo_path=None):
"""
生成一张完整的证书
:param template_path: 背景模板路径
:param output_path: 输出路径
:param name: 姓名
:param title: 奖项名称
:param date: 日期
:param logo_path: Logo路径(可选)
"""
# 1. 打开模板
img = Image.open(template_path)
draw = ImageDraw.Draw(img)
# 2. 加载字体
font_title = ImageFont.truetype("fonts/SimHei.ttf", 80)
font_name = ImageFont.truetype("fonts/SimSun.ttf", 60)
font_body = ImageFont.truetype("fonts/SimSun.ttf", 36)
# 3. 绘制边框
draw.rectangle([(20, 20), (img.width-20, img.height-20)], outline="#8B4513", width=6)
draw.rectangle([(35, 35), (img.width-35, img.height-35)], outline="#DAA520", width=2)
# 4. 绘制标题(居中)
center_x = img.width // 2
bbox = draw.textbbox((0, 0), "荣誉证书", font=font_title)
title_width = bbox[2] - bbox[0]
draw.text((center_x - title_width//2, 80), "荣誉证书", fill="#8B4513", font=font_title)
# 5. 绘制姓名
bbox = draw.textbbox((0, 0), name, font=font_name)
name_width = bbox[2] - bbox[0]
draw.text((center_x - name_width//2, 220), name, fill="#333333", font=font_name)
# 6. 绘制奖项
bbox = draw.textbbox((0, 0), title, font=font_body)
title_w = bbox[2] - bbox[0]
draw.text((center_x - title_w//2, 320), title, fill="#555555", font=font_body)
# 7. 绘制日期(右对齐)
date_text = f"签发日期:{date}"
bbox = draw.textbbox((0, 0), date_text, font=font_body)
date_width = bbox[2] - bbox[0]
draw.text((img.width - 80 - date_width, 450), date_text, fill="#777777", font=font_body)
# 8. 插入Logo(如果有)
if logo_path and os.path.exists(logo_path):
logo = Image.open(logo_path)
logo_width = 100
ratio = logo_width / logo.width
logo_height = int(logo.height * ratio)
logo_resized = logo.resize((logo_width, logo_height), Image.LANCZOS)
logo_x = 50
logo_y = 50
if logo_resized.mode == 'RGBA':
img.paste(logo_resized, (logo_x, logo_y), logo_resized)
else:
img.paste(logo_resized, (logo_x, logo_y))
# 9. 保存
img.save(output_path)
print(f"证书已生成:{output_path}")
# 使用示例
generate_certificate(
template_path="template.png",
output_path="cert_张三.png",
name="张三",
title="荣获2024年度优秀员工称号",
date="2024年12月31日",
logo_path="company_logo.png"
)
关键参数速查表:
| 方法 | 用途 | 常用参数 |
|---|---|---|
| draw.text() | 绘制文字 | xy, text, fill, font |
| draw.textbbox() | 计算文字尺寸 | xy, text, font |
| img.paste() | 粘贴图片 | im, box, mask |
| draw.rectangle() | 绘制矩形边框 | xy, outline, width |
| draw.line() | 绘制线条 | xy, fill, width |
| Image.open() | 打开图片 | filepath |
| ImageFont.truetype() | 加载字体 | fontpath, size |