Les animations

\(\newcommand{\ds}{\displaystyle}\) \(\newcommand{\Frac}{\ds\frac}\) \(\renewcommand{\r}{\mathbb{ R}}\) \(\newcommand{\C}{\mathbb{ C}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\z}{\mathbb{ Z}}\) \(\newcommand{\Q}{\mathbb{ Q}}\) \(\newcommand{\N}{\mathbb{ N}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\ol}{\overline}\) \(\newcommand{\abs}[1]{\left| \,{#1} \right|}\) \(\newcommand{\pv}{\;;\;}\) \(\newcommand{\ens}[1]{\left\{ {#1} \right\}}\) \(\newcommand{\mens}[1]{\setminus\left\{ {#1} \right\}}\) \(\newcommand{\Par}[1]{\left({#1}\right)}\) \(\newcommand{\pe}[1]{\left\lfloor {#1} \right\rfloor}\)

Les animations

La méthode after

La technique pour créer des animations en Tkinter est basée sur l’usage de la méthode after. Il s’agit d’une méthode associée à un widget et permettant d’appeler avec un certain délai (ou même périodiquement) l’exécution d’une fonction qui se charge d’animer (en déplaçant ou supprimant des objets, en en changeant les propriétés, ou la vitesse de déplacement, etc).

Voici un exemple très simplifié permettant de voir comment fonctionne et s’utilise la méthode `after`

../../../_images/after_simple.gif

Quand l’interface s’ouvre, on observe un rectangle rouge et au bout d’une seconde, un rectangle bleu est ajouté.

Le code corresponsant est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from tkinter import Tk, Canvas

root = Tk()
cnv = Canvas(root, width=180, height=100)
cnv.pack()

cnv.create_rectangle(20, 20, 80, 80, fill='red', outline='')

def dessiner(c):
    cnv.create_rectangle(100, 20, 100+c, 20+c, fill='blue', outline='')

cnv.after(1000, dessiner, 42)

root.mainloop()
  • Ligne 7 : le rectangle rouge visible au départ.

  • Lignes 9-10 : fonction qui va dessiner un carré bleu de côté c qui sera donné en argument à la fonction.

  • Ligne 12 : appel de la méthode after en tant que méthode du canevas. L’appel prend ici 3 arguments

    • le premier argument 1000 qui correspond à une durée en millisecondes
    • le 2e argument dessiner qui est la fonction qui sera appelée après le délai de 1000 ms
    • les autres arguments (ici juste 42), les arguments, dans l’ordre qu’il faudra passer à la fonction dessiner donnée en 2e argument de l’appel de la méthode after.

On peut appeler after depuis n’importe quel widget ou fenêtre :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from tkinter import Tk, Canvas

root = Tk()
cnv = Canvas(root, width=180, height=100)
cnv.pack()

cnv.create_rectangle(20, 20, 80, 80, fill='red', outline='')

def dessiner(c):
    print(c)
    cnv.create_rectangle(100, 20, 100+c, 20+c, fill='blue', outline='')

root.after(1000, dessiner, 42)

root.mainloop()
  • Ligne 13 : appel de la méthode after comme une méthode de la fenêtre root.

La méthode after : usage typique

L’usage typique de la méthode after est qu’elle appelle à intervalle régulier (et non plus une seule fois) une fonction d’animation.

Voici un exemple. Le programme ci-dessous montre un carré bleu se déplaçant aléatoirement chaque seconde :

../../../_images/anim.gif

Le code correspondant est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from tkinter import Tk, Canvas
from random import randint


root = Tk()
cnv = Canvas(root, width=180, height=100)
cnv.pack()

def dessiner(item=None):
    cnv.delete(item)
    a=randint(1,90)
    b=randint(1,50)
    c=50
    item=cnv.create_rectangle(a, b, a+c, b+c, fill='blue', outline='')
    root.after(1000, dessiner, item)

dessiner()

root.mainloop()
  • Ligne 9-15 : fonction d’animation qui est appelée périodiquement.
  • Ligne 10 : la fonction efface le carré présent sur le canvas.
  • Ligne 11-14 : la fonction dessiner crée un carré, nommé encore item, aléatoire (lignes 11-12), de 50 pixels de côté (ligne 13) et le dessine (ligne 14).
  • Ligne 15 : l’élément fondamental de l’animation qui permet de boucler l’animation. La méthode after temporise 1000 ms et fait en sorte que la fonction dessiner soit appelée en prenant le nouveau carré comme argument.

Illustrer after en créant des images

La méthode after permet de déclencher avec un certain délai de temps une action définie par une fonction.

Soit par exemple le code ci-dessous :

after_images.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from tkinter import *
from random import randrange

SIDE=400
root = Tk()
cnv = Canvas(root, width=SIDE, height=SIDE)
cnv.pack()

logo = PhotoImage(file="python.gif")

def action(x, y):
    cnv.create_image((x, y), image=logo)

x, y= randrange(SIDE),randrange(SIDE)
cnv.after(1000, action, x, y)

x, y= randrange(SIDE),randrange(SIDE)
cnv.after(2000, action, x, y)

x, y= randrange(SIDE),randrange(SIDE)
cnv.after(3000, action, x, y)

x, y= randrange(SIDE),randrange(SIDE)
cnv.after(4000, action, x, y)

x, y= randrange(SIDE),randrange(SIDE)
cnv.after(5000, action, x, y)

x, y= randrange(SIDE),randrange(SIDE)
cnv.after(6000, action, x, y)

root.mainloop()

On a défini une fonction action qui prend deux paramètres (le nom action n’a rien d’obligatoire).

Voici l’exécution :

../../../_images/after_images1.gif

Lorsque le programme est lancé, les 6 instructions cnv.after(ms, action, x, y) vont être exécutées les unes après les autres et instantanément. Chacune de ces instructions va déclencher l’exécution de la fonction action mais après le délai en ms indiqué par l’appel cnv.after(ms, action, x, y). Par exemple, une instruction telle que cnv.after(4000, action, 200, 300) va provoquer, 4 secondes (4000 ms) après avoir été activée, l’exécution de l’appel action(200, 300). D’où l’effet d’animation.

La méthode after n’est pas spécifique de Canvas : dans le code ci-dessus, on pourrait remplacer les appels cnv.after par des appels root.after.

Annuler la méthode after

Un appel à la méthode after renvoie un identifiant de la tâche qui va être relancée. Cet identifiant, disons id_anim, peut être utilisé pour annuler cette tâche. Pour cela, il suffit d’appeler after_cancel(id_anim)after_cancel est une méthode d’un widget.

Dans l’exemple ci-dessous, un compteur est lancé :

../../../_images/annul.gif

Un bouton permet (artificiellement) d’annuler l’animation et d’en relancer une nouvelle avec un nouveau compteur.

Le code est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from tkinter import Tk, Canvas, Button
from random import randrange

def anim(cpt):
    global id_anim
    cnv.delete("all")
    cnv.create_text(SIDE//2, SIDE//2, text =int(cpt), font="Arial 200 bold")
    id_anim=cnv.after(500, anim, cpt+1)

def go():
    cnv.after_cancel(id_anim)
    anim(1)

SIDE=400
root = Tk()
cnv = Canvas(root, width=SIDE, height=SIDE, bg='ivory')
cnv.pack()
btn=Button(root, text="Reset", command=go)
btn.pack()

id_anim=None
anim(1)

root.mainloop()
  • Lignes 4-8 : la fonction d’animation : elle efface tout (ligne 6) et crée le nombre suivant du compteur. La foncion est relancée toutes les demi secondes (ligne 8). Le rappel provoque la création d’un identifiant (ligne 8). Pour pouvoir agir sur cet identifiant, il est placé dans une variable globale (ligne 5).
  • Ligne 18 : Création d’un bouton. Lorsqu’on clique sur ce bouton, il lance la fonction go.
  • Lignes 10-12 : le clic sur le bouton entraîne l’annulation de l’animation en cours : la méthode d’annulation after_cancel (ligne 11) est appelée. Puis une animation est aussitôt relancée (ligne 12).

Balle rebondissante

Voici une animation classique :

../../../_images/balle_rebond.gif

une balle est lancée dans un rectangle et quand elle touche un mur, elle rebondit et repart en position symétrique.

Voici le code correspondant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from tkinter import *

SIDE=400
PAD=SIDE//12

def move(dx, dy):
    ulx, uly, lrx, lry = list(map(int, cvs.coords(ball)))
    # bords verticaux
    if ulx <=0 or lrx >=SIDE:
        dx=-dx
    # bords horizontaux
    elif uly<=0 or lry>=SIDE:
        dy=-dy
    cvs.move(ball, dx, dy)
    cvs.after(30, move, dx, dy)

# Création d'un canevas
w=Tk()
cvs=Canvas(w, width=SIDE, height=SIDE, highlightthickness=0,
           bg="ivory")
cvs.pack(padx=PAD, pady=PAD)

# Création d'une balle
n=20
R=SIDE//n
x0=200
y0=40
ball=cvs.create_oval(x0, y0, 2*R+x0, 2*R+y0, outline='OrangeRed2',
                     fill='OrangeRed2')

# Lancement de l'animation
move(4, 6)
w.mainloop()
  • Lignes 18-21 : création du rectangle (un canevas en fait)
  • Lignes 24-29 : création de la balle rebondissante. La balle a une id (ball, ligne 28) qui sert pour détecter la position de la balle sur le canevas (ligne 7) et la déplacer (ligne 14)
  • Ligne 25 : le rayon de la balle qui s’ajuste en fonction de la taille du canevas pour garder des proportions correctes.
  • Lignes 26-27 : le point où la balle est lâchée.
  • Lignes 6-15 : la fonction d’animation
  • Ligne 32 : le lancement de l’animation.
  • Ligne 6 : la fonction d’animation déplace la balle toujours de la même façon : 4 pixels horizontalement, 6 pixels verticalement (cf. ligne 32).
  • Ligne 15 : l’animation est relancée toutes les 30 millisecondes.
  • Ligne 14 : le déplacement de la balle après rectification éventuelle si la balle a touché le mur.
  • Lignes 8-13 : gestion des collisions avec les murs. Quand la balle touche un mur, le sens de son mouvement est inversé (ce qui explique pourquoi dx ou dy est changé en son opposé aux lignes 10 et 13)
  • Ligne 7 : la balle a une id (créée ligne 28). Grâce à cette id, le canevas est en mesure de fournir les quatre coins du carré qui encadre la balle. C’est ainsi que l’on sait si la balle est en train de rebondir ou pas (lignes 9 et 12).

Pour modifier la vitesse de la balle, on peut jouer sur :

  • le délai de rafraîchissement (ligne 15)
  • l’amplitude de déplacement (ligne 32).

Certains choix de ces deux paramètres peuvent produire des animations disgrâcieuses.

Effet de fading

Un effet de fading (atténuation progressive d’une couleur) est obtenu en faisant une animation.

On passe graduellement (ici en deux secondes et 10 étapes) d’un carré jaune à un carré blanc :

../../../_images/fading.gif

Le code correspondant est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from tkinter import *

SIDE=400

root = Tk()
cnv = Canvas(root, width=SIDE, height=SIDE,
             bg='light blue')
cnv.pack()

def fading_color(r,g,b, N):
    return (255-r)//N,(255-g)//N,  (255-b)//N

def fading(rect, r, g, b, N):
    if N<=0:
        return
    s=cnv.itemcget(rect,"fill")[1:]
    R,G,B=[int(''.join(u),base=16) for u in zip(s[0::2],s[1::2])]
    rr=str(hex(min(R+r, 255)))[2:]
    gg=str(hex(min(G+g, 255)))[2:]
    bb=str(hex(min(B+b, 255)))[2:]
    color="#"+str(rr+gg+bb)
    cnv.delete(rect)
    rect=cnv.create_rectangle(100, 100, 300, 300,
                              fill=color, outline='')
    cnv.after(200, fading, rect, r, g, b, N-1)

rect= cnv.create_rectangle(100, 100, 300, 300,
                           fill='#f0e68c', outline='')

r, g, b=fading_color(0xf0,0xe6, 0x8c, 10)
fading(rect, r, g, b, 10)
  • Lignes 13-23 : la fonction d’animation.
  • Ligne 30 : on calcule au préalalble trois tranches r, g et b de composantes RGB que l’on va additionner (lignes 18-20) à la couleur courante à chaque étape de l’animation.
  • Ligne 25 : l’animation est relancée tous les 200 ms.
  • Ligne 16 : on lit les couleurs courantes du rectangle en accédant aux options de l’item grâce à son id (ici rect) qui est un paramètre de la fonction fading.
  • Ligne 22-24 : on efface le carré courant et on le remplace par le carré avec la nouvelle couleur.