使用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)
问题原因及解决方案
核心问题
- 索引颜色模式冲突:原GIF采用索引颜色模式(
P模式),每个像素通过索引对应调色板中的颜色。直接将原帧粘贴到模板画布时,调色板无法兼容,导致颜色失真、黑白化。 - 冗余IO操作:将帧保存到
BytesIO再读取的步骤会重复压缩GIF,既损失画质又拖慢处理速度。 - 未保留帧延迟:原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
相关产品推荐
相关产品推荐

