Skip to content
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

Reading GIF and saving it introduces artifacts #5307

Closed
FirefoxMetzger opened this issue Mar 4, 2021 · 6 comments · Fixed by #5333
Closed

Reading GIF and saving it introduces artifacts #5307

FirefoxMetzger opened this issue Mar 4, 2021 · 6 comments · Fixed by #5333
Labels

Comments

@FirefoxMetzger
Copy link
Contributor

What did you do?

I'm trying to read a .gif into a numpy array to obtain a sequence of frames and then write the result back to disk as a new gif.

What did you expect to happen?

The image sequence loads and each frame contains a (fully rendered?) image. The created .gif and the original one look alike (minus FPS) upon visual inspection.

What actually happened?

The written/created .gifcontains artifacts, which (I suppose) come from either the writer not using the appropriate disposal method (1 - do not dispose), the reader only reading data from the current frame and disposing of the previous frame(s) each time, or the image containing transparent pixels which would then show the underlying frame/background.

What are your OS, Python and Pillow versions?

  • OS: Windows Build 19042.844
  • Python: Python 3.8.8
  • Pillow: Pillow==8.1.0
import numpy as np
from PIL import Image, ImageSequence
import urllib.request

url = "https://github.com/imageio/imageio-binaries/blob/master/images/newtonscradle.gif?raw=true"

# For (visual) comparison, store the original GIF
response = urllib.request.urlopen(url)
with open("original.gif", "wb+") as file:
    file.write(response.read())

# load the image into a numpy array (via pillow) and write it to dist
response = urllib.request.urlopen(url)
pil_image = Image.open(response)

# It seems I am missing an option here
np_image = np.asarray([np.array(frame.convert("RGBA")) for frame in ImageSequence.Iterator(pil_image)])
pil_frames = [Image.fromarray(arr) for arr in np_image]

pil_frames[0].save("test.gif", loop=0, fps=60, save_all=True, append_images=pil_frames[1:])

Original Image:
original

Resulting Image:
test

It might be a user error on my part and I am simply missing an option. In either case, I don't think the default behavior should change, but it would be good to have the option to make a frame contain the same pixel values that a user would see once the frame is displayed (including non-disposed pixels of the previous frame(s)).

@radarhere radarhere changed the title reading GIF to nparray and writing it back to disk introduces artifacts Reading GIF to nparray and writing it back to disk introduces artifacts Mar 4, 2021
@radarhere radarhere added the GIF label Mar 4, 2021
@radarhere
Copy link
Member

Two comments for now -

  1. If I remove numpy from your example, it produces the same output, so this is a purely Pillow issue.
from PIL import Image, ImageSequence
import urllib.request

url = "https://github.com/imageio/imageio-binaries/blob/master/images/newtonscradle.gif?raw=true"

response = urllib.request.urlopen(url)
with open("original.gif", "wb+") as file:
    file.write(response.read())

response = urllib.request.urlopen(url)
pil_image = Image.open(response)

pil_frames = [frame for frame in ImageSequence.Iterator(pil_image)]

pil_frames[0].save("test.gif", loop=0, fps=60, save_all=True, append_images=pil_frames[1:])
  1. There is no 'fps' parameter. Take a look at 'duration' instead - https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#saving

@radarhere radarhere changed the title Reading GIF to nparray and writing it back to disk introduces artifacts Reading GIF and saving it introduces artifacts Mar 4, 2021
@radarhere
Copy link
Member

And just for my future reference, I don't expect this has to do with saving, just reading, as even this simpler instance shows a broken image.

from PIL import Image
import urllib.request

response = urllib.request.urlopen("https://github.com/imageio/imageio-binaries/blob/master/images/newtonscradle.gif?raw=true")
with open("original.gif", "wb+") as f:
    f.write(response.read())

with Image.open("original.gif") as im:
    im.seek(1)
    im.save("out.png")

@radarhere
Copy link
Member

Testing, I find this would be resolved by #5333

@FirefoxMetzger
Copy link
Contributor Author

Will it still be possible to get the raw frame values containing transparent pixels?

I like the idea of not replacing pixel values if the next pixel in the sequence is transparent, At the same time, it may still be useful to be able to just get the current frame without applying information from previous frames, e.g., for inspecting that writing worked as intended.

Another question: How will this work with GIFs ability to have per-frame color pallets?

If I understand #5333 correctly, then the pixel of the previous frame will be kept, which could effectively increase the number of colors in the image above 256. Couldn't this cause aliasing? How will the color pallet/conversion mechanism handle this?

@radarhere
Copy link
Member

Without modifying Pillow, I wouldn't think that getting the raw frame with transparent pixels is even possible at the moment. If you'd like that as a new feature, please create a new issue.

If you look through the issues tagged with GIF, you will find that there is already a problem with combining the palette between GIF frames. Looking at the code, when loading each frame, it defaults to the global palette, which can be overridden by the local palette. The matter of keeping colors from previous frames doesn't just apply to transparency, but also to each new tile that may not take up the entire width and height of the image.

It doesn't solve everything, but I find that #5333 does fix the original problem that you posted here.

@FirefoxMetzger
Copy link
Contributor Author

Yes it does :) Thank you for addressing this issue with a PR so swiftly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants