Reputation: 208
Problem: When saving in Image
sequence as gif with transparency, it works great, but when saving as webp, the transparency is lost in all frames.
Using: Python (Latest) with PIL (Latest)
Question: How can this be fixed, and what is causing it?
Code:
from PIL import Image, ImageSequence
def gen_frame(im: Image) -> Image:
alpha = im.getchannel('A')
# Convert the image into P mode but only use 255 colors in the palette out of 256
im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=255)
# Set all pixel values below 128 to 255 , and the rest to 0
mask = Image.eval(alpha, lambda a: 255 if a <= 128 else 0)
# Paste the color of index 255 and use alpha as a mask
im.paste(255, mask)
# The transparency index is 255
im.info['transparency'] = 255
return im
im = Image.open("input_gif.gif")
im_list = []
for frame in ImageSequence.Iterator(im):
# perform some functions on the image frames
frame = frame.convert('RGBA').resize((512, 512), Image.ANTIALIAS)
frame = gen_frame(frame)
im_list.append(frame)
img = im_list[0]
imgs = im_list[1:]
img.save("output_gif.gif", save_all=True, append_images=imgs, duration=50, loop=0, optimize=False, disposal=2)
# works correctly, as intended
img.save("output_webp.webp", save_all=True, append_images=imgs, duration=50, loop=0, optimize=False, disposal=2)
# Transparency loss in the webp format
Upvotes: 3
Views: 2565
Reputation: 2331
Fixed it - there was literally 1 character wrong with your code!
Inside of gen_frame, it was im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=255)
and it should be im = im.convert('RGBA').convert('P', palette=Image.ADAPTIVE, colors=255)
. As per one of my favorite YouTubers, "The truth hurts.". (Ben Finegold).
Correct:
from PIL import Image, ImageSequence
def gen_frame(im: Image) -> Image:
alpha = im.getchannel('A')
# Convert the image into P mode but only use 255 colors in the palette out of 256
# -------------------------------------------
# THE im.convert('RGB') WAS CHANGED TO "RGBA"
# -------------------------------------------
im = im.convert('RGBA').convert('P', palette=Image.ADAPTIVE, colors=255)
# Set all pixel values below 128 to 255 , and the rest to 0
mask = Image.eval(alpha, lambda a: 255 if a <= 128 else 0)
# Paste the color of index 255 and use alpha as a mask
im.paste(255, mask)
# The transparency index is 255
im.info['transparency'] = 255
return im
im = Image.open("input_gif.gif")
im_list = []
for frame in ImageSequence.Iterator(im):
# perform some functions on the image frames
frame = frame.convert('RGBA').resize((512, 512), Image.ANTIALIAS)
frame = gen_frame(frame)
im_list.append(frame)
img = im_list[0]
imgs = im_list[1:]
img.save("output_gif.gif", save_all=True, append_images=imgs, duration=50, loop=0, optimize=False, disposal=2)
# works correctly, as intended
img.save("output_webp.webp", save_all=True, append_images=imgs, duration=50, loop=0, optimize=False, disposal=2, lossless=True)
# Transparency loss in the webp format
https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#gif
From the docs:
GIF
Pillow reads GIF87a and GIF89a versions of the GIF file format. The library writes run-length encoded files in GIF87a by default, unless GIF89a features are used or GIF89a is already in use.
GIF files are initially read as grayscale (L) or palette mode (P) images, but seeking to later frames in an image will change the mode to either RGB or RGBA, depending on whether the first frame had transparency.
So in essence, when you went to generate a frame for the image, you were not adding the alpha channel to it, despite setting the transparency in the info properties, and when you went to save it, it was lost.
As per the docs:
transparency
Transparency color index. This key is omitted if the image is not transparent.
Upvotes: 5