resize_gif.py 2.54 KB
Newer Older
1 2 3 4 5
from io import BytesIO
from PIL import Image


def resize_gif(mem, path, resize_to):
6
    frames, result = extract_and_resize_frames(mem, resize_to)
7 8 9 10 11 12 13 14

    if len(frames) == 1:
        frames[0].save(path, optimize=True)
    else:
        frames[0].save(path,
                       optimize=True,
                       save_all=True,
                       append_images=frames[1:],
15
                       duration=result['duration'],
16 17 18 19 20 21 22 23 24 25 26 27 28
                       loop=1000)


def analyse_image(mem):
    """
    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.
    """
    image = Image.open(BytesIO(mem))
    results = {
        'size': image.size,
        'mode': 'full',
29
        'duration': image.info.get('duration', 0)
30
    }
31

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
    try:
        while True:
            if image.tile:
                tile = image.tile[0]
                update_region = tile[1]
                update_region_dimensions = update_region[2:]
                if update_region_dimensions != image.size:
                    results['mode'] = 'partial'
                    break
            image.seek(image.tell() + 1)
    except EOFError:
        pass
    return results


def extract_and_resize_frames(mem, resize_to):
48
    result = analyse_image(mem)
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
    image = Image.open(BytesIO(mem))

    i = 0
    palette = image.getpalette()
    last_frame = image.convert('RGBA')

    frames = []

    try:
        while True:
            '''
            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 image.getpalette():
                image.putpalette(palette)

            new_frame = Image.new('RGBA', image.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.
            '''
75
            if result['mode'] == 'partial':
76 77 78 79
                new_frame.paste(last_frame)

            new_frame.paste(image, (0, 0), image.convert('RGBA'))

80
            # This method preservs aspect ratio
81 82 83 84 85 86 87 88 89
            new_frame.thumbnail(resize_to, Image.ANTIALIAS)
            frames.append(new_frame)

            i += 1
            last_frame = new_frame
            image.seek(image.tell() + 1)
    except EOFError:
        pass

90
    return frames, result