PAYATHON 2026

Pillow 循环粘贴图片为何只显示第一个主题?

数据阿明

结论

问题出在 y_offset += height:height 表示整张目标图片的高度,不是单张色板的高度。粘贴第一张色板后,偏移量就到了目标图片底部,后续色板会被粘贴到画布范围之外,因此保存结果中只看得到第一张。

例如,目标画布高度设为 sw_height * len(theme_list),但第一次粘贴后,y_offset 就增加了这个完整高度。第二张色板因此从画布范围外开始粘贴。Pillow 不会自动扩大目标图片。

修正偏移量

如果每张色板的实际高度都是 sw_height,偏移量可以按单张色板的高度递增:

y_offset = 0

for theme_file in theme_list:
    # 生成 sw 色板的代码略
    swatches.paste(sw, (0, y_offset))
    y_offset += sw_height

swatches.save('theme_swatches.png')

也可以根据生成图片的实际高度递增。plt.savefig(buf) 输出的图片高度未必等于变量 sw_height,因此可以使用 sw.height:

swatches.paste(sw, (0, y_offset))
y_offset += sw.height

目标画布需要容纳所有色板。若色板尺寸固定,可以按实际高度创建画布;若尺寸可能不同,可以先收集色板,再根据图片尺寸创建画布:

swatch_images = []

for theme_file in theme_list:
    # 读取主题并生成色板,得到 sw
    swatch_images.append(sw.copy())
    plt.close()

if swatch_images:
    canvas_width = max(sw.width for sw in swatch_images)
    canvas_height = sum(sw.height for sw in swatch_images)
    swatches = Image.new('RGB', (canvas_width, canvas_height))

    y_offset = 0
    for sw in swatch_images:
        swatches.paste(sw, (0, y_offset))
        y_offset += sw.height

    swatches.save('theme_swatches.png')

检查 Matplotlib 状态

循环绘图时,建议每轮结束后调用 plt.close(),避免图形状态累积或占用资源。保存前也可以将缓冲区位置重置,并在打开图片后显式加载:

buf = BytesIO()
plt.savefig(buf, format='png')
buf.seek(0)
sw = Image.open(buf)
sw.load()

这些处理有助于提高循环中的图片处理稳定性。从给出的代码看,后续色板不可见的直接原因是偏移量按整张画布的高度递增。

备注:内容仅供参考。