2

I am trying to resize and crop an image in Django. I'm trying to add support for GIF images.

The majority works just but some happen to have an unexpected behavior. Some peculiar GIF files have strange glitchy patterns appearing on them.

Example:

Initial gif

Initial GIF

Processed gif

Processed gif

from PIL import Image

# crop_dim : (x, y, width, height)
# resize_dim : (width, height)
# path : save path
# image : image object
def crop_animated_image(crop_dim, resize_dim, path, image):

   crop_dim = (crop_dim[0], crop_dim[1], crop_dim[2] + crop_dim[0], crop_dim[3] + crop_dim[1])

   frames = crop_and_resize_frames(crop_dim, resize_dim, image)

   frames[0].info = image.info
   if image.get_format_mimetype() == "image/webp":
      frames[0].save(path, optimize=True, save_all=True, append_images=frames[1:], loop=0)
   elif image.get_format_mimetype() == "image/gif" or image.get_format_mimetype() == "image/apng":
      if len(list(frames)) == 1:
         frames[0].save(path, optimize=True)
      else:
         frames[0].save(path, optimize=True, save_all=True, append_images=frames[1:], loop=0, duration=image.info['duration'])


def crop_and_resize_frames(crop_dim, resize_dim, image):
frames = []
last_frame = image.convert('RGBA')
palette = image.getpalette()
try:
    while True:
        if not image.getpalette():
            image.putpalette(palette)

        new_frame = Image.new('RGBA', image.size)
        new_frame.paste(last_frame)
        new_frame.paste(image, (0, 0), image.convert('RGBA'))

        resized_frame = new_frame.crop(crop_dim)
        resized_frame = resized_frame.resize(resize_dim, Image.ANTIALIAS)

        frames.append(resized_frame)
        last_frame = new_frame
        image.seek(image.tell() + 1)

except EOFError:
    pass
return frames

I also used a slightly adapted solution of that post but the problem was the same.

Here is the result of the magick identify command on the source image :

glitchy.gif[0] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.003
glitchy.gif[1] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.006
glitchy.gif[2] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.008
glitchy.gif[3] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.010
glitchy.gif[4] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.013
glitchy.gif[5] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.015
glitchy.gif[6] GIF 34x17 667x373+366+183 8-bit sRGB 32c 0.000u 0:00.017
glitchy.gif[7] GIF 34x23 667x373+366+177 8-bit sRGB 32c 0.000u 0:00.020
glitchy.gif[8] GIF 34x23 667x373+366+177 8-bit sRGB 32c 0.000u 0:00.022
glitchy.gif[9] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.024
glitchy.gif[10] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.027
glitchy.gif[11] GIF 572x373 667x373+95+0 8-bit sRGB 128c 0.000u 0:00.029
glitchy.gif[12] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.032
glitchy.gif[13] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.034
glitchy.gif[14] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.036
glitchy.gif[15] GIF 625x330 667x373+42+43 8-bit sRGB 256c 0.000u 0:00.039
glitchy.gif[16] GIF 639x373 667x373+28+0 8-bit sRGB 256c 0.000u 0:00.041
glitchy.gif[17] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.044
glitchy.gif[18] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.046
glitchy.gif[19] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.048
glitchy.gif[20] GIF 558x373 667x373+109+0 8-bit sRGB 256c 0.000u 0:00.051
glitchy.gif[21] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.053
glitchy.gif[22] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.055
glitchy.gif[23] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.058
glitchy.gif[24] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.060
glitchy.gif[25] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.062
glitchy.gif[26] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.065
glitchy.gif[27] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.068
glitchy.gif[28] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.000u 0:00.070
glitchy.gif[29] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.016u 0:00.073
glitchy.gif[30] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.016u 0:00.075
glitchy.gif[31] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.016u 0:00.077
glitchy.gif[32] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.016u 0:00.079
glitchy.gif[33] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.016u 0:00.081
glitchy.gif[34] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.016u 0:00.084
glitchy.gif[35] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.016u 0:00.088
glitchy.gif[36] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.016u 0:00.091
glitchy.gif[37] GIF 667x373 667x373+0+0 8-bit sRGB 256c 0.016u 0:00.093
glitchy.gif[38] GIF 636x373 667x373+31+0 8-bit sRGB 256c 1.93615MiB 0.016u 0:00.096

It does not work either if I remove the crop and resize part of my code. Do you have any clue of what happen? Do you know how to fix it?

Thank you in advance for you help!

Br00k
  • 21
  • 4

1 Answers1

2

The answer is simple.

To achieve better compression, GIFs do not always store whole frames; instead, any given GIF frame may be a patchwork that heavily relies on previous frames, so that only the pixels actually altered are stored whereas the rest is transparent.

This is exactly the case with your GIF. You need to merge frames together manually to get the expected result.

hidefromkgb
  • 5,834
  • 1
  • 13
  • 44
  • Thanks. I tried to implement what you said. The result is somewhat better but artefacts are present. – Br00k Dec 23 '20 at 14:44
  • @Br00k Are you sure you interpret frame disposal flags correctly? – hidefromkgb Dec 23 '20 at 16:06
  • I don't understand what you mean. I updated the post so you can check. – Br00k Dec 24 '20 at 06:55
  • @Br00k As far as I can tell, there is nothing exceptionally wrong with your code. The problem is, PIL is itself broken in almost anything GIF related — so much broken I even made my own Python-compatible GIF decoder. – hidefromkgb Dec 25 '20 at 15:26