Pillow: How to add text to GIF?

Created on 12 May 2018  路  6Comments  路  Source: python-pillow/Pillow

How Can I add a custom text on an animated gif file?

Question

Most helpful comment

I was surprised that I couldn't find a simpler way, but here is what I came up with -

from PIL import Image, ImageDraw, ImageSequence
import io

im = Image.open('Tests/images/iss634.gif')

# A list of the frames to be outputted
frames = []
# Loop over each frame in the animated image
for frame in ImageSequence.Iterator(im):
    # Draw the text on the frame
    d = ImageDraw.Draw(frame)
    d.text((10,100), "Hello World")
    del d

    # However, 'frame' is still the animated image with many frames
    # It has simply been seeked to a later frame
    # For our list of frames, we only want the current frame

    # Saving the image without 'save_all' will turn it into a single frame image, and we can then re-open it
    # To be efficient, we will save it to a stream, rather than to file
    b = io.BytesIO()
    frame.save(b, format="GIF")
    frame = Image.open(b)

    # Then append the single frame image to a list of frames
    frames.append(frame)
# Save the frames as a new image
frames[0].save('out.gif', save_all=True, append_images=frames[1:])

See the following parts of the docs if you would like more information -

All 6 comments

I was surprised that I couldn't find a simpler way, but here is what I came up with -

from PIL import Image, ImageDraw, ImageSequence
import io

im = Image.open('Tests/images/iss634.gif')

# A list of the frames to be outputted
frames = []
# Loop over each frame in the animated image
for frame in ImageSequence.Iterator(im):
    # Draw the text on the frame
    d = ImageDraw.Draw(frame)
    d.text((10,100), "Hello World")
    del d

    # However, 'frame' is still the animated image with many frames
    # It has simply been seeked to a later frame
    # For our list of frames, we only want the current frame

    # Saving the image without 'save_all' will turn it into a single frame image, and we can then re-open it
    # To be efficient, we will save it to a stream, rather than to file
    b = io.BytesIO()
    frame.save(b, format="GIF")
    frame = Image.open(b)

    # Then append the single frame image to a list of frames
    frames.append(frame)
# Save the frames as a new image
frames[0].save('out.gif', save_all=True, append_images=frames[1:])

See the following parts of the docs if you would like more information -

Thank you! 馃槂

Works like a charm. But if I try to save the final result into a BytesIO, I always get an empty file:

my_bytes = BytesIO()
frames[0].save(my_bytes, format='gif', save_all=True, append_images=frames[1:])

And I need to place a white text on the image. If I use the fill=(255,255,255), it gives me an error:

ValueError: unrecognized raw mode

I would conclude that you need to fill with the appropriate value for the mode. So since the image I used is in P mode, a fill value of 251 will show you a difference.

However, you probably will want to convert the image to RGB - which I've found incidentally also changes the image away from being multiple frames. So the temporary save operation is no longer required.

Here is code for that, as well as saving the final result into BytesIO. Let me know if it does not work for you.

from PIL import Image, ImageDraw, ImageSequence
import io

im = Image.open('Tests/images/iss634.gif')

frames = []
for frame in ImageSequence.Iterator(im):
    frame = frame.convert('RGB')

    d = ImageDraw.Draw(frame)
    d.text((10,100), "Hello World", fill=(255,255,255))
    del d

    frames.append(frame)
my_bytes = io.BytesIO()
frames[0].save(my_bytes, format="GIF", save_all=True, append_images=frames[1:])
print(my_bytes.getvalue())

This one gave me an error unfortunately:

  File "C:\Users\Steve\AppData\Local\Programs\Python\Python36-32\lib\site-packages\PIL\Image.py", line 1935, in save
    save_handler(self, fp, filename)
  File "C:\Users\Steve\AppData\Local\Programs\Python\Python36-32\lib\site-packages\PIL\GifImagePlugin.py", line 456, in _save_all
    _save(im, fp, filename, save_all=True)
  File "C:\Users\Steve\AppData\Local\Programs\Python\Python36-32\lib\site-packages\PIL\GifImagePlugin.py", line 468, in _save
    if not save_all or not _write_multiple_frames(im, fp, palette):
  File "C:\Users\Steve\AppData\Local\Programs\Python\Python36-32\lib\site-packages\PIL\GifImagePlugin.py", line 451, in _write_multiple_frames
    _write_frame_data(fp, im_frame, offset, frame_data['encoderinfo'])
  File "C:\Users\Steve\AppData\Local\Programs\Python\Python36-32\lib\site-packages\PIL\GifImagePlugin.py", line 728, in _write_frame_data
    _write_local_header(fp, im_frame, offset, 0)
  File "C:\Users\Steve\AppData\Local\Programs\Python\Python36-32\lib\site-packages\PIL\GifImagePlugin.py", line 494, in _write_local_header
    transparency = int(transparency)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'

Actually changing the mode to RGBA solved my problem. 馃槂 Works like a charm now. Thanks for all your amazing help!

And for the BytesIO thing, it was a noob thing,,, I forgot to do a seek(0)...

For the record, I've since realised a simpler way of converting a multiframe image into a single frame image - im = im.copy().

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nomarek picture nomarek  路  3Comments

FlowerCode picture FlowerCode  路  4Comments

vytisb picture vytisb  路  4Comments

boskicthebrain picture boskicthebrain  路  4Comments

naaaargle picture naaaargle  路  3Comments