-
-
Save plallin/46bb8ddec8a1c3279c24482ae48a1e06 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
from PIL import Image | |
''' | |
Based on a script by BigglesZX: https://gist.github.com/BigglesZX/4016539 | |
BigglesZX was adapted as follows: | |
- Updated to be compatible with Python 3. | |
- The original function 'processImage' was renamed 'extract_and_resize_frames' and was adapted as follows: | |
- It resizes each frames as it extracts them | |
- It saves all the frames to an array | |
- It returns the array of all frames | |
- a function 'resize_gif' was added which calls 'extract_and_resize_frames' to extract all the GIF frames and then | |
saves all frames to an output file. | |
- the function main() was modified to call 'resize_gif' | |
Functionality of the current script: | |
- Similar functionality to BigglesZX's original script | |
- Extracts all frames from a GIF and returns as array of all frames | |
- Resizes the GIF to a given size | |
JAN 2017 | |
''' | |
def resize_gif(path, save_as=None, resize_to=None): | |
""" | |
Resizes the GIF to a given length: | |
Args: | |
path: the path to the GIF file | |
save_as (optional): Path of the resized gif. If not set, the original gif will be overwritten. | |
resize_to (optional): new size of the gif. Format: (int, int). If not set, the original GIF will be resized to | |
half of its size. | |
""" | |
all_frames = extract_and_resize_frames(path, resize_to) | |
if not save_as: | |
save_as = path | |
if len(all_frames) == 1: | |
print("Warning: only 1 frame found") | |
all_frames[0].save(save_as, optimize=True) | |
else: | |
all_frames[0].save(save_as, optimize=True, save_all=True, append_images=all_frames[1:], loop=1000) | |
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 extract_and_resize_frames(path, resize_to=None): | |
""" | |
Iterate the GIF, extracting each frame and resizing them | |
Returns: | |
An array of all frames | |
""" | |
mode = analyseImage(path)['mode'] | |
im = Image.open(path) | |
if not resize_to: | |
resize_to = (im.size[0] // 2, im.size[1] // 2) | |
i = 0 | |
p = im.getpalette() | |
last_frame = im.convert('RGBA') | |
all_frames = [] | |
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.thumbnail(resize_to, Image.ANTIALIAS) | |
all_frames.append(new_frame) | |
i += 1 | |
last_frame = new_frame | |
im.seek(im.tell() + 1) | |
except EOFError: | |
pass | |
return all_frames | |
def main(): | |
resize_gif('foo.gif', save_as='bar.gif') | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
you have to copy value from
im.info['duration']
tonew_frame.info['duration']