You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Python的PIL保存GIF时出现黑白失真与闪烁问题

问题描述

我尝试给输入的GIF顶部添加文字区域,模仿热门meme样式,但输出的GIF完全变为黑白,还存在严重失真,播放速度也变慢,仅能看到猫咪跳舞的轮廓。

操作流程:用PIL打开输入GIF,创建带有额外顶部高度的白色空白画布,添加文字后,遍历GIF的每一帧并粘贴到画布对应位置,将处理后的帧存入列表再保存为新GIF。但如果不把GIF帧粘贴到带文字的画布上,输出的GIF显示正常。

复现代码:

from PIL import Image, ImageFont, ImageDraw, ImageSequence
from io import BytesIO

caption = "sample"
giftemplate = Image.open("input.gif")
font = ImageFont.truetype("Futura Condensed Extra Bold Regular.ttf", 10)
# text margin size scales with image height
text_margin = int((giftemplate.height / 100))

# text width and height
tw, th = font.getsize(caption)
# top left text box coordinate with respect to image pixels. Top left of image is 0,0
cx, cy = int(giftemplate.width / 2), text_margin

padding_size = th
base_width, base_height = giftemplate.size
new_height = base_height + padding_size

# create empty white frame with extra height for text
result_template = Image.new(giftemplate.mode, size=(base_width, new_height), color=(255, 255, 255))

# draw text lines in the extra height
tw, th = font.getsize(caption)
draw = ImageDraw.Draw(result_template)
draw.text((cx - tw / 2, cy), caption, (0, 0, 0), font=font)

# paste each frame of gif under extra height
frames = []
for frame in ImageSequence.Iterator(giftemplate):
    result_template.paste(frame, (0, padding_size))
    b = BytesIO()
    result_template.save(b, format="GIF")
    result_frame = Image.open(b)
    frames.append(result_frame)

frames[0].save('meme_out.gif', save_all=True, append_images=frames[1:], loop=0)

问题原因及解决方案

核心问题

  1. 索引颜色模式冲突:原GIF采用索引颜色模式(P模式),每个像素通过索引对应调色板中的颜色。直接将原帧粘贴到模板画布时,调色板无法兼容,导致颜色失真、黑白化。
  2. 冗余IO操作:将帧保存到BytesIO再读取的步骤会重复压缩GIF,既损失画质又拖慢处理速度。
  3. 未保留帧延迟:原GIF的每帧播放延迟参数未被继承,导致输出GIF的播放速度异常。

修复后的代码

from PIL import Image, ImageFont, ImageDraw, ImageSequence

caption = "sample"
giftemplate = Image.open("input.gif")
font = ImageFont.truetype("Futura Condensed Extra Bold Regular.ttf", 10)
text_margin = int(giftemplate.height / 100)

# 获取文本尺寸并计算居中坐标
tw, th = font.getsize(caption)
cx, cy = int(giftemplate.width / 2) - int(tw / 2), text_margin
padding_size = th
base_width, base_height = giftemplate.size
new_height = base_height + padding_size

# 先创建RGB模式的模板,避免索引模式调色板冲突
result_template_rgb = Image.new("RGB", (base_width, new_height), color=(255, 255, 255))
draw = ImageDraw.Draw(result_template_rgb)
draw.text((cx, cy), caption, (0, 0, 0), font=font)

frames = []
delays = []  # 保存原GIF每帧的播放延迟

for frame in ImageSequence.Iterator(giftemplate):
    # 记录原帧的延迟时间
    delays.append(frame.info.get("duration", 100))
    # 将原帧转为RGB模式,粘贴到模板的对应位置
    frame_rgb = frame.convert("RGB")
    current_frame = result_template_rgb.copy()
    current_frame.paste(frame_rgb, (0, padding_size))
    # 转回索引模式,使用自适应调色板保证颜色准确
    current_frame_p = current_frame.convert("P", palette=Image.ADAPTIVE)
    frames.append(current_frame_p)

# 保存时写入延迟参数,设置帧清理规则避免画面残留
frames[0].save(
    'meme_out.gif',
    save_all=True,
    append_images=frames[1:],
    loop=0,
    duration=delays,
    disposal=2
)

关键修改说明

  • 颜色模式兼容:先在RGB模式下完成文字绘制和帧粘贴,再转回索引模式,彻底避免调色板冲突问题。
  • 移除冗余操作:直接复制模板并粘贴帧,跳过BytesIO的保存读取步骤,提升处理速度并保留画质。
  • 保留播放速度:提取原GIF每帧的duration参数,保存时传入,保证输出GIF的播放节奏和原文件一致。
  • 优化帧显示:设置disposal=2,确保每帧播放后正确清理上一帧内容,避免画面残留。

内容的提问来源于stack exchange,提问作者Clinterpottrmus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 13:20:28