为什么我的代码中的 GIF 调色板会损坏?

Why does the GIF colour palette get corrupted in my code?

我制作了一个为 GIF 或图像添加字幕的功能:

import textwrap
from io import BytesIO

from PIL import Image, ImageDraw, ImageFont, ImageOps, ImageSequence


def caption(fn: str, text: str):
    old_im = Image.open(fn)
    ft = old_im.format
    W = old_im.size[0]
    font = ImageFont.truetype('BebasNeue.ttf', 50) # replace with your own font

    width = 10
    while True:
        lines = textwrap.wrap(text, width=width)
        if (font.getsize(max(lines, key=len))[0]) > (0.9 * W):
            break
        width += 1

    # amount of lines * height of one line
    bar_height = len(lines) * (font.getsize(lines[0])[1])
    frames = []
    for frame in ImageSequence.Iterator(old_im):
        frame = ImageOps.expand(
            frame,
            border=(0, bar_height, 0, 0),
            fill='white'
        )
        draw = ImageDraw.Draw(frame)
        for i, line in enumerate(lines):
            w, h = draw.multiline_textsize(line, font=font)
            # Position is x: centered, y: line number * height of line
            draw.text(
                ((W - w) / 2, i * h),
                line,
                font=font,
                fill='black'
            )

        del draw
        b = BytesIO()
        frame.save(b, format=ft)
        b.seek(0)
        frames.append(Image.open(b))

    frames[0].save(
        f'out.{ft}',
        save_all=True,
        append_images=frames[1:],
        format=ft,
        loop=0,
        optimize=True
    )


caption(
    'in.gif',
    'this is a test message this is a test message this is a test message this is a test message this is a test message this is a test message'
)

这略有变化,会产生奇怪的结果,其中 none 是需要的。

这是in.gif:

  1. 以上代码不变:

  1. 随着 palette=old_im.palette 传入 frames[0].save():

  1. 框架在展开后立即转换为 'RGB' (.convert('RGB'))):

  1. palette=old_im.palette 传递到 frames[0].save(...) 框架在扩展后立即转换为 'RGB' (.convert('RGB'))):

  1. 随着 palette=old_im.getpalette() 传入 frames[0].save(...):

  1. palette=old_im.getpalette() 传递到 frames[0].save(...) 框架在扩展后立即转换为 'RGB' (.convert('RGB'))):

如您所见,none 的选项具有所需的输出,尽管数字 5 似乎具有最佳结果,除了白底黑字 canvas 现在突然变成深红色文本在红色 canvas 上。是什么原因造成的,我怎样才能得到正常的输出?

在扩展 frame 之前转换为 'RGB'

# [...]
for frame in ImageSequence.Iterator(old_im):
    frame = frame.convert('RGB')                # <--
    frame = ImageOps.expand(
        frame,
        border=(0, bar_height, 0, 0),
        fill='white'
    )
    draw = ImageDraw.Draw(frame)
# [...]

这就是我得到的输出:

我假设,用 white 填充会以某种方式破坏现有的调色板。如果你在扩展之前转换为 'RGB',我猜“白色”将是“正确的白色”。

----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.16299-SP0
Python:        3.9.1
Pillow:        8.1.2
----------------------------------------