使用 Python 和 PIL 实现图片合并

本文记录了一次使用 Python 和 Pillow 库实现图片批量合并并添加标题的实践过程。目的是将一个文件夹内的多张图片,按照指定的布局合并为单张图片,并在每张图片下方添加标题文字。

需求概述

需要实现以下功能:

  1. 批量图片处理:处理指定文件夹内的所有图片文件。

  2. 自定义布局:设定合并图片的行数和列数。

  3. 可配置间距与边距:设置图片之间的水平和垂直间距,以及整体图片的页边距。

  4. 自动添加标题:从文件名提取信息作为标题,并添加至每张图片下方。

  5. 可定制标题样式:设置标题的字体、大小、粗细等属性。

  6. 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")



使用 Python 和 PIL 实现图片合并
http://localhost:8090/archives/image_merge_with_pil
作者
Administrator
发布于
2025年04月11日
许可协议