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 writing should use LZW encoding #5278

Closed
raygard opened this issue Feb 23, 2021 · 6 comments · Fixed by #5291
Closed

GIF writing should use LZW encoding #5278

raygard opened this issue Feb 23, 2021 · 6 comments · Fixed by #5291
Labels

Comments

@raygard
Copy link
Contributor

raygard commented Feb 23, 2021

What did you do?

Create a GIF

What did you expect to happen?

It should use LZW encoding, as most GIF applications do.

What actually happened?

It used Fredrik Lundh's cleverly compatible but suboptimal encoding from 1999.

What are your OS, Python and Pillow versions?

  • OS: Windows, Linux
  • Python: 3.7, 3.8, 3.9, any other
  • Pillow: Doesn't matter; does this on everything since 1999.

This isn't a bug report, it's an enhancement request. I'm pretty sure Fredrik did it this way to avoid the then-current LZW patent problems with Unisys. Those ended in 2004 after all Unisys patents on LZW expired. I'm puzzled why, 17 years later, LZW has been barely mentioned anywhere regarding GIFs from PIL. I see a mention in #617 and #4644. Both of these say GIFs are currently "uncompressed"; that's not quite right. Fredrik had a clever sort-of-run-length-encoding technique. I think using LZW still won't fix all the concerns in #617, I think PIL could use more work for animated GIFs but would need to look into that more.

I see in #617 "Filesize concerns" an invite from @wiredfool "If you’d like to contribute a gif compressor under the PIL license, go for it."

I have one ready to go, well tested at my end in Windows and Linux (WSL2 and Raspios). It's a mod to Gif.h and a major overhaul of GifEncode.c. A drop-in replacement that does not require any other changes.

PIL currently writes all GIFs as 256-color, with full 8 bits in the uncompressed pixels (as in tobytes()). My GifEncode.c writes them this way, but it is ready to go for shorter codes (smaller color tables) when the rest of PIL is (probably GifImagePlugin.py and others).

@raygard
Copy link
Contributor Author

raygard commented Apr 12, 2022 via email

@smccombie
Copy link

smccombie commented Apr 12, 2022

Holy smokes, yes that image works!

And thank you for all of that information, I need a second to digest everything. I'm really an amateur at this, although I did also notice that Python emits the application extension for looping for each frame, that doesn't seem necessary either, but yes, looks like you solved the problem.

for reference here is the significant portion of code, nothing spectacular:

img = []
for i in range(0, **LENGTH**):
  img[i] = **FUNCTION TO GET THE IMAGE**
buf = io.BytesIO()
img[0].save(buf, append_images = img[1:], comment = str(datetime.datetime.now()),
  duration = 1000, loop = 0x0, format = "GIF", optimize = False, save_all = True)

Digging through PIL now to see if I can control the disposal method... Or if I can call some underlying library, my issue is further complicated because I'm using Pyodide for Python, so hopefully there's a pure Python solution I can find... Otherwise, I have to patch the webassembly :(

@raygard
Copy link
Contributor Author

raygard commented Apr 13, 2022

@smccombie Scott, try just putting disposal=1 as an arg to .save(). I don't know how that will affect the size or if they'll look the way you want. Let us know how it works for you.

About the "NETSCAPE" looping application extension appearing each frame: yes, I've seen that, and it would be nice to be rid of it. But I did not see that in the sample bad animation you said came from PIL. How did that happen? Did you do something to remove the extra extensions? And, it is really supposed to appear right after the global color table. I may report this as an issue, but may try to fix it myself first.

@smccombie
Copy link

smccombie commented Apr 13, 2022

@raygard Good morning, so I tried that fix, turns out it's still not working with disposal = 1. I'm not sure why that original bad file has only 1 application extension but this new one has 5 (1 for each frame). Here's the latest produced, I tried to simplify the file a bit as well to make it easier to review. Any advice would be appreciated, thanks!!

bad

I also removed the extra application extensions and re-arranged the first one and see if that solves my problem, nope, still doesn't work :(

proposed

here it is working, re-saved with gimp:

proposed2

the file is significantly larger too, still seems unlikely that only 2 colors would inflate the size of the file by nearly 4K, the GCT would only account for 768 bytes of increase given the 8-bit depth that is always used, I'd have to read up on LZW compression, but seems like this is an ideal compression opportunity, here's ezgif using coalesce to de-optimize the image (full-frames for each frame) and factoring in the GCT size difference, seems like 2K of extra size is coming from somewhere

ezgif-coalesce

@raygard
Copy link
Contributor Author

raygard commented Apr 13, 2022

@smccombie I love a good mystery, don't you? Let's continue this as a discussion #6207, as I think it has nothing to do with this (LZW) issue. But I would like to get to the bottom of it...

@radarhere
Copy link
Member

Quick summary for anyone who visits this thread in the future - the above issues should have been fixed as of Pillow 9.2.0.

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.

3 participants