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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 08:15:03