Animated cartoon Gif with PIL and Python 1

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.

One

Two

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.

Png to animated Gif with Python

This is the video about making a gif with png

This is the output of the example

Published by pythonprogramming

Started with basic on the spectrum, loved javascript in the 90ies and python in the 2000, now I am back with python, still making some javascript stuff when needed.