Gusepo
Gusepo

Reputation: 889

Get color palette from animated GIF with Python PIL

I'd like to get the color palette of each frame of an animated GIF (I'm using ColorThief to generate color palette from an image). I'm using PIL to save each frame as an image, my problem is that the images of most of the frames have strange colors, different from the one you see if you open the GIF on a browser. I guess this is because of the way animated GIF are compressed. I tried to convert every image in RGB but this doesn't solve the problem. How can I render each frame with its intended colors?

To get every frame I'm using the following code:

from PIL import Image, ImageSequence

im = Image.open("phicons/icontech-3-2019-1.gif")


index = 1
for frame in ImageSequence.Iterator(im):

    frame.save("phicons/frame%d.gif" % index)
    print(frame)
    index += 1

This is the image I'm using for test

Animated GIF example

This is an example of frame with strange colors

enter image description here

Upvotes: 1

Views: 3344

Answers (3)

spike
spike

Reputation: 21

You can try Python libary moviepy.

from moviepy.editor import VideoFileClip
import cv2
import numpy as np

clip = VideoFileClip('sample.gif')
idx = 0
for frame in clip.iter_frames(): # frame is in rgb format
    cv2.imwrite('sample-{}.png'.format(idx), frame[:,:,::-1])

Upvotes: 2

fmatheis
fmatheis

Reputation: 241

The problem seems to be a bug at PIL at the class GifImageFile, when the palette is normalized at _normalize_palette it is not taking into account the palette of the current frame:

If you add the following code, it solves the problem:

def _normalize_palette(im, palette, info):

    source_palette = None

    # fmatheis, note that I am initializing the variable here to solve the bug
    if im.palette and im.palette.palette:
        source_palette = im.palette.palette[:768]

    if palette:
        # a bytes palette
        if isinstance(palette, (bytes, bytearray, list)):
            source_palette = bytearray(palette[:768])
        if isinstance(palette, ImagePalette.ImagePalette):
            source_palette = bytearray(
                itertools.chain.from_iterable(
                    zip(
                        palette.palette[:256],
                        palette.palette[256:512],
                        palette.palette[512:768],
                    )
                )
            )

    if im.mode == "P":
        if not source_palette:
            source_palette = im.im.getpalette("RGB")[:768]
    else:  # L-mode
        if not source_palette:
            source_palette = bytearray(i // 3 for i in range(768))
        im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)

    used_palette_colors = GifImagePlugin._get_optimize(im, info)
    if used_palette_colors is not None:
        return im.remap_palette(used_palette_colors, source_palette)

    im.palette.palette = source_palette
    return im


#You can patch the code like this
from PIL import GifImagePlugin
GifImagePlugin._normalize_palette = _normalize_palette

Upvotes: 1

Mark Setchell
Mark Setchell

Reputation: 207355

This looks like a bug in PIL. It appears not to notice/realise each frame can potentially have a different palette, and indeed, does so in this image.

If you look at the image with ImageMagick you can see it has 54 frames, some with 8, some with 16, some with 32 and some with 64 colours.

identify anim.gif

Output

anim.gif[0] GIF 483x483 483x483+0+0 8-bit sRGB 16c 0.000u 0:00.002
anim.gif[1] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[2] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[3] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[4] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[5] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[6] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[7] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[8] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[9] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[10] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[11] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[12] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[13] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[14] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[15] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[16] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[17] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[18] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[19] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[20] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[21] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[22] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.003
anim.gif[23] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.002
anim.gif[24] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.002
anim.gif[25] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[26] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[27] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[28] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[29] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[30] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[31] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[32] GIF 483x483 483x483+0+0 8-bit sRGB 8c 0.000u 0:00.002
anim.gif[33] GIF 483x483 483x483+0+0 8-bit sRGB 8c 0.000u 0:00.002
anim.gif[34] GIF 483x483 483x483+0+0 8-bit sRGB 8c 0.000u 0:00.002
anim.gif[35] GIF 483x483 483x483+0+0 8-bit sRGB 8c 0.000u 0:00.002
anim.gif[36] GIF 483x483 483x483+0+0 8-bit sRGB 8c 0.000u 0:00.002
anim.gif[37] GIF 483x483 483x483+0+0 8-bit sRGB 8c 0.000u 0:00.002
anim.gif[38] GIF 483x483 483x483+0+0 8-bit sRGB 32c 0.000u 0:00.002
anim.gif[39] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[40] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[41] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[42] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[43] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[44] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[45] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[46] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[47] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[48] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[49] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[50] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[51] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[52] GIF 483x483 483x483+0+0 8-bit sRGB 64c 0.000u 0:00.002
anim.gif[53] GIF 483x483 483x483+0+0 8-bit sRGB 16c 383348B 0.000u 0:00.002

And, if you extract all the frames and montage them together with ImageMagick:

for ((f=0; f<54; f++)) ; do 
   convert anim.gif[$f] miff:-
done | montage miff:- -geometry +5+5 result.png

enter image description here

If you want to see the palettes of each frame, use:

identify -verbose anim.gif

As an alternative, you may like to have a look at wand which is a Python binding to ImageMagick. You can extract your frames correctly like this:

#!/usr/bin/env python3

from wand.image import Image

with Image(filename='anim.gif') as img: 
    for n in range(len(img.sequence)):
        frame = img.sequence[n]
        i = Image(image=frame)
        i.save(filename=f'f-{n:02}.gif')

Upvotes: 2

Related Questions