Created
November 5, 2012 10:31
-
-
Save BigglesZX/4016539 to your computer and use it in GitHub Desktop.
Extract frames from an animated GIF, correctly handling palettes and frame update modes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
from PIL import Image | |
''' | |
I searched high and low for solutions to the "extract animated GIF frames in Python" | |
problem, and after much trial and error came up with the following solution based | |
on several partial examples around the web (mostly Stack Overflow). | |
There are two pitfalls that aren't often mentioned when dealing with animated GIFs - | |
firstly that some files feature per-frame local palettes while some have one global | |
palette for all frames, and secondly that some GIFs replace the entire image with | |
each new frame ('full' mode in the code below), and some only update a specific | |
region ('partial'). | |
This code deals with both those cases by examining the palette and redraw | |
instructions of each frame. In the latter case this requires a preliminary (usually | |
partial) iteration of the frames before processing, since the redraw mode needs to | |
be consistently applied across all frames. I found a couple of examples of | |
partial-mode GIFs containing the occasional full-frame redraw, which would result | |
in bad renders of those frames if the mode assessment was only done on a | |
single-frame basis. | |
Nov 2012 | |
''' | |
def analyseImage(path): | |
''' | |
Pre-process pass over the image to determine the mode (full or additive). | |
Necessary as assessing single frames isn't reliable. Need to know the mode | |
before processing all frames. | |
''' | |
im = Image.open(path) | |
results = { | |
'size': im.size, | |
'mode': 'full', | |
} | |
try: | |
while True: | |
if im.tile: | |
tile = im.tile[0] | |
update_region = tile[1] | |
update_region_dimensions = update_region[2:] | |
if update_region_dimensions != im.size: | |
results['mode'] = 'partial' | |
break | |
im.seek(im.tell() + 1) | |
except EOFError: | |
pass | |
return results | |
def processImage(path): | |
''' | |
Iterate the GIF, extracting each frame. | |
''' | |
mode = analyseImage(path)['mode'] | |
im = Image.open(path) | |
i = 0 | |
p = im.getpalette() | |
last_frame = im.convert('RGBA') | |
try: | |
while True: | |
print "saving %s (%s) frame %d, %s %s" % (path, mode, i, im.size, im.tile) | |
''' | |
If the GIF uses local colour tables, each frame will have its own palette. | |
If not, we need to apply the global palette to the new frame. | |
''' | |
if not im.getpalette(): | |
im.putpalette(p) | |
new_frame = Image.new('RGBA', im.size) | |
''' | |
Is this file a "partial"-mode GIF where frames update a region of a different size to the entire image? | |
If so, we need to construct the new frame by pasting it on top of the preceding frames. | |
''' | |
if mode == 'partial': | |
new_frame.paste(last_frame) | |
new_frame.paste(im, (0,0), im.convert('RGBA')) | |
new_frame.save('%s-%d.png' % (''.join(os.path.basename(path).split('.')[:-1]), i), 'PNG') | |
i += 1 | |
last_frame = new_frame | |
im.seek(im.tell() + 1) | |
except EOFError: | |
pass | |
def main(): | |
processImage('foo.gif') | |
processImage('bar.gif') | |
if __name__ == "__main__": | |
main() |
Hi James,
I am a beginner with python. I tried some things with the old Fireworks Console of John Dunning. But there are some bugs in it ( feather select
and so on).
This combination of Python and FW *.jsx works
But it is silly. So I try to do some things only with Python and try to optimize that. Yes you are right, there are newer packages, which work with
animated gifs (imageio e.g.), but I did not find out how to get the pixel-data with coordinates out from there. I'll see.
Thanks for your answer and have a nice day - here (Aachen-Germany) they are all mad about Corona-Virus :)
Best regards
Axel
Von: James Tiplady [mailto:[email protected]]
Gesendet: Donnerstag, 5. März 2020 17:37
An: BigglesZX
Cc: Axel Arnold Bangert; Comment
Betreff: Re: BigglesZX/gifextract.py
Hi Axel, that's a little outside of the scope of this code I'm afraid – I also haven't touched it in many years so there are probably better solutions
out there by now. moviepy is mentioned above, that might help. In any case I hope it was helpful.
You are receiving this because you commented.
Reply to this email directly
Hi Axel – that's great, always nice to hear from other people. Buying extra pasta in London today :). Good luck with your project and stay healthy! James
Hi James,
your extra pasta has been fine :) ? OK :) ... guess that this works. I found it at https://stackoverflow.com/questions/50054187/convert-animated-gif-to-4d-array-in-python
import numpy as np
from PIL import Image, ImageSequence
img = Image.open('test.gif')
frames = np.array([np.array(frame.copy().convert('RGB').getdata(),dtype=np.uint8).reshape(frame.size[1],frame.size[0],3) for frame in ImageSequence.Iterator(img)])
Best
Axel
Von: James Tiplady [mailto:[email protected]]
Gesendet: Freitag, 6. März 2020 12:46
An: BigglesZX
Cc: Axel Arnold Bangert; Comment
Betreff: Re: BigglesZX/gifextract.py
Hi Axel – that's great, always nice to hear from other people. Buying extra pasta in London today :). Good luck with your project and stay healthy! James
—
You are receiving this because you commented.
Due to PIL updates, to continue using this file as is this would be a possible solution
if not im.getpalette() and im.mode in ("L", "LA", "P", "PA"):
im.putpalette(p)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi Axel, that's a little outside of the scope of this code I'm afraid – I also haven't touched it in many years so there are probably better solutions out there by now. moviepy is mentioned above, that might help. In any case I hope it was helpful.