Search code examples
pythonpython-imaging-librarygifanimated-gif

PIL adding text to a gif frames adds noise to the picture


I'm creating a simple GIF animation using PIL:

from PIL import Image, ImageDraw, ImageFont

images = []

for x, i in enumerate(range(10)):
    image = Image.new(mode="RGB", size=(320, 60), color="orange")

    draw = ImageDraw.Draw(image)
    fnt = ImageFont.truetype('font.ttf', size=10)
    draw.text((10, 10), ("%s" % x), fill=(0, 0, 255), font=fnt)
    images.append(image)

images[0].save("result/pil.gif", save_all=True, append_images=images[1:], duration=1000, loop=0, format="GIF")

The problem is that whenever I use Draw.text, image's background is getting some kind of white noze:

enter image description here

I found some info that I have to use getpalette from the first frame and putpalette for all the other frames like this:

for x, i in enumerate(range(10)):
    image = Image.new(mode="RGB", size=(320, 60), color="orange")

    if x == 0:
        palette = image.getpalette()
    else:
        image.putpalette(palette)

But it just gives me: ValueError: illegal image mode.

What's the reason of the noizy background and how can I fix it?

UPD I was able to fix the background by changing image mode to "P", but in this case my fonts became unreadable. These are examples with RGB mode (fonts are well) and P mode (fonts are awful):

enter image description here enter image description here

Why am I getting either nice background or nice fonts but not both? Is there a workaround?


Solution

  • This is dithering that happens, because gif can contain only colors from palette of size 256. Most likely PIL uses very basic algorithm to convert from RGB format to indexed format, which is required by gif. As your image contains colors #ff9900 and #ffcc00, then palette presumably consists of hex values 00, 33, 66, 99, cc, ff for each byte and has size 6x6x6 = 216, which fits nicely into 256 possible values. 'orange' has value of #ffa500 and can't be represented by such palette, so the background is filled by nearest available colors.

    You can try to use color '#ff9900' instead of 'orange'. Hopefully this color can be represented by palette, as it is present in your noisy image.

    You can also try to convert from RGB to indexed format using your own palette as an argument in quantize method, as suggested in this answer. Adding the following line results in nice solid background:

    image = image.quantize(method=Image.MEDIANCUT)
    

    enter image description here

    Or you can just save RGB image with PNG format. In this case it will be saved as APNG image.

    images[0].save("pil.png", save_all=True, append_images=images[0:],duration=1000, loop=0, format="PNG")
    

    enter image description here