Infoforall

Python 14 : Interface Tk - GESTION DES CANVAS et DESSINS

Nous avons déjà vu les bases des interfaces graphiques avec Tk à travers deux activités : création et positionnement des widgets.

Nous allons exploiter aujourd'hui le nouveau type de widget (le Canvas) que nous avons vu à la fin de l'activité précédente. Il s'agit d'une zone graphique dans laquelle nous allons pouvoir dessiner ou écrire du texte.

Par exemple, on peut obtenir ceci avec quelques lignes de code :

4 boutons

Cette activité va nous permettre également d'utiliser les connaissances acquises lors de l'activité sur les fonctions. Si vous ne l'avez pas encore faite, il est possible que certaines questions vous semblent obscures.

1 - Créer un Canvas

Commençons par créer le widget que nous allons utiliser pour dessiner : le Canvas.

Exemple :

monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory').

01° Utiliser le code suivant pour créer un Canvas de 500x500 dans une fenêtre de 600x600. On utilisera la méthode pack pour l'afficher dans la fenêtre fen_princ.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *


fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory')

monCanvas.pack()


fen_princ.mainloop()

4 boutons

Comme vous pouvez le voir, le Canvas n'est pas très bien centré. Nous allons utiliser la méthode place avec deux arguments : la position x et y du point haut et gauche de votre widget.

Ainsi monCanvas.place(x=100,y=100) va placer le début du Canvas aux coordonnées (100,100).

02° Modifier le code en remplaçant la méthode pack par la méthode place de façon à obtenir l'affichage suivant. Il faudra modifier les valeurs des coordonnées x et y.

4 boutons

Pour rappel, voici les axes de coordonnées :

4 boutons

Si vous voulez décaler le widget vers la droite, il faut donc imposer une coordonnée x de plus en plus grande.

Si vous voulez décaler le widget vers le bas, il faut également imposer une coordonnée y de plus en plus grande.

2 - Dessiner des lignes sur le Canvas

Nous venons de créer notre zone de dessin. Il nous reste à dessiner dessus !

Pour commencer, la ligne droite : monCanvas.create_line(x1, y1, x2, y2, width=2, fill="red") permet de créer une ligne rouge partant du point (x1,y1) inclus et allant au point (x2,y2) exclu avec une épaisseur de 2 pixels.

On aura pu également noter la couleur en code RGB hexadécimal :

monCanvas.create_line(x1, y1, x2, y2, width=2, fill="#ff0000").

03° Utiliser la méthode create_line pour tenter d'obtenir une image semblable à cela plus ou moins :

4 boutons

Une grande petite remarque

Voici le résultat des lignes de commandes suivantes (on indique rien pour width et fill, qui prendront leurs valeurs par défaut : 1 pixel et "black") :

Mais où sont passés les deux premiers points (le noir et le rouge)?

monCanvas.create_line(0,0,1,0, fill="black")

monCanvas.create_line(1,1,2,1, fill="red")

monCanvas.create_line(2,2,3,2, fill="blue")

monCanvas.create_line(3,3,4,3, fill="green")

monCanvas.create_line(4,4,5,4, fill="yellow")

monCanvas.create_line(5,5,6,5, fill="cyan")

monCanvas.create_line(5,6,7,6, fill="orange")

4 boutons

Et bien, voilà le but de cette remarque : par défaut, le Canvas possède une marge vers l'extérieur de 2 pixels ! Voici la même image avec le rajout des coordonnées :

4 boutons

Comme vous le voyez, nous avons tenté par inadvertance de dessiner dans la bordure externe. Donc Tkinter n'a pas dessiné nos deux premiers points.

Alors comment faire ?

Il faut soit gérer ce décalage d'origine dans nos programmes, soit supprimer cette marge lors de la création du Canvas. Voilà comment coder ceci :

Pour supprimer la marge :

monCanvas = Canvas(fen_princ, width=499, height=499, bg='ivory', borderwidth=0, highlightthickness=0)

Si on relance les instructions précédentes avec une marge de 0 pour le Canvas, on obtient bien le résultat voulu : le carré noir est dans la position (0;0) attendue.

4 boutons

Il faudra donc penser à rajouter borderwidth=0, highlightthickness=0) si on veut gérer de façon simple les coordonnées des objets qu'on va placer sur le Canvas.

Voilà un programme qui affiche un Canvas et y crée une droite en utilisant une fonction rotation_ligne documentée dans le code de la fonction elle-même. Si vous voulez savoir comment elle fonctionne, allez lire la remarque juste en dessous.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math


# ----------------------------------------------------------------

# Les fonctions

# ----------------------------------------------------------------

def rotation_ligne(leCanvas, x0, y0, longueur, angle, couleur):

    """

    x0, y0 sont les coordonnées d'origine

    longueur est la longueur de la ligne en pixels

    couleur est la couleur sous forme de string : "red" ou "#ff0000"

    angle est l'angle par rapport à l'horizontal en degré

    """

    angle = math.radians(angle) # math.radians permet de convertir en radians

    xf = int(x0+longueur*math.cos(angle))

    yf = int(y0+longueur*math.sin(angle))

    leCanvas.create_line(x0, y0, xf, yf, width=2, fill=couleur)


# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


rotation_ligne(monCanvas, 250, 250, 100, 45, "#ff0000")


fen_princ.mainloop()

04° Regarder la déclaration de la fonction pour trouver la position du paramètre qui gère l'angle de tracage de la droite. Utiliser le programme pour tracer des traits à -45°, 0° et +45°. On prendra le point (x0=250,y0=250) comme origine des lignes.

Rotation de lignes : des explications

Cette remarque est plus ou moins optionnelle : elle vous explique briévement comment créer la fonction qui met en rotation. Or, la puissance des fonctions est qu'on n'a pas besoin de savoir comment elle fonctionne pour l'utiliser. Il suffit de savoir ce qu'elle désire comme paramètres.

Faire des rotations, c'est un peu plus compliqué avec Tkinter. Il va falloir sortir le module math car nous allons avoir besoin de faire des projections. Le but de ce chapitre n'est pas de faire un cours de mathématiques mais nous allons devoir faire des projections sur l'axe Ox (horizontal) et l'axe Oy (vertical).

Un trait de longueur L aura une longueur résultante

  • de valeur L*cos(angle) sur l'axe Ox
  • de valeur L*sin(angle) sur l'axe Oy

Si le trait part des coordonnées (x0,y0), les coordonnées finales (xF,yF)seront :

  • xF = x0+L*cos(angle) sur l'axe Ox
  • yF = y0+L*sin(angle) sur l'axe Oy

Si on doit créer une fonction qui génére à partir d'un point (x0,y0) une ligne ayant une certaine longueur avec un angle donné, on peut obtenir

def rotation_ligne(leCanvas, x0, y0, longueur, angle, couleur):

    """

    x0, y0 sont les coordonnées d'origine

    longueur est la longueur de la ligne en pixels

    couleur est la couleur sous forme de string : "red" ou "#ff0000"

    angle est l'angle par rapport à l'horizontal en degré

    """

    angle = math.radians(angle) # math.radians permet de convertir en radians

    xf = int(x0+longueur*math.cos(angle))

    yf = int(y0+longueur*math.sin(angle))

    leCanvas.create_line(x0, y0, xf, yf, width=2, fill=couleur)


05° Utiliser une boucle FOR pour tracer les lignes de 0 à 360° par incrémentation de 20°.

4 boutons

...CORRECTION...

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math


# ----------------------------------------------------------------

# Les fonctions

# ----------------------------------------------------------------

def rotation_ligne(leCanvas, x0, y0, longueur, angle, couleur):

    """

    x0, y0 sont les coordonnées d'origine

    longueur est la longueur de la ligne en pixels

    couleur est la couleur sous forme de string : "red" ou "#ff0000"

    angle est l'angle par rapport à l'horizontal en degré

    """

    angle = math.radians(angle) # math.radians permet de convertir en radians

    xf = int(x0+longueur*math.cos(angle))

    yf = int(y0+longueur*math.sin(angle))

    leCanvas.create_line(x0, y0, xf, yf, width=2, fill=couleur)


# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


for angle in range(0,361,20) :

    rotation_ligne(monCanvas, 250, 250, 100, angle, "#ff0000")


fen_princ.mainloop()

06° Modifier une dernière fois le programme pour obtenir quelque chose qui ressemble à ceci :

4 boutons

3 - Le module random : gestion du hasard

On peut même aller plus loin : on peut utiliser le module random pour créer des nombres aléatoires :

Ce module n'est pas propre aux Canvas ou à Tkinter. Mais, nous allons réutiliser les étoiles ci-dessus pour créer une répartition d'étoiles au hasard justement.

Voilà un programme qui vous montre un peu la puissance des fonctions et du module random :

Il faut savoir lire un programme désormais : on commence par le programme principal et on va voir le contenu des fonctions lorsqu'on les rencontre. Le programme est :

# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


for i in range(6) :

    etoile_aleatoire(monCanvas, "alea")


fen_princ.mainloop()

On constate qu'on va lancer un appel à etoile_aleatoire 6 fois (i valant de 0, 1, 2, 3, 4 et 5).

Voilà le programme total maintenant :

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math

import random


# ----------------------------------------------------------------

# Les fonctions

# ----------------------------------------------------------------


def rotation_ligne(leCanvas, x0, y0, longueur, angle, couleur) :

    """

    Cette fonction permet de dessiner une ligne

    leCanvas est la référence du canvas dans lequel on veut dessiner la ligne

    x0, y0 sont les coordonnées d'origine

    longueur est la longueur de la ligne en pixels

    couleur est la couleur sous forme de string : "red" ou "#ff0000"

    angle est l'angle par rapport à l'horizontal en degré

    """

    angle = math.radians(angle) # math.radians permet de convertir en radians

    xf = int(x0+longueur*math.cos(angle))

    yf = int(y0+longueur*math.sin(angle))

    leCanvas.create_line(x0, y0, xf, yf, width=2, fill=couleur)


def etoile(leCanvas, x0, y0, couleur) :

    """

    Cette fonction permet de dessiner une étoile en utilisant la fonction rotation_ligne

    leCanvas est la référence du canvas dans lequel on veut dessiner l'étoile

    x0, y0 sont les coordonnées du centre de l'étoile

    couleur est la couleur de l'étoile sous forme de string : "red" ou "#ff0000"

    """

    for angle in range(0,361,20) :

        rotation_ligne(leCanvas, x0, y0, random.randint(50,100), angle, couleur)


def etoile_aleatoire(leCanvas, couleur) :

    """

    Cette fonction permet de dessiner une étoile de façon aléatoire.

    Si le paramètre couleur vaut 'alea', on tire la couleur au hasard.

    Les coordonnées du centre sont tirés au hasard.

    La fonction utilise la fonction etoile.

    """

    if couleur == "alea" :

        liste_couleurs = ['red','blue','red','yellow', 'green']

        couleur = random.choice(liste_couleurs)

    x0 = random.randint(50,450)

    y0 = random.randint(50,450)

    etoile(leCanvas, x0, y0, couleur)


# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


for i in range(6) :

    etoile_aleatoire(monCanvas, "alea")


fen_princ.mainloop()

07° Lancer le programme, analyser le pour bien comprendre l'imbrication des fonctions et l'utilisation des éléments aléatoires. Modifier alors deux trois valeurs pour en voir l'influence visuellement.

08° Modifier le programme avec cette seule ligne située dans la fonction rotation_ligne :

leCanvas.create_line(x0, y0, xf, yf, width=2, fill=couleur, dash=(4, 4))

4 - Dessiner des rectangles sur le Canvas

On peut dessiner d'autres choses que des lignes :

Pour créer des rectangles, on peut utiliser ceci :

monCanvas.create_rectangle(100,200,300,400, fill="red", activefill="yellow", outline="blue", width=5)

Attention à la coordonnée finale : il s'agit du pixel juste extérieur à la boîte. Ainsi, si vous donnez les coordonnées (0,0,10,5), en réalité vous allez créer un rectangle qui va s'arrêter en (9,4). Le point de coordonnées (10,5) n'est pas dans la forme.

Voici le résultat de monCanvas.create_rectangle(0,0,10,5,width=0,fill="red").

point extreme

Mais pourquoi agir ainsi ? A cause de la bordure peut-être. Par défaut la bordure a une couleur noire et une largeur de 1 pixel : width=1.

Le code monCanvas.create_rectangle(0,0,10,5, fill="red") (on ne précise pas width, il vaut donc 1) donne :

point extreme

On retrouve donc une raison logique à cette seconde coordonnée : elle est bien intégrée à la forme SI la forme possède une bordure de 1 pixel.

A titre d'exemple, voilà un programme qui crée deux rectangles, l'un avec les valeurs par défaut, l'autre en fournissant certains arguments.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math

import random


# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


monCanvas.create_rectangle(20,50,120,250)

monCanvas.create_rectangle(100,300,300,400, fill="red", activefill="yellow", outline="blue", width=5)


fen_princ.mainloop()

09° Lancer ce programme pour vérifier que l'un des rectangles change bien de couleur.

Cela va nous permettre de dessiner des traits et des rectangles. Reprenons le code qui permet de dessiner des étoiles aux rayons aléatoires.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math

import random


# ----------------------------------------------------------------

# Les fonctions

# ----------------------------------------------------------------

def rotation_ligne(leCanvas, x0, y0, longueur, angle, couleur) :

    """

    Cette fonction permet de dessiner une ligne

    leCanvas est la référence du canvas dans lequel on veut dessiner la ligne

    x0, y0 sont les coordonnées d'origine

    longueur est la longueur de la ligne en pixels

    couleur est la couleur sous forme de string : "red" ou "#ff0000"

    angle est l'angle par rapport à l'horizontal en degré

    """

    angle = math.radians(angle) # math.radians permet de convertir en radians

    xf = int(x0+longueur*math.cos(angle))

    yf = int(y0+longueur*math.sin(angle))

    leCanvas.create_line(x0, y0, xf, yf, width=2, fill=couleur, activefill = "red", dash=(4, 4))

    # DU CODE SERA A RAJOUTER ICI


def etoile(leCanvas, x0, y0, couleur) :

    """

    Cette fonction permet de dessiner une étoile en utilisant la fonction rotation_ligne

    leCanvas est la référence du canvas dans lequel on veut dessiner l'étoile

    x0, y0 sont les coordonnées du centre de l'étoile

    couleur est la couleur de l'étoile sous forme de string : "red" ou "#ff0000"

    """

    for angle in range(0,361,20) :

        rotation_ligne(leCanvas, x0, y0, random.randint(50,100), angle, couleur)


# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


etoile(monCanvas, 250,250,"red")


fen_princ.mainloop()

10° Utiliser ce programme pour rajouter des carrés aux bouts des droites. On pourrait avoir par exemple :

étoile avec carrés

11° Rajouter un peu de hasard ensuite dans le choix des couleurs des carrés en utilisant la méthode choice du module random (voir la partie précédente 3 - Le module random : gestion du hasard). Un exemple avec trois étoiles de ce type :

étoile avec carrés

Ce n'est pas très pratique de donner les coordonnées. Voici une fonction qui vous permet de donner les coordonnées du point haut-gauche, la largeur et la hauteur. Elle se charge de créer le rectangle, avec une bordure de 0 pixel.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math

import random


# ----------------------------------------------------------------

# Les fonctions

# ----------------------------------------------------------------


def dessine_rectangle(leCanvas, x0, y0, largeur, hauteur, couleur1, couleur2, couleur3) :

    """

    Cette fonction permet de dessiner un rectangle en fournissant :

    * les coordonnées xo et yo du point haut à gauche.

    * la référence leCanvas du Canvas dans lequel dessiner le rectangle.

    * vous donner fournir la taille via les paramètres largeur et hauteur.

    * Le paramètre couleur1 correspond à la couleur de remplissage.

    * Le paramètre couleur2 correspond à la couleur lorsqu'on survole le rectangle.

    * Le paramètre couleur3 correspond du contour (mais attention, la bordure vaut 0 initialement, ce paramètre ne sert donc à rien).

    * ATTENTION : la bordure n'est pas gérée dans le calcul de la largeur et de la hauteur.

    """

    leCanvas.create_rectangle(x0,y0,x0+largeur,y0+hauteur, fill=couleur1, activefill=couleur2, outline=couleur3, width=0)


# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


dessine_rectangle(monCanvas,0,0,50,50,""gray", ""yellow", "black")

dessine_rectangle(monCanvas,50,0,50,50,""ivory", ""yellow", ""black")


fen_princ.mainloop()

12° Utiliser le programme pour tenter de répondre à la question suivante : Avec la méthode create_rectangle, la bordure est-elle interne ou externe ? Vous pouvez modifier la largeur de la bordure avec le paramètre width :

    leCanvas.create_rectangle(x0,y0,x0+largeur,y0+hauteur, fill=couleur1, activefill=couleur2, outline=couleur3, width=20)

Vous devriez parvenir à la conclusion que les bordures sont à la fois interne et externe. Voici pourquoi :

On crée un rectangle entre (100;250) et (200;350) avec la méthode de base de Tkinter (create_rectangle), on crée une bordure ayant une largeur de 20 pixels.

On crée un point jaune en (100;250) et en (200;350) pour indiquer clairement les deux points d'encrage.

monCanvas.create_rectangle(100,250,200,350,fill="red", outline="black", width=20)

monCanvas.create_line(100,250,101,250,fill="yellow")

monCanvas.create_line(200,350,201,350,fill="yellow")

point extreme

On voit que :

Comme la largeur de la bordure est de 20 pixels, il faudra rajouter 10 pixels à la largeur et à la hauteur du rectangle :

Si on fait un zoom sur le coin supérieur gauche, on voit qu'on a 10 pixels externes et 10 pixels internes, le point (100;250) faisant partie des pixels internes.

point extreme

Si on fait un zoom sur le coin inférieur droit, on voit qu'on a 10 pixels externes et 10 pixels internes, le point (200;350) faisant partie des pixels externes.

point extreme

Voici par exemple le résultat du code monCanvas.create_rectangle(1,1,10,5, fill="red",width=2) :

point extreme

Voici par exemple le résultat du code monCanvas.create_rectangle(1,1,10,5, fill="red",width=3) :

point extreme

PARTIE TOTALEMENT OPTIONNELLE : Gestion de la largeur automatique avec bordure incluse.

Cette partie est assez technique. Si vous préférez passer à la suite, n'hésitez pas. Mais si vous avez un jour un problème avec des bordures larges, pensez à revenir vous documenter ici !

Le programme précédent n'est donc pas très pratique car la taille du rectangle va dépendre de la taille de la bordure ... Voici une nouvelle fonction rectangle_automatique qui gère la taille du rectangle en y intégrant la taille de la bordure. Attention, il faudra vous limiter au cas d'une bordure de taille paire. Aucun test n'est fait non plus si on demande une bordure trop épaisse par rapport à la taille demandée du rectange.

La fonction décale

  • les limites gauche de (bordure/2) pixels vers la droite (pour laisser de la place à la bordure vers l'externe)
  • les limites droite de (bordure/2) pixels vers la gauche (pour laisser de la place à la bordure vers l'externe)

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math

import random


# ----------------------------------------------------------------

# Les fonctions

# ----------------------------------------------------------------


def rectangle_automatique(leCanvas, x0,y0,largeur,hauteur,bordure,couleur1,couleur2,couleur3) :

    """

    Cette fonction permet de dessiner un rectangle en fournissant :

    * les coordonnées xo et yo du point haut à gauche.

    * la référence leCanvas du Canvas dans lequel dessiner le rectangle.

    * vous donner fournir la taille via les paramètres largeur et hauteur.

    * Le paramètre couleur1 correspond à la couleur de remplissage.

    * Le paramètre couleur2 correspond à la couleur lorsqu'on survole le rectangle.

    * Le paramètre couleur3 correspond du contour (mais attention, la bordure vaut 0 initialement, ce paramètre ne sert donc à rien).

    * ATTENTION : la bordure est gérée dans le calcul MAIS necessite une bordure de pixels multiple de 2 et supérieure ou égale à 2 pixelsde la largeur et de la hauteur.

    """

    leCanvas.create_rectangle(x0+bordure//2, y0+bordure//2, x0+largeur-bordure//2, y0+hauteur-bordure//2, fill=couleur1, activefill=couleur2, outline=couleur3, width=bordure)


# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


rectangle_automatique(monCanvas,0,0,50,50,4,"#888888","yellow","black")

rectangle_automatique(monCanvas,50,0,50,50,4,"#ffffcc","yellow","black")

rectangle_automatique(monCanvas,100,0,50,50,4,"#888888","yellow","black")


fen_princ.mainloop()

Pour savoir s'il fonctionne, il faut l'utiliser. Voici un zoom en largeur avec des ensembles de 10 pixels représentés en jaune. On a bien 50 pixels de large en tout, avec 4 pixels noirs de chaque côté.

point extreme

Op01° Utiliser ce programme pour visualiser le résultat puis modifier les valeurs données aux x0,y0 dans le corps du programme, sans toucher à la fonction) les valeurs des coordonnées de départ des rectangles pour que leurs bordures soient communes :

Visuellement, je veux ceci :

point extreme

plutôt que ceci :

point extreme

Si on veut construire un plateau de jeu de 10 cases sur 10, on peut alors l'afficher en quelques lignes. Il faudra utiliser deux boucles FOR imbriquées pour gérer les coordonnées x et y, en incrémentant de 46 pixels à chaque fois. Il faudra également rajouter une variable binaire de test pour alterner la couleur des cases.

point extreme

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math

import random


# ----------------------------------------------------------------

# Les fonctions

# ----------------------------------------------------------------


def rectangle_automatique(leCanvas, x0,y0,largeur,hauteur,bordure,couleur1,couleur2,couleur3) :

    """

    Cette fonction permet de dessiner un rectangle en fournissant :

    * les coordonnées xo et yo du point haut à gauche.

    * la référence leCanvas du Canvas dans lequel dessiner le rectangle.

    * vous donner fournir la taille via les paramètres largeur et hauteur.

    * Le paramètre couleur1 correspond à la couleur de remplissage.

    * Le paramètre couleur2 correspond à la couleur lorsqu'on survole le rectangle.

    * Le paramètre couleur3 correspond du contour (mais attention, la bordure vaut 0 initialement, ce paramètre ne sert donc à rien).

    * ATTENTION : la bordure est gérée dans le calcul MAIS necessite une bordure de pixels multiple de 2 et supérieure ou égale à 2 pixelsde la largeur et de la hauteur.

    """

    leCanvas.create_rectangle(x0+bordure//2, y0+bordure//2, x0+largeur-bordure//2, y0+hauteur-bordure//2, fill=couleur1, activefill=couleur2, outline=couleur3, width=bordure)


# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")


monCanvas = Canvas(fen_princ, width=464, height=464, bg='ivory', bd=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


test_couleur = True

for y in range(0,460,46) :

    test_couleur = not(test_couleur)

    for x in range(0,460,46) :

        if test_couleur :

            rectangle_automatique(monCanvas,x,y,50,50,4,"#888888","yellow","black")

        else :

            rectangle_automatique(monCanvas,x,y,50,50,4,"#ffffcc","yellow","black")

        test_couleur = not(test_couleur)


fen_princ.mainloop()

Op02° Tester le programme et tenter de le modifier pour que le plateau soit centré puis qu'on affiche uniquement 8x8 cases par exemple.

Nous allons nous arrêter là mais vous pourriez créer un programme qui automatise les calculs en fonction d'une variable nombre_de_cases qui contiendrait le nombre de cases voulues.

5 - Les ellipses

Nous allons maintenant survoler rapidement les autres types d'objets qu'on peut créer dans un Canvas.

Commençons par les ellipses, ces formes ovales qui décrivent les trajectoires des planètes par exemple :

ellipse

Crédit pour l'image gif : Par MADe, CC BY-SA 2.0 be, https://commons.wikimedia.org/w/index.php?curid=3232320

Pour décrire une ellipse avec Tkinter, c'est assez facile : il faut trouver le rectangle qui la contient. On a alors simplement besoin des coordonnées (x0,y0) du point supérieur gauche et des coordonnées (x1,y1) du point inférieur droit du rectangle.

Le code est assez proche du code nécessaire pour créer un rectangle : monCanvas.create_oval(0,0,20,10) va créer une ellipse de bordure 1 pixel qu'on peut dessiner dans le rectangle d'épaisseur 1 pixel dessinable entre (0;0) et (20;10).

ellipse

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *


# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")

monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


monCanvas.create_rectangle(0,0,20,10,outline="#dddddd")

monCanvas.create_oval(0,0,20,10)


fen_princ.mainloop()

13° Utiliser le programme comme base pour générer trois ellipses de dimensions différentes, dont un cercle.

On trouve beaucoup d'arguments permettant de personnaliser les ellipses, comme pour les lignes et les rectangles. Parmi celles que je vous propose ici :

14° Tenter de reproduire les affichages suivants à partir du code proposé (il faudra rajouter des arguments adaptés dans les méthodes create_oval() ).

ellipse

Au survol :

ellipse

Le code des rectangles englobants et des ellipses est :

monCanvas.create_rectangle(10,110,150,210,outline="#dddddd")

monCanvas.create_oval(10,110,150,210, ...)


monCanvas.create_rectangle(210,110,280,210,outline="#dddddd")

monCanvas.create_oval(210,110,280,210, ...)


monCanvas.create_rectangle(310,110,450,210,outline="#dddddd")

monCanvas.create_oval(310,110,450,210, ...)

6 - Les polygones

En géométrie euclidienne, un polygone est figure géométrique plane composée d'une ligne brisée fermée sur elle-même.

Dit comme ça, ça parait compliqué mais non. Les exemples courants sont le carré, le triangle, le rectangle, l'hexagone... Voilà pour les cas simples.

On notera qu'un polygone peut être croisé, c'est à dire que ses segments peuvent s'entrecouper.

Pour définir un polygone, il faut donc au moins trois points non alignés.

Pour écrire un polygone dans Tkinter, on utilise create_polygon(x0, y0, x1, y1, x2, y2 ...)

15° Lancer le programme-test suivant pour voir le résultat de n'importe quelle combinaison qui vous viendrait à l'esprit.

Vous pouvez utiliser les paramètres habituels pour gérer la largeur de la bordure (width) , le remplissage (fill) ...

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *


# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")

monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


monCanvas.create_polygon(100,100,100,200,450,300)


fen_princ.mainloop()

polygone

Comme vous pouvez le voir, les options par défaut sont : fill="black" et pas transparent.

Par contre, la bordure est de base définie par outline="", c'est à dire transparent. Le code suivant permet de retrouver les cas précédents (rectangle, oval...) : monCanvas.create_polygon(100,100,100,200,450,300, fill="", outline="black").

polygone

Si vous voulez créer n'importe quel polygone ou presque, il faudra trouver les coordonnées à la main, ou presque. Pour vous aiguiller, voici un programme qui trace un cercle en (250;250) avec un rayon de 150 pixels. Puis il trace un certain nombre de droites de 150 pixels partant de (250;250) après avoir fait une rotation d'un certain angle.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math


def rotation_ligne(leCanvas, x0, y0, longueur, angle, couleur) :

    """

    Cette fonction permet de dessiner une ligne

    leCanvas est la référence du canvas dans lequel on veut dessiner la ligne

    x0, y0 sont les coordonnées d'origine

    longueur est la longueur de la ligne en pixels

    couleur est la couleur sous forme de string : "red" ou "#ff0000"

    angle est l'angle par rapport à l'horizontal en degré

    """

    angle = math.radians(angle) # math.radians permet de convertir en radians

    xf = int(x0+longueur*math.cos(angle))

    yf = int(y0+longueur*math.sin(angle))

    leCanvas.create_line(x0, y0, xf, yf, width=1, fill=couleur)


# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")

monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


monCanvas.create_oval(100,100,400,400)

nbr_cotes = 3

for i in range(0,360, int(360/nbr_cotes)) :

    rotation_ligne(monCanvas, 250, 250, 212, i, "red")


fen_princ.mainloop()

16° Utiliser ce progamme pour parvenir à comprendre comment on peut créer la fonction hexagone qui trace un hexagone lorsqu'on lui donne le point central et la distance entre le centre et les points extremes.

On part de ceci normalement :

polygone

On remarque que les points extrêmes des droites correspondantes ont les coordonnées des points désirés. On réutilise donc en partie le même code pour créer la fonction. J'utilise la méthode append sur des listes de façon à ne pas créer trop de variables. De plus, cela rendra le code plus facilement adaptable au cas des autres polygones réguliers.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

import math


# ----------------------------------------------------------------

# Les fonctions

# ----------------------------------------------------------------

def hexagone(leCanvas, x0, y0, longueur, couleur) :

    """

    x0, y0 sont les coordonnées du point central de la figure

    longueur est la longueur entre le centre et les points extrêmes

    couleur est la couleur sous forme de string : "red" ou "#ff0000"

    Fonctionnement :

    -- On stocke dans les listes x[] et y[] les coordonnées des points définissant la forme."

    -- On utilise ensuite create_polygone en utilisant les contenus des deux listes."

    """

    x = []

    y = []

    for i in range(6) :

        angle = math.radians(i*360/6)

        x.append(int(x0+longueur*math.cos(angle)))

        y.append(int(y0+longueur*math.sin(angle)))

    leCanvas.create_polygon(x[0]y[0],x[1],y[1],x[2],y[2],x[3],y[3],x[4],y[4],x[5],y[5], fill="", outline="black")


# ----------------------------------------------------------------

# Corps du programme

# ----------------------------------------------------------------

fen_princ = Tk()

fen_princ.title("ESSAI AVEC CANVAS")

fen_princ.geometry("600x600")

monCanvas = Canvas(fen_princ, width=500, height=500, bg='ivory', borderwidth=0, highlightthickness=0)

monCanvas.place(x=50,y=50)


hexagone(leCanvas,250,250,150,"red")


fen_princ.mainloop()

Et on obtient :

polygone

Remarque : rajouter le paramètre smooth=1 (pour "adoucir") lors de l'appel à create_polygon.

17° Question au choix :

7 - Les arcs de cercle ou d'ellipse

Il nous reste encore à voir comment faire un pac-man crédible. En trois questions, pour arriver à 20.

Rien de nouveau pour le début : on utilise la méthode create_arc en donnant le point supérieur gauche (qu'on nommera x0,y0) et le point inférieur droit (qu'on nommera x1,y1) du rectangle d'épaisseur 1 qui entoure l'arc de cercle ou d'ellipse.

Voici quelques exemples avec le rectangle-conteneur en trait gris clair. Ici (x0;y0) = (10;10) et (x1;y1) = (100;100).

monCanvas.create_rectangle(10,10,100,100,outline="#dddddd")

monCanvas.create_arc(10,10,100,100)

polygone

monCanvas.create_rectangle(10,10,100,100,outline="#dddddd")

monCanvas.create_arc(10,10,100,100, fill="yellow")

polygone

monCanvas.create_rectangle(10,10,100,100,outline="#dddddd")

monCanvas.create_arc(10,10,100,100, outline="#ff0000")

polygone

De base, on pourrait croire qu'on ne crée que des arcs de cercle (ou d'ellipse) portant de l'axe horizontal et avec un angle de +90° (+ car on tourne dans le sens trigonométrique). Par défaut, l'angle de rotation est de 0° : start=0. C'est pour cela que les arcs du dessus sont tous orientés de la même façon.

Mais on peut faire mieux : on peut indiquer un angle de rotation à l'aide du paramètre start=angle_en_degré.

18° Effectuer différents tests pour parvenir à comprendre le principe de ce paramètre. Tentez par exemple d'obtenir les deux cas ci-dessous (l'image contient trois objets : un rectangle et un arc dont les contours sont très clairs et l'arc de cercle après rotation) :

polygone polygone

On peut aller plus loin en définissant l'angle de l'arc de cercle. Le paramètre se nomme extent. De base, on a extent=90, inutile de préciser l'unité, la méthode vaut des degrés. On crée donc par défaut un quart de cercle. Mais on peut créer des demi-cercle en prenant un angle de 180°, ect ...

19° Effectuer différents tests pour parvenir à comprendre le principe de ce paramètre. Tentez par exemple d'obtenir les deux cas suivants (l'image contient trois objets : un rectangle et un arc de 90° dont les contours sont très clairs et l'arc de cercle après augmentation de l'angle extent) :

polygone polygone

20° Il vous reste à créer les deux images de pac-man, l'un avec la bouche ouverte, et l'autre avec la bouche fermée.

polygone polygone

Vous savez maintenant créer une interface, créer des boutons, gérer les tests, créer des fonctions et afficher du dessin vectoriel dans les Canvas. Il ne nous reste qu'à savoir créer du mouvement, et vous pourrez faire votre premier jeu ! Nous verrons cela dans le prochain chapitre sur les interfaces graphiques.