Instantly share code, notes, and snippets.

Embed
What would you like to do?
Extract frames from an animated GIF, correctly handling palettes and frame update modes
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()
@clickinfinite

This comment has been minimized.

clickinfinite commented Nov 13, 2017

hi, it seems that the duration info missed. how to keep the origin duration?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment