Talking of Gifs (animated gifs)
Today we want to take a further step with our journey around PIL the Python module to manipulate images. Lately we have made some scripts to create animated gifs. I invite you to take a look at those posts.
The aim of this new project is to create a sort of animated cartoon. In this post we will make something very basic.
At the end of this post, you will learn how you can get to this.
Were we were?
The code that is the starting point for the new code that I want to show you today is this (taken from the last post about this topic).
from PIL import Image, ImageDraw, ImageFont def create_image_with_text(size, text): img = Image.new('RGB', (600, 50), "yellow") draw = ImageDraw.Draw(img) draw.text((size[0], size[1]), text, font = fnt, fill="black") return img # Create the frames frames = [] def roll(text): for i in range(len(text)+1): new_frame = create_image_with_text((0,0), text[:i]) frames.append(new_frame) fnt = ImageFont.truetype("arial", 36) all_text = """ Pythonprogramming Brought you this code ................. Hi folks This text was made with PIL and Python Great Job """.splitlines() [roll(text) for text in all_text] # Save into a GIF file that loops forever frames[0].save('banner1.gif', format='GIF', append_images=frames[1:], save_all=True, duration=80, loop=0) print("Done") HTML("<img src='banner1.gif'>")
The result of this code was this:
The next step towards something a little different
Starting from here I thought to add a speech/script from Romeo and Juliet and try to make it nicely readable on the banner, to see if it could be interesting to make something with a ‘message’ in it.
The first attempt of a little interest was this:
from PIL import Image, ImageDraw, ImageFont from IPython.display import display, HTML import os def create_image_with_text(size, text): img = Image.new('RGB', (600, 50), "yellow") draw = ImageDraw.Draw(img) draw.text((size[0], size[1]), text, font = fnt, fill="black") return img # Create the frames frames = [] def roll(text): if text == "BEN...": text = "[ [ [ - - - BEN - - - ] ] ]" if text == "ROMEO...": text = "[ [ [ - - - ROMEO - - - ] ] ]" for i in range(len(text)+1): new_frame = create_image_with_text((0,0), text[:i]) frames.append(new_frame) fnt = ImageFont.truetype("arial", 36) TXT = """ _ __ ___ From Romeo & Juliet _ __ ___ BEN... Good morrow, cousin. ROMEO... the day so young? BEN... But new struck nine. ROMEO... Ay me! sad hours seem long. Was that my father that went hence so fast? BEN... It was. What sadness lengthens Romeo's hours? ROMEO... Not having that which having makes them short. BEN... In love? ROMEO... Out BEN... Of love? ROMEO... Out of her favour where I am in love. BEN... Alas that love, so gentle in his view, Should be so tyrannous and rough in proof! """.splitlines() # text is a line of all text and it is passed to roll2 [roll(text) for text in TXT] # Save into a GIF file that loops forever frames[0].save('banner_a.gif', format='GIF', append_images=frames[1:], save_all=True, duration=60, loop=0) os.startfile("banner_a.gif")
The result was not very different from the previous.
As you can see, I tried to put into evidence who was speaking, but it is not so intuitive and you cannot know for sure who’s talking. So I thought it was time to add images and I thought to the following solution to automate the process, so that I could just worry about the text and not about the images.
from PIL import Image, ImageDraw, ImageFont from IPython.display import display, HTML import os color = "green" def create_text(size, text): global color img = Image.new('RGB', (600, 50), "yellow") draw = ImageDraw.Draw(img) draw.text((size[0], size[1]), text, font = fnt, fill=color) return img def image(image, size, text): global color if stuck == "romeo": color = "blue" elif stuck == "benvolio": color = "darkred" else: color = "darkgreen" img = Image.new('RGB', (600, 50), "yellow") img2 = Image.open(image) img.paste(img2,(-0,-0)) draw = ImageDraw.Draw(img) draw.text((size[0]+100, size[1]), text, font = fnt, fill=color) return img def text_pause(img,text): # If first character is empty the pause is twice pause = 10 if len(text) > 0: if text[0] == " " and len(text) > 2: pause = 30 * text[:3].count(" ") for i in range(pause): new_frame = image(img, (0,0), text) frames.append(new_frame) # Create the frames frames = [] stuck = "" def roll(text): global stuck if text == "ROMEO...": stuck = "romeo" elif text == "BENVOLIO...": stuck = "benvolio" else: if stuck == "romeo": for i in range(len(text)+1): new_frame = image("romeo.png", (0,0), text[:i]) frames.append(new_frame) text_pause("romeo.png", text) elif stuck == "benvolio": for i in range(len(text)+1): new_frame = image("benvolio.png",(0,0), text[:i]) frames.append(new_frame) text_pause("benvolio.png", text) else: for i in range(len(text)+1): new_frame = create_text((0,0), text[:i]) frames.append(new_frame) fnt = ImageFont.truetype("arial", 36) # a space before the text means a pause, 2 or 3 spaces a longer one TXT = """ A scene from Romeo & Juliet BENVOLIO... Enter Benvolio ROMEO... enter Romeo BENVOLIO... Good morrow, cousin. ROMEO... the day so young? BENVOLIO... But new struck nine. ROMEO... Ay me! sad hours seem long. Was that my father that went hence so fast? BENVOLIO... It was. What sadness lengthens Romeo's hours? ROMEO... Not having that which having makes them short. BENVOLIO... In love? ROMEO... Out BENVOLIO... Of love? ROMEO... Out of her favour where I am in love. BENVOLIO... Alas that love, so gentle in his view, Should be so tyrannous and rough in proof! ROMEO... """.splitlines() # text is a line of all text and it is passed to roll2 [roll(text) for text in TXT] # Save into a GIF file that loops forever frames[0].save('cartoon10.gif', format='GIF', append_images=frames[1:], save_all=True, duration=45, loop=0) os.startfile("cartoon10.gif")
The result was this (version 10)
That’s why they say that an image is worth a thousand word. If you want to change the speed, change the duration in the last lines of code. The smaller the duration is the quicker the animation goes. If you want to make the images of Romeo and Benvolio for more time at the end of the sentence, change the for loop range in still_image. Have fun changing the code.
30/07/2019
… to be continued?
The video showing the different versions
I haven’t yet done a video that explains all the code line by line, but I want to make it as soon as I can. In the next one I explain some parts of the process that carried me throught this code.
Experiments
In this banner I tryed some movements to make it more attractive. I don’t know if it is an annoying effect after a while. The weight of this is 3 mb.
New Images (the time of duration is 57 sec. and it occupies 252 kb)
Romeo and Benvolio
Romeo and the scene of the balcony
This time I added a little sign “>” to check at what point of the scene we are… maybe it works?
This is the code, followed by the output
from PIL import Image, ImageDraw, ImageFont from IPython.display import display, HTML import os color = "yellow" def create_text(size, text): global color img = Image.new('RGB', (700, 200), "black") draw = ImageDraw.Draw(img) draw.text((size[0], size[1]+10), text, font = fnt, fill=color) return img iromeo = Image.open("romeo.png") ibenvolio = Image.open("benvolio.png") def image(image, size, text): global color, over40, fcount if stuck == "romeo": color = "blue" elif stuck == "benvolio": color = "darkred" else: color = "darkgreen" img = Image.new('RGB', (700, 200), "yellow") img2 = Image.open(image) img.paste(img2,(30,25)) draw = ImageDraw.Draw(img) draw.text((size[0]+50, size[1]+10), text, font = fnt, fill=color) draw.text((int(fcount/5), 180), ">" , font = fnt, fill=color) return img def text_pause(img,text): # If first character is empty the pause is twice global fcount pause = 10 if len(text) > 0: if text[0] == " " and len(text) > 2: pause = 30 * text[:3].count(" ") for i in range(pause): new_frame = image(img, [30,0], text) fcount += 1 frames.append(new_frame) # Create the frames frames = [] stuck = "" fcount = 0 def roll(text, *actors): global stuck, fcount if text[2:6] == "Rom.": stuck = "romeo" elif text == "BENVOLIO...": stuck = "benvolio" else: if stuck == "romeo": for i in range(len(text)+1): new_frame = image("romeo.png", [30,0], text[:i]) fcount += 1 frames.append(new_frame) text_pause("romeo.png", text) elif stuck == "benvolio": for i in range(len(text)+1): new_frame = image("benvolio.png",[300,0], text[:i]) fcount += 1 frames.append(new_frame) text_pause("benvolio.png", text) else: for i in range(len(text)+1): new_frame = create_text([0,10], text[:i]) frames.append(new_frame) fcount += 1 for n in range(5): # This makes the text stand at the end for a sec new_frame = create_text([0,10], text) fcount += 1 frames.append(new_frame) fnt = ImageFont.truetype("arial", 24) # a space before the text means a pause, 2 or 3 spaces a longer one TXT = """ Scene II. Capulet's orchard. Enter Romeo. Rom. He jests at scars that never felt a wound. Enter Juliet above at a window. But soft! What light through yonder window breaks? It is the East, and Juliet is the sun! Arise, fair sun, and kill the envious moon, Who is already sick and pale with grief That thou her maid art far more fair than she. Be not her maid, since she is envious. Her vestal livery is but sick and green, And none but fools do wear it. Cast it off. It is my lady; O, it is my love! O that she knew she were! She speaks, yet she says nothing. What of that? Her eye discourses; I will answer it. I am too bold; 'tis not to me she speaks. Two of the fairest stars in all the heaven, Having some business, do entreat her eyes To twinkle in their spheres till they return. What if her eyes were there, they in her head? The brightness of her cheek would shame those stars As daylight doth a lamp; her eyes in heaven Would through the airy region stream so bright That birds would sing and think it were not night. See how she leans her cheek upon her hand! O that I were a glove upon that hand, That I might touch that cheek! """.splitlines() # text is a line of all text and it is passed to roll2 for text in TXT: roll(text) print(f"Numero di frame: {fcount}") # Save into a GIF file that loops forever frames[0].save('cartoon16.gif', format='GIF', append_images=frames[1:], save_all=True, duration=45, loop=0) os.startfile("cartoon16.gif")
But… let’s check a video of this. In another post we will see how to convert a gif in a video.
Convert gif to mp4 with ffmpeg
Having installed ffmpeg, you can open the cmd and write
ffmpeg -r 30 -i input.gif -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" output.mp4
The result is this (253 kb):
If you want to make it go slower write 15 instead of 30 and you’ll get this (283 kb… 30kb more and 1:34 minutes, instead of 0:47.
Link to ffmpeg documentation about this command to convert gif to mp4
Convert mp4 to gif with Python and imageio
After you have installed imageio
pip install imageio
and
pip install imageio-ffmpeg
you can convert a git to mp4 with the following code:
# from gif to mp4 import imageio import os fpath = os.path.abspath("romeo18_15.mp4") def convert(i, fmt): o = os.path.splitext(i)[0] + fmt reader = imageio.get_reader(i) fps = reader.get_meta_data()["fps"] writer = imageio.get_writer(o, fps=fps) for f in reader: writer.append_data(f) print(f) print(f"{o} File created") writer.close() convert(fpath, ".gif")
In this case the gif output was heavy: 27 mb, while the mp4 was just 283 kb.
Animated gif from png files (another approach)
We have talked about this topic in another post, having a different approach. Here is a video that explains how to make a gif out of png files.
This is the video about making a gif with png
This is the output of the example