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

GIF frames loses transparency with Image.alpha_composite() #4650

Closed
hutomosaleh opened this issue May 26, 2020 · 5 comments · Fixed by #5333
Closed

GIF frames loses transparency with Image.alpha_composite() #4650

hutomosaleh opened this issue May 26, 2020 · 5 comments · Fixed by #5333
Labels

Comments

@hutomosaleh
Copy link

hutomosaleh commented May 26, 2020

What did you do?

I am trying to remake a transparent GIF by loading a GIF, extracting the GIF and appending them to make a GIF again.

What did you expect to happen?

The result would retain the transparency.

What actually happened?

The frames loses its transparency when using alpha_composite.

What are your OS, Python and Pillow versions?

  • OS: Windows
  • Python: 3.7.4
  • Pillow: 7.0.0

GIF file:
charizard

from PIL import Image

filename = ''
output_png = ''
output_gif = ''
frames = Image.open(filename)
Image.open(filename).convert('RGBA').save(output_png)  # To test that it is indeed transparent per Frame
all_frames = []

for i in range(frames.n_frames):
    frames.seek(i)

    if i != 0:
        curr_frame = frames.convert('RGBA')
        disp_frame = Image.alpha_composite(prev_frames, curr_frame)   # I think the issue is here
    else:
        disp_frame = frames.convert('RGBA')

    all_frames.append(disp_frame)
    prev_frames = frames.convert('RGBA')

all_frames[0].save(output_gif, save_all=True, optimize=True, append_images=all_frames[:], loop=0)
@hugovk hugovk added the GIF label May 26, 2020
@caojianfeng
Copy link

try to add "transparency=0" in all_frames[0].save

@radarhere
Copy link
Member

Testing, I find that with #5333, the following code works.

all_frames = []

for i in range(frames.n_frames):
    frames.seek(i)

    all_frames.append(frames)

all_frames[0].save(output_gif, save_all=True, optimize=True, append_images=all_frames[:], loop=0, disposal=2)

That is, if I'm correct in thinking that you're just trying to recreate the GIF.

I presume you threw in alpha_composite because you understood GIFs to be made up of layers that then need to be combined. However, Pillow does this automatically when reading. This is also why I appended disposal=2 onto the end when saving, so that it would reset to the background color after each frame - Pillow has combined the frames, we don't need to have GIF do that in the final version.

@raygard
Copy link
Contributor

raygard commented Mar 18, 2021

@radarhere, a problem with your code above. I find the output GIF has 2256 (=47*48) frames. The original dragon has 47 frames. The output GIF has blown up to almost 14 MB (or 6 MB with my GIF LZW pull #5291). Seems each element of all_frames may contain the entire 47 frames, and the append_images adds all 47 all_frames to all_frames[0] -- maybe should append all_frames[1:] ?

I tried changing all_frames.append(frames) to all_frames.append(frames.copy()) but it produced a weird GIF -- when I opened it in Irfanview it did not run until I pressed Enter a couple times. I have not analyzed that yet.

On another note, I like the progress made with #5333. I am also trying to fix GifImagePlugin problems with animated GIFs. If you are working on other GIF animation issues I would like to avoid conflicts or duplicated effort. Not sure how to proceed.

Also hoping someone is trying out my GIF LZW pull #5291 (@wiredfool ?) and maybe it can get merged someday? I am new to this process. (My #5291 also needs a small change to its Gif.h to add the int transparency; line to work with your #5333.)

@radarhere
Copy link
Member

You're right, there are two mistakes in the code I posted. frames.copy() and [1:] are both good amends.

from PIL import Image
frames = Image.open('in.gif')
output_gif = 'out.gif'
all_frames = []

for i in range(frames.n_frames):
    frames.seek(i)
    disp_frame = frames.copy()
    all_frames.append(disp_frame)

all_frames[0].save(output_gif, save_all=True, optimize=True, append_images=all_frames[1:], loop=0, disposal=2)

I do not know what you mean by 'a weird GIF' though.

I wouldn't worry too much about duplicating efforts. Credit for #5333 should go to the author of #3434, so don't mistake my PR for a grand attempt on my part. If you're interested, and as you may have figured out yourself, I think the ultimate way forward for GIFs is to stop reading in images as P and start reading them in as RGB (for more detail, see #3735).

@raygard
Copy link
Contributor

raygard commented Mar 26, 2021

@radarhere, While working on the fixes I suggested to your sample code, I somehow managed to comment out the frames.seek(i), and that gave me the weird GIF.

I did know that the idea came from zewt in #3434, and it seems clever to me. I would not have thought of handling transparent pixels by just not reading them in.

I've been thinking about the entire thing of how to handle animated GIFs and it is a puzzler. Not sure about reading as RGB as you mention in #3735. Solves some problems and causes some problems, it seems. After you get > 256 colors, you need to quantize. Do you also need to dither? Is it best to try to recreate what a frame looks like as it appears when the animation runs? Will those images best "re-compose" into a proper animated GIF? Lotta questions...

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.

5 participants