如何用Python的Pillow库实现裁剪后GIF的圆角透明效果
问题修复方案
核心问题原因
- 粘贴逻辑顺序错误:现有代码将背景图粘贴到头像帧上层,直接覆盖了头像内容,所以调用
mask_circle_transparent后只显示背景 - GIF格式特性不兼容半透明:PNG支持8位半透明通道,而GIF仅支持1位二进制透明(完全透明/完全不透明),且保存时未指定透明配置导致边角显示背景色
- 冗余操作问题:GIF帧循环内重复执行保存、重复resize背景,既降低效率也可能引发异常
- 逻辑缺失:GIF帧处理未做正方形裁剪,直接拉伸会导致头像变形
修复后的完整代码
from PIL import Image, ImageDraw, ImageFilter, ImageSequence from io import BytesIO def crop_center(pil_img, crop_width, crop_height): img_width, img_height = pil_img.size return pil_img.crop(((img_width - crop_width) // 2, (img_height - crop_height) // 2, (img_width + crop_width) // 2, (img_height + crop_height) // 2)) def crop_max_square(pil_img): return crop_center(pil_img, min(pil_img.size), min(pil_img.size)) def mask_circle_transparent(pil_img, blur_radius, offset=0): offset = blur_radius * 2 + offset mask = Image.new("L", pil_img.size, 0) draw = ImageDraw.Draw(mask) draw.ellipse((offset, offset, pil_img.size[0] - offset, pil_img.size[1] - offset), fill=255) mask = mask.filter(ImageFilter.GaussianBlur(blur_radius)) # 适配GIF透明,把半透明区域做阈值处理,可根据需要调整阈值数值 threshold = 128 mask = mask.point(lambda x: 255 if x > threshold else 0, mode='L') result = pil_img.copy() result.putalpha(mask) return result async def on_message(message): # 背景只需要resize一次,不要放到循环里重复处理 background = background.resize((256, 256)) avatar_data = BytesIO(await message.author.avatar_url.read()) if ".gif" in str(message.author.avatar_url): frames = [] gif = Image.open(avatar_data) # 读取原GIF的播放参数 duration = gif.info.get('duration', 100) loop = gif.info.get('loop', 0) for frame in ImageSequence.Iterator(gif): frame = frame.copy() # 先裁剪正方形再resize,避免变形 frame = crop_max_square(frame) frame = frame.convert("RGBA") frame = frame.resize((256, 256)) frame = mask_circle_transparent(frame, 4) # 修正粘贴顺序:先铺背景,再把带透明的头像贴到背景上 composite_frame = background.copy() composite_frame.paste(frame, (0, 0), frame) frames.append(composite_frame) # 保存逻辑移到循环外,添加GIF专属参数 frames[0].save( 'temp_images/result.gif', save_all=True, append_images=frames[1:], duration=duration, loop=loop, disposal=2, # 每帧播放后清空,避免残留 transparency=0, optimize=False ) else: im_square = crop_max_square(Image.open(avatar_data).convert("RGB")) im_square = im_square.resize((256, 256)) im_thumb = mask_circle_transparent(im_square, 4) im_composite = background.copy() im_composite.paste(im_thumb, (0, 0), im_thumb) im_composite.show() im_composite.save('temp_images/result.png')
补充说明
如果觉得圆形边缘太硬,可以调整mask_circle_transparent里的阈值数值,数值越小边缘保留的模糊部分越多,反之边缘越锐利。
内容的提问来源于stack exchange,提问作者Razzer
相关产品推荐
相关产品推荐

