Animations utilisant Python et SDL


Les manipulations expérimentées ici résultent de la découverte progressive de la bibliothèque SDL, telle qu'elle est accessible via le langage Python.

Il faut, bien évidemment, installer la bibliothèque pyGame sur l'ordinateur pour que cela marche.
Pour lancer l'exécution de chaque programme :
- télécharger le code source
- télécharger les éléments graphiques
- mettre le tout dans un seul répertoire
- ouvrir un terminal et se positionner dans le répertoire concerné
- taper la commande : python mon-programme.py


Consulter en ligne :
En français :

http://fr.wikipedia.org/wiki/Pygame

En anglais :

http://www.pygame.org/news.html


Un premier programme

Description :

Il s'agit de la personnalisation (très réduite) d'un exemple didactique trouvé sur le "net". Une balle : http://lerautal.lautre.net/journal/sdl/ball.png va se déplacer sur un écran noir, rebondissant sur les bords, comme le ferait une boule de billard.

Le mouvement est ralenti par une boucle qui compte de 0 à 10 000 avant chaque déplacement de la balle. Si l'on modifie cette valeur le mouvement peut être rendu plus rapide ou plus lent.

Commentaires sur le code :

0. Déclaration des bibliothèques nécessaires et initialisation
import sys, pygame
pygame.init()
1. Création d'un écran vide, avec fond noir
size = width, height = 640, 480
black = 0, 0, 0
screen = pygame.display.set_mode(size)
screen.fill(black)
2. Chargement de l'image à afficher sur l'écran et incorporation de cette image dans l'écran
ball = pygame.image.load("ball.png")
screen.blit(ball, ballrect)
3. Moyen de sortir du programme
for event in pygame.event.get():
		if event.type == pygame.QUIT: sys.exit()
4. Gestion de la balle : elle ne doit pas sortir du cadre et on doit simuler un rebond.
La variable speed correspond aux "vitesses" en x et y.
speed = [1, 1]
while 1:
	ballrect = ballrect.move(speed) 
	if ballrect.left < 0 or ballrect.right > width:
		speed[0] = -speed[0]
	if ballrect.top < 0 or ballrect.bottom > height:
		speed[1] = -speed[1]
5. Affichage du résultat
	pygame.display.flip()
6. Ralentissement de l'affichage par une boucle qui "perd son temps"
	while ral<10000:
		ral=ral+1
	ral=0

La totalité du programme :

# -*- coding:Utf-8 -*-
import sys, pygame
pygame.init()
ral=0
size = width, height = 640, 480
speed = [1, 1]
black = 0, 0, 0
screen = pygame.display.set_mode(size)
ball = pygame.image.load("ball.png")
ballrect = ball.get_rect()
while 1:
	for event in pygame.event.get():
		if event.type == pygame.QUIT: sys.exit()
	ballrect = ballrect.move(speed) 
	if ballrect.left < 0 or ballrect.right > width:
		speed[0] = -speed[0]
	if ballrect.top < 0 or ballrect.bottom > height:
		speed[1] = -speed[1]
	screen.fill(black)
	screen.blit(ball, ballrect)
	pygame.display.flip()
	while ral<10000:
		ral=ral+1
	ral=0

Le programme est à télécharger ici :

http://lerautal.lautre.net/journal/sdl/ballesdl.py

Améliorations

La balle unique a été complétée par un second élément graphique : http://lerautal.lautre.net/journal/sdl/bils.png

Quand ces objets heurtent les bords verticaux ils effectuent une rotation de 90 degrés.
	if ballrect.left < 0 or ballrect.right > width:
		speed[0] = -speed[0]
		ball=pygame.transform.rotate(ball,90)
	if ballrect.top < 0 or ballrect.bottom > height:
		speed[1] = -speed[1]
	if billrect.left < 0 or billrect.right > width:
		speed2[0] = -speed2[0]
		bill=pygame.transform.rotate(bill,90)
On a remplacé la boucle 0..10000 par une attente de 10 millisecondes.
	pygame.time.wait(10)

La totalité du programme :

# -*- coding:Utf-8 -*-
import sys, pygame
pygame.init()
size = width, height = 630, 450
speed = [1, 2]
speed2= [2,3]
black = 0, 0, 0
screen = pygame.display.set_mode(size)
ball = pygame.image.load("ball.png")
bill = pygame.image.load("bils.png")
ballrect = ball.get_rect()
billrect = bill.get_rect()
while 1:
	for event in pygame.event.get():
		if event.type == pygame.QUIT: sys.exit()
	ballrect = ballrect.move(speed) 
	billrect = billrect.move(speed2) 
	if ballrect.left < 0 or ballrect.right > width:
		speed[0] = -speed[0]
		ball=pygame.transform.rotate(ball,90)
	if ballrect.top < 0 or ballrect.bottom > height:
		speed[1] = -speed[1]
	if billrect.left < 0 or billrect.right > width:
		speed2[0] = -speed2[0]
		bill=pygame.transform.rotate(bill,90)
	if billrect.top < 0 or billrect.bottom > height:
		speed2[1] = -speed2[1]
	screen.fill(black)
	screen.blit(ball, ballrect)
	screen.blit(bill, billrect)
	pygame.display.flip()
	pygame.time.wait(10)

Le programme est à télécharger ici :

http://lerautal.lautre.net/journal/sdl/ballesdl2.py

Autres améliorations

L'intention est ici de remplacer le fond noir par une image choisie. L'image est ici : http://lerautal.lautre.net/journal/sdl/cact.jpg

1. Chargement de l'image
cact= pygame.image.load("cact.jpg")
2. Incorporation sur le fond noir
screen.blit(cact,(0,0))
3. La seconde image (le mot "succulente") tourne maintenant de 180 degrés.
		bill=pygame.transform.rotate(bill,180)

La totalité du programme :

# -*- coding:Utf-8 -*-
import sys, pygame
pygame.init()
size = width, height = 600, 450
speed = [1, 2]
speed2= [2,3]
black = 0, 0, 0
screen = pygame.display.set_mode(size)
cact= pygame.image.load("cact.jpg")
ball = pygame.image.load("ball.png")
bill = pygame.image.load("bils.png")
ballrect = ball.get_rect()
billrect = bill.get_rect()
while 1:
	for event in pygame.event.get():
		if event.type == pygame.QUIT: sys.exit()
	ballrect = ballrect.move(speed) 
	billrect = billrect.move(speed2) 
	if ballrect.left < 0 or ballrect.right > width:
		speed[0] = -speed[0]
		ball=pygame.transform.rotate(ball,90)
	if ballrect.top < 0 or ballrect.bottom > height:
		speed[1] = -speed[1]
	if billrect.left < 0 or billrect.right > width:
		speed2[0] = -speed2[0]
		bill=pygame.transform.rotate(bill,180)
	if billrect.top < 0 or billrect.bottom > height:
		speed2[1] = -speed2[1]
	screen.fill(black)
	screen.blit(cact,(0,0))
	screen.blit(ball, ballrect)
	screen.blit(bill, billrect)
	
	pygame.display.flip()
	pygame.time.wait(10)

Le programme est à télécharger ici :

http://lerautal.lautre.net/journal/sdl/ballesdl3.py

Nouvelle série : le corbeau et le renard

Début d'une série : illustration d'une fable de La Fontaine.
Ce thème me paraît intéressant parce qu'il permet de multiples développements : réalisation d'un scénario, d'illustrations incluant images, textes, sprites...
Pour les "pédagos" : remarquer que l'on peut utiliser des images d'enfants scannées...

Avant de faire élaboré, faisons très très simple pour tester la validité des choix.

L'idée est ici d'afficher trois images successivement (sorte de diaporama) en affectant une durée de visibilité à chaque image.
Les images ont été dessinées avec Inkscape.

http://lerautal.lautre.net/journal/sdl/1corbeau.png
http://lerautal.lautre.net/journal/sdl/2corbeau.png
http://lerautal.lautre.net/journal/sdl/3corbeau.png

Programmation : un choix (TRES discutable) a été fait : chaque image est affichée par une fonction particulière.
La vraie justification est ici que l'on peut avoir un code très lisible, limite enfantin.

1. Les définitions initiales :

 # -*- coding:Utf-8 -*-
import sys, pygame
pygame.init()
size = width, height = 600, 402
black = 0, 0, 0
screen = pygame.display.set_mode(size)

2. Exemple d'une fonction (affichage de la première images)

def image1():   Déclaration de la fonction
    screen.fill(black)   on remplit l'écran de noir
    im=pygame.image.load("1corbeau.png")   on charge l'image
    screen.blit(im,(0,0))    on colle l'image sur l'écran noir
    pygame.display.flip()  on affiche
    pygame.time.wait(5000)  on fait durer 5 secondes puis on s'en va

3. L'ordonnancement de tout cela :

image1()
image2()
image3()
# attente 5 secondes avant effacement
pygame.time.wait(5000)

Si vous voulez tester sur votre machine, il faudra charger les trois images, installer python et pygame (si vous ne les avez pas encore). (sous Debian c'est du genre apt-get install pygame !!! excusez du difficile).

Le code du programme est ici :

http://lerautal.lautre.net/journal/sdl/corbeau1.py

Pour exécuter, ouvrir un terminal, se positionner dans le répertoire qui contient les 3 images et le programme et taper : python corbeau1.py

Bien-sûr on peut faire ceci avec tel ou tel langage de programmation. L'avantage de python étant que, sous Linux, il est déjà sur les machines.


"Mise en vidéo"

Comme tout le monde n'a pas envie de jouer avec Python, j'ai modifié le programme initial de façon à pouvoir enregistrer une succession d'images... qui permettent (comme dans certains des exemples antérieurs) de créer une vidéo.
La vidéo est ici : http://lerautal.lautre.net/journal/sdl/corbeau1.mp4 Elle n'a que peu d'intérêt... sinon de prouver qu'on pouvait la créer.
Elle est délibérément courte de façon à ne pas "peser" trop lourd.
Le programme modifié est ici :
http://lerautal.lautre.net/journal/sdl/corbeau2.py

Points importants :

nb=1111    # pour les noms de fichiers successifs

Fonction d'enregistrement des images successives :

def enregistre_images(n,scr):
    nomfich=str(n)+".jpg"
    pygame.image.save(scr,nomfich)

Affichage et enregistrement de la première image :

def image1(nb):
    compt=0  pour gestion de la durée
    screen.fill(black)
    im=pygame.image.load("1corbeau.png")
    screen.blit(im,(0,0))
    pygame.display.flip()
    while compt<1000:   permet de cntrôler la durée
        enregistre_images(nb,screen)
        compt=compt+100
        pygame.time.wait(50) affichage pendant 50 millisecondes
        nb=nb+1
    return nb   retourne le numéro de fichier cible

Appel de fonction avec récupération du numéro d'image :

nb=image1(nb) 

Le corbeau bat des ailes

Le programme suivant utilise trois images successives du corbeau, de façon à simuler les battements d'aile :

http://lerautal.lautre.net/journal/sdl/corb1.png
http://lerautal.lautre.net/journal/sdl/corb2.png
http://lerautal.lautre.net/journal/sdl/corb3.png

Le fond va être successivement on fond "gris blanc variable", puis un scrolling dans une image rectangulaire :

http://lerautal.lautre.net/journal/sdl/arriere_plan.jpg

Le programme va successivement faire voler le corbeau
- devant fond qui s'éclaircit,
- devant un paysage qui se décale

A signaler :

1. la gestion de l'éclaircissement du fond :
fond = [120, 120, 120]
.... puis ...
def eclaircit():
    if ((fond[0]+2)<255):     fond[0]=fond[0]+5
    if ((fond[1]+2)<255):    fond[1]=fond[1]+5   
    if ((fond[2]+2)<255):    fond[2]=fond[2]+5
  

2. Le scolling dans une grande image (variation valeur x) :

a=0
while a>-640 :
    screen.blit(arriere,(a,0))
    ....
    a=a-10

La totalité du programme est ici :

http://lerautal.lautre.net/journal/sdl/corbeau4.py

Le résultat est assez sympa...


Remarques

Je voulais enregistrer une série d'images pour faire une vidéo : c'est plus causant de renvoyer une URL par mail sur le source d'un programme que les gens n'ont pas forcément envie (ni le moyen) d'exécuter.

Il y avait deux séries d'images à générer :
- vol du corbeau sur fond monochrome = pas de difficulté (il y a coïncidence entre la taille de l'écran et celle de l'image à enregistrer.
- vol du corbeau sur écran, lequel écran est une fenêtre ouverte sur le scrolling d'une image plus grande... et là, patatras !!! Pas possible.
J'ai parcouru la doc de pygame dans tous les sens sans trouver un "crop" qui va bien pour découper un rectangle dans la grande image avant de le coller dans l'écran...
J'ai fini, mais au bout d'un moment par trouver une astuce (!!!) : je fais une copie de l'écran et c'est cette copie que j'enregistre comme une image.

Voici le programme minimaliste qui teste cette démarche :

# -*- coding:Utf-8 -*-
import sys, pygame
pygame.init()
size = width, height = 150, 200    # taille de l'écran
fond = [120, 120, 120]
screen = pygame.display.set_mode(size)
screen.fill(fond)
arriere=pygame.image.load("arriere_plan.jpg").convert() # ==
# chargement de la grande image qui fait 800 x 200 pixels

def enregistre_images(n,scr):  # permet d'enregistrer l'image
    nomfich="images/"+str(n)+".jpg"
    pygame.image.save(scr,nomfich)
    
screen.blit(arriere,(-300,0)) # on affiche dans la fenêtre écran
# une partie seulement de la grande image

pygame.display.flip()  # affichage
img=screen.copy()      # ma superbe !!! astuce = on copie
# la fenêtre écran de façon à avoir un "truc" du bon type et de
# la bonne taille !!!

enregistre_images(1111,img)  # et là, cela marche

# attente 5 secondes avant effacement
pygame.time.wait(5000)

Dès que je peux je met le tout au propre et je mets la vidéo en ligne.
Ceci étant, je détourne SDL pour revenir à la vidéo... Mais pour des raisons de démonstration.

Plus tard...

Voila : j'ai raccourci un peu les boucles pour que la vidéo ne soit pas trop grosse. Elle est là :
http://lerautal.lautre.net/journal/sdl/corbeau2.mp4

Normalement, en dehors du corbeau, vous verrez :
- le fond uni qui s'éclaircit
- le fond photographique qui "scrolle"

Pour les plus studieux, le code est ici :
http://lerautal.lautre.net/journal/sdl/corbeau7.py

C'est tout sauf exemplaire, et cela sent le bricolage "à la va-vite".

Des cours de Python on en trouve de bons... ailleurs. Pour pygame, vous pouvez aller voir la référence ici :

http://www.pygame.org/docs/ref/index.html

C'est austère :-)

Autres compléments

Les images et le code.

Si vous voulez faire tourner le truc chez vous (sans enregister). Le renard :

http://lerautal.lautre.net/journal/sdl/renard1.png
http://lerautal.lautre.net/journal/sdl/renard2.png
http://lerautal.lautre.net/journal/sdl/renard3.png

La photographie du paysage :

http://lerautal.lautre.net/journal/sdl/arbre-neige.jpg

Le fond dessiné, pour la suite :

http://lerautal.lautre.net/journal/sdl/fond2.png

Le code python :

http://lerautal.lautre.net/journal/sdl/corbeau8.py

Commentaires :

Le programme réalise l'animation à l'écran, mais en plus si on veut, l'enregistrement des images pour créer une vidéo. Ceci est résolu à l'aide d'un test logique :

    if enr>0:
        enregistre_images(nb,img)   
La fonction image3 en est à l'état d'ébauche.