使用 Python 和 PIL 实现图片合并
本文记录了一次使用 Python 和 Pillow 库实现图片批量合并并添加标题的实践过程。目的是将一个文件夹内的多张图片,按照指定的布局合并为单张图片,并在每张图片下方添加标题文字。
需求概述
需要实现以下功能:
批量图片处理:处理指定文件夹内的所有图片文件。
自定义布局:设定合并图片的行数和列数。
可配置间距与边距:设置图片之间的水平和垂直间距,以及整体图片的页边距。
自动添加标题:从文件名提取信息作为标题,并添加至每张图片下方。
可定制标题样式:设置标题的字体、大小、粗细等属性。
A4尺寸输出:最终合并的图片可调整至A4纸张尺寸。
技术选型方面,选择了 Python 语言和 Pillow (PIL) 库,Pillow 库提供了图像处理所需的功能。
实现步骤及问题解决
以下是代码实现的关键步骤和过程中遇到的问题及解决方案:
1. 图片读取与布局计算
首先,需要读取指定文件夹内的图片文件。使用 os.listdir()
获取文件名列表,并筛选出图片文件(根据文件扩展名)。使用 PIL.Image.open()
打开图片。
布局计算根据用户设定的布局参数(行数、列数)、图片尺寸、边距和间距计算画布大小以及每张图片在画布上的坐标位置。 计算公式如下:
rows = layout[0]
cols = layout[1]
image_width = image_size[0]
image_height = image_size[1]
horizontal_margin = margin[0]
vertical_margin = margin[1]
horizontal_padding = padding[0]
vertical_padding = padding[1]
canvas_width = cols * image_width + (cols - 1) * horizontal_padding + horizontal_margin * 2
canvas_height = rows * image_height + (rows - 1) * vertical_padding + vertical_margin * 2
2. 画布创建与图片粘贴
使用 PIL.Image.new()
创建指定尺寸的白色背景画布。循环遍历图片列表,根据计算出的坐标,使用 combined_image.paste(img, (x, y))
将图片粘贴到画布上。
combined_image = Image.new("RGB", (canvas_width, canvas_height), "white")
# ... 循环处理图片 ...
combined_image.paste(img, (x, y))
3. 标题提取与文字绘制
标题提取功能从文件名中获取标题信息。通过 title_extract
参数,用户可以指定文件名分隔符和索引,例如 title_extract=("+", 0)
表示使用 "+" 分隔符,并取分割后的第一部分作为标题。
文字绘制使用 PIL.ImageDraw.Draw().text()
方法。 初期遇到中文乱码问题,原因是默认字体可能不支持中文显示。 解决方法是指定支持中文的字体文件,例如Windows系统自带的 simsun.ttc
(宋体)。 使用 PIL.ImageFont.truetype()
加载字体文件,并在 draw.text()
中设置 font
参数。
from PIL import ImageFont
try:
font = ImageFont.truetype(title_font[0], title_font[1])
except IOError:
print(f"警告: 字体文件 {title_font[0]} 未找到,使用默认字体.")
font = ImageFont.load_default()
# ... 循环绘制文字 ...
draw.text(
(text_x, text_y),
img_name,
fill="black",
font=font,
anchor="mt",
stroke_width=title_font[2],
stroke_fill="black",
)
4. 调整为A4尺寸
为满足打印需求,添加 resize_to_a4()
函数,使用 PIL.Image.resize()
将合并后的图片调整为A4纸张尺寸。 采用 Image.Resampling.LANCZOS
重采样滤波器以保证图像质量。
def resize_to_a4(image):
a4_width = 2480
a4_height = 3508
return image.resize((a4_width, a4_height), Image.Resampling.LANCZOS)
# ... 主程序调用 ...
combined_image = resize_to_a4(combined_image)
总结与改进方向
本次实践基本实现了图片合并的需求。 后续可能的改进方向包括:
扩展布局选项,例如支持非网格布局。
增加更多标题样式设置,例如背景、边框等。
考虑开发图形用户界面,提升易用性。
完整代码
from PIL import Image, ImageDraw, ImageFont
import os
import pypinyin
def combine_images(
folder_path,
layout,
image_size,
margin,
padding,
title_extract,
title_font,
title_offset,
):
"""
合并指定文件夹中的图片为一张大图,并在每张图片下方添加标题文字。
Args:
folder_path: 图片文件夹路径
output_path: 输出图片路径
layout: (行数, 列数)
image_size: (图片宽度, 图片高度)
margin: (左右页边距, 上下页边距)
padding: (图片水平间距, 图片垂直间距)
title_extract: (分隔符, 索引) 用于提取标题文字
title_font: (字体文件, 字体大小, 字体粗细)
title_offset: 标题文字与图片的垂直间距
"""
images = os.listdir(folder_path)
images = [os.path.join(folder_path, image) for image in images if image.endswith((".jpg", ".png", ".jpeg"))]
# 使用 pypinyin 将文件名中的中文转换为拼音,并根据拼音排序
images.sort(key=lambda x: "".join([item[0] for item in pypinyin.pinyin(os.path.basename(x), style=pypinyin.Style.NORMAL)]))
rows = layout[0]
cols = layout[1]
image_width = image_size[0]
image_height = image_size[1]
horizontal_margin = margin[0]
vertical_margin = margin[1]
horizontal_padding = padding[0]
vertical_padding = padding[1]
canvas_width = cols * image_width + (cols - 1) * horizontal_padding + horizontal_margin * 2
canvas_height = rows * image_height + (rows - 1) * vertical_padding + vertical_margin * 2
combined_image = Image.new("RGB", (canvas_width, canvas_height), "white")
draw = ImageDraw.Draw(combined_image)
try:
font = ImageFont.truetype(title_font[0], title_font[1])
except IOError:
print(f"警告: 字体文件 {title_font[0]} 未找到,使用默认字体.")
font = ImageFont.load_default()
print("正在处理图片...")
for index, image in enumerate(images):
try:
img = Image.open(image).convert("RGB") # 确保图片是 RGB 模式
img = img.resize((image_width, image_height))
img_name = os.path.basename(image).split(title_extract[0])[title_extract[1]]
row_index = index // cols
col_index = index % cols
x = horizontal_margin + col_index * (image_width + horizontal_padding)
y = vertical_margin + row_index * (image_height + vertical_padding)
combined_image.paste(img, (x, y))
text_x = x + image_width / 2
text_y = y + image_height + title_offset
draw.text(
(text_x, text_y),
img_name,
fill="black",
font=font,
anchor="mt",
stroke_width=title_font[2],
stroke_fill="black",
)
except FileNotFoundError:
print(f"警告: 图片文件未找到: {image}")
except Exception as e:
print(f"处理图片时发生错误 {image}: {e}")
return combined_image
def resize_to_a4(image):
"""
调整图片大小为A4纸大小
"""
a4_width = 2480
a4_height = 3508
return image.resize((a4_width, a4_height), Image.Resampling.LANCZOS)
if __name__ == "__main__":
# 设置合并图片的参数
combined_image = combine_images(
folder_path="8班",
layout=(8, 5),
image_size=(390, 567),
margin=(500, 200),
padding=(250, 100),
title_extract=(".", 0),
title_font=("simsun.ttc", 50, 1.3),
title_offset=30,
)
# 调整为A4纸大小
combined_image = resize_to_a4(combined_image)
# 保存合并后的图片
combined_image.save("combined_image.png")
print("合并后的图片已保存到: combined_image.png")