Here is a long but the additional muratgozel's answer considering the semi-transparent case.
def process_frame(frame):
"""
Process GIF frame, repair edges, ensure no white or semi-transparent pixels, while keeping color information intact.
"""
frame = frame.convert('RGBA')
# Decompose Alpha channel
alpha = frame.getchannel('A')
# Process Alpha channel with threshold, remove semi-transparent pixels
# Threshold can be adjusted as needed (0-255), 128 is the middle value
threshold = 128
alpha = alpha.point(lambda x: 255 if x >= threshold else 0)
# Process Alpha channel with MinFilter, remove edge noise
alpha = alpha.filter(ImageFilter.MinFilter(3))
# Process Alpha channel with MaxFilter, repair edges
alpha = alpha.filter(ImageFilter.MaxFilter(3))
# Apply processed Alpha channel back to image
frame.putalpha(alpha)
return frame
def webp_to_gif(data: bytes) -> bytes:
with tempfile.NamedTemporaryFile(suffix=".webp") as webp:
webp.write(data)
webp.flush()
with tempfile.NamedTemporaryFile(suffix=".gif") as img:
print(".", end="", flush=True)
im = Image.open(webp.name)
im.info.pop('background', None)
frames = []
duration = []
for frame in ImageSequence.Iterator(im):
frame = process_frame(frame)
frames.append(frame)
duration.append(frame.info.get('duration', 100))
frames[0].save(img.name, save_all=True, lossless=True, quality=100, method=6,
append_images=frames[1:], loop=0, duration=duration, disposal=2)
img.seek(0)
return img.read()
Why is it so effective?
Eliminate semi-transparent pixels:
Threshold processing converts semi-transparent pixels into fully transparent or fully opaque pixels, respectively, thereby avoiding white or other noise at the edges.
Smooth edges:
The combined operation of erosion and dilation smoothes the edges of the image by shrinking and then expanding. Erosion can remove small noise points, and dilation can restore the main part – in this way, while eliminating small noise, large blocks of image information are retained as much as possible.
Keep color information:
Since only the Alpha channel is processed and the RGB color channels are not changed, the color information remains unchanged.
Due to space limitations, I documented the detailed reasoning steps and a complete conversion script in WebM/WebP to GIF with semi-transparency(xzos.net).