动画 GIF 仅在第一帧透明(Python PIL)

Animated GIFs are only transparent on their first frame (Python PIL)

以下代码从两张图片创建 GIF:

# GIFs are always palette images so they would be converted later anyway
im1 = PIL.Image.open('grin-emoji-by-twitter-rgba.png').convert('P')
im2 = PIL.Image.open('grin-emoji-by-twitter-rgba-2.png').convert('P')

im1.save('output.gif', save_all=True, append_images=[im2, im1, im2], loop=0, duration=200, transparency=255)

结果出乎意料的错误。第一帧没问题,但后续帧在更新区域周围包含一个黑色矩形而不是透明度:

我认为错误如下:在第一张图片上,我们将索引 255 指定为完全透明颜色的索引。然而,save 函数似乎只在第一帧将索引 255 转换为透明度,但在所有其他帧上跳过此步骤。

有什么办法可以避免这种情况吗?

是的,有办法。 我们可以手动编辑调色板,将透明度从索引 255 移动到 0。 如果透明度位于索引 0 而不是 255,save 不会出现错误。

我通过将整个调色板右移一个索引来完成此操作,因此索引 5 变为索引 6,索引 255 变为索引 0,依此类推。

在最坏的情况下(例如彩色长 GIF)透明度并不总是在索引 255 处,我们必须手动将其与索引 0 对齐(参见 shiftme 行)。

im1 = PIL.Image.open('grin-emoji-by-twitter-rgba.png').convert('P')
im2 = PIL.Image.open('grin-emoji-by-twitter-rgba-2.png').convert('P')

p1 = im1.getpalette()
p2 = im2.getpalette()

# if you know a data point in the resulting image that will be
# transparent you can also set it directly e.g. 'shiftme = -frame[0][0]'
shiftme = 1       
im1 = (numpy.array(im1) + shiftme) % 256  # shift data pointing into palette
im2 = (numpy.array(im2) + shiftme) % 256

im1 = PIL.Image.fromarray( im1 ).convert('P')
im2 = PIL.Image.fromarray( im2 ).convert('P')

im1.putpalette( p1[-3*shiftme:] + p1[:-3*shiftme] )  # shift palette
im2.putpalette( p2[-3*shiftme:] + p2[:-3*shiftme] )  # NB this is NOT '-4' as it is RGB not RGBA

print(numpy.array(im1))
print(numpy.array(im2))

im1.save('output.gif', save_all=True, append_images=[im2, im1, im2], loop=0, duration=200, transparency=0)

结果

作为我的其他答案的替代方案,您也可以 set the disposal value to 2:

im1.save('output.gif', save_all=True, append_images=[im2, im1, im2], 
         loop=0, duration=200, transparency=255, disposal=2)

请注意,与我的其他答案不同,这在 100% 的时间都不起作用,因为透明通道可以跳转到其他索引。 :-/ 这似乎只发生在较长的 GIF 中,但是其中有很多颜色。

左:这个答案,右:手动对齐的其他答案

编辑:Here 它声称在较新版本的 pillow 中已 修复! (我认为 8.1.2+)