Gifs read with Pillow have black borders

Viewed 1048

I am reading gifs with Pillow, this code outputs every frame from a gif file as .gif and .png with and without converting to RGBA.

I'll discuss the issues below, after showing the results

from PIL import Image

# Download link below
image = Image.open('PepePls.gif')

loop = []
frames = []
durations = []
try:
    while True:
        loop.append(image.info.get('loop', None))
        frame = image.copy()
        frames.append(frame)
        durations.append(image.info.get('duration', None))
        image.seek(image.tell() + 1)
except EOFError:
    pass

for i in range(len(frames)):
    print(f'#{i} loop = {loop[i]} duration = {durations[i]} mode = {frames[i].mode}')
    frames[i].save(f'gif_f{i}.gif')
    frames[i].save(f'png_f{i}.png')
    frames[i].convert(mode='RGBA').save(f'rgba_gif_f{i}.gif')
    frames[i].convert(mode='RGBA').save(f'rgba_png_f{i}.png')

Results, console output then explorer.exe screenshot:

#0 loop = 0 duration = 70 mode = P
#1 loop = None duration = 70 mode = P
#2 loop = None duration = 70 mode = P
#3 loop = None duration = 70 mode = P
#4 loop = None duration = 70 mode = P
#5 loop = None duration = 70 mode = P
#6 loop = None duration = 70 mode = P
#7 loop = None duration = 70 mode = P
#8 loop = None duration = 70 mode = P
#9 loop = None duration = 70 mode = P
#10 loop = None duration = 70 mode = P
#11 loop = None duration = 70 mode = P
#12 loop = None duration = 70 mode = P
#13 loop = None duration = 70 mode = P
#14 loop = None duration = 70 mode = P
#15 loop = None duration = 70 mode = P
#16 loop = None duration = 70 mode = P
#17 loop = None duration = 70 mode = P
#18 loop = None duration = 70 mode = P
#19 loop = None duration = 70 mode = P
#20 loop = None duration = 70 mode = P

resulting frames

Issues:

  1. Why did rgba gifs lose the transparency? And where does the background color comes from

  2. Why are there black borders everywhere? I know from using GIMP a bit that gif frames can be combined or replace the previous one, does this explain what I'm seeing or is something else? (while on the topic, does Pillow handle combine/replace frames gifs on itw own?)

  3. Not sure what's happening with the image size, explorer.exe says 68x104 but frames saved by Pillow are all 85x112 (firefox also says 85x112 when viewing the input gif file)

Find PepePls.gif here

I have a similar issue with this one too

EDIT:

I am pretty sure after a lot of research and reading the Pillow source code https://github.com/python-pillow/Pillow/blob/master/src/PIL/GifImagePlugin.py that black borders are a bug, and it must be linked to the fact that each frame is drawn on a background of the inappropriate color (probably not checking the palette/color/transparency index at all)

Here's what I achieved replacing frame = image.copy() in the above code by

frame = Image.new('RGBA', (85, 112), color=(255,0,0,0))
frame.paste(image.crop(image.dispose_extent), box=(image.dispose_extent[0],image.dispose_extent[1]))

May want to use image.tile[0][1] instead of image.dispose_extent but I'm really not sure

new results

It also seems to show that Pillow doesn't like gifs with transparency at all

EDIT 2:

I fixed black borders and combine/replace frames by using this:

from PIL import Image

gif = 'PeepoCreepo.gif'
#gif = 'PepePls.gif'

image = Image.open(gif)

frames = []

disposal_method_last = 0

try:
    while True:
        disposal_method = disposal_method_last
        disposal_method_last = image.__dict__.get('disposal_method', 0)
        if disposal_method == 2 or (disposal_method == 1 and frames == []):
            frame = Image.new('RGBA', image.size, color=(255,0,0,0))
            frame.paste(image.crop(image.dispose_extent), box=(image.dispose_extent[0],image.dispose_extent[1]))
        elif disposal_method == 1:
            newStuff = image.crop(image.dispose_extent)
            frame = frames[-1].copy()
            frame.paste(newStuff, image.dispose_extent, newStuff.convert("RGBA"))
        else:
            frame = image.copy()
        frames.append(frame)
        image.seek(image.tell() + 1)
except EOFError:
    pass

for i in range(len(frames)):
    frames[i].convert(mode='RGBA').save(f'f{i}.png')

PeepoCreepo frames almost perfect

One issue I still see is if you zoom on f2.png you can see some of the black in the eyes is missing. This is not an issue from the gif file, as gimp can open and display each frame properly:

PeepoCreepo gif frames in gimp

EDIT3:

Opened an issue on Github

EDIT4:

The Github issue has been marked as fixed!

1 Answers

This is a known bug in PIL dating back to at least February 2018, so I wouldn`t expect any fixes in the foreseeable future.

One of the possible solutions is to use an external GIF library such as mine.

Related