New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Each frame of an animated GIF can have a different palette #4977
Comments
I think this is two separate issues. I think The noise in the image on the other hand seems to be using the correct palette, but parts of the image are garbled because those parts use the "do not dispose" "disposal method". That is, those parts of the frames are supposed to be using the pixels from the previous frame. I think Pillow might not take into account that when copying the previous frame, it's still in palette mode, and it's not possible to have pixels from two different frames that use two different palettes while still maintaining a single palette for the whole frame... |
Somewhat illustrative diff: diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
index 4ca5a697..c3a8411d 100644
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -304,8 +304,11 @@ class GifImageFile(ImageFile.ImageFile):
if self._prev_im and self.disposal_method == 1:
# we do this by pasting the updated area onto the previous
# frame which we then use as the current image content
+ tmp = Image.new('P', self.im.size)
+ tmp.putpalette(self.im.getpalette())
updated = self._crop(self.im, self.dispose_extent)
- self._prev_im.paste(updated, self.dispose_extent, updated.convert("RGBA"))
+ tmp.im.paste(updated, self.dispose_extent, updated.convert("RGBA"))
+ self._prev_im = tmp.im
self.im = self._prev_im
self._prev_im = self.im.copy() With this diff you'll only see the newly encoded parts of each frame (with their correct colors). Everything that's supposed to be re-used from previous frames is black. E.g. the minion Christmas hats are only encoded in the first frame. The total number of colors used in a single frame can be greater than 256 because each frame can be a composite of multiple images, each potentially using a different palette. No idea how to fix that, though. |
I'm working on a fix for this issue in my Pillow fork, here is the result so far: !! Epilepsy Warning !! Still a bit of noise being left behind in this particular GIF, and I know why for the "Merry Christmas" part, the decoder is still leaving behind integers in the
I've decided to handle this by decoding those frames with transparency intact, but layering them on other frames once they are converted to a format that can handle more than 256 colors. Overlaying that cat image onto existing GIF frames would require making the cat picture the first frame and creating transparency in subsequent frames where you want the cat to appear, this could cause the rest of the first frame to lack colors though and that cat image might be too colorful in itself. |
I've created PR #5857 to resolve this. Using the code and images from StackOverflow, from PIL import Image
from io import BytesIO
animated_gif = './img/gif_distort.gif'
transparent_foreground = './img/cat.jpeg'
img = Image.open(animated_gif)
image = Image.open(transparent_foreground)
duration = []
frames = []
for i in range(img.n_frames):
img.seek(i)
frame = img.convert('RGBA').copy()
duration.append(img.info['duration'])
frame.paste(image)
frames.append(frame)
# save gif in temp file
temp_gif_path = 'img/output.gif'
frames[0].save(temp_gif_path, format='GIF', save_all=True, append_images=frames[1:], duration=duration, optimise=True) |
This StackOverflow question refers https://stackoverflow.com/q/64334674/2836621
If you look at the animated GIF in that question and analyse it with ImageMagick, you will see that each frame has a different palette:
If you append all the frames side-by-side with ImageMagick, you will get:
If you analyse the same image with PIL:
You get the same palette for all frames:
And a big mess :-)
Using Pillow v7.2.0 on macOS with Python 3.8.
The text was updated successfully, but these errors were encountered: