TP2. Module SVG
Énoncé
L'objectif de cet exercice est de créer un module python SVG
que vous utiliserez tout au long du semestre.
Cet exercice est difficile, mais il est donc nécessaire de prendre le temps de bien le faire et de bien le comprendre.
Étape 1 : comprendre ce qu'est une image SVG
Scalable Vector Graphics (SVG) est un format de données textuelles basé sur XML
permettant de décrire des images vectorielles.
Une image au format SVG
n'est donc rien d'autre qu'un fichier contenant du texte conforme au format de données SVG
.
On peut donc assez facilement, si l'on connaît les bases du format, créer une image SVG
en utilisant notre éditeur de texte favori.
Ouvrez donc votre éditeur de texte préféré et créez un fichier ma-premiere-image.svg
avec le contenu suivant :
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='360' height='340'>
<g stroke='black' stroke-width='2' fill='pink'>
<circle cx='100' cy='20' r='10'/>
<circle cx='260' cy='20' r='10'/>
<circle cx='20' cy='120' r='10'/>
<circle cx='180' cy='120' r='10'/>
<circle cx='340' cy='120' r='10'/>
<circle cx='100' cy='220' r='10'/>
<circle cx='260' cy='220' r='10'/>
<circle cx='180' cy='320' r='10'/>
</g>
</svg>
Ouvrez ensuite ce même ce fichier avec un visualisateur d'image, par exemple eog
:
[selvama@ensipc215]$ eog ma-premiere-image.svg
Félicitations, vous avez créé votre première image SVG
qui doit ressembler à l'image suivante:
Qu'est-ce que je viens d'écrire, là ?
Le format SVG
s'appuie sur du texte structuré par une hiérarchie de balises.
Une balise est un mot-clé permettant de décrire un ou plusieurs éléments de l'image finale.
Comme en HTML ou en XML, le texte est structuré sous la forme d'un enchainement de
balises ouvrantes et fermantes, formatées toujours de la même façon :
<keyword option1='something' option2='somethingelse' ... optionN='anotherthing'>
pour les balises ouvrantes, oùkeyword
indique quel élément de l'image est décrit par cette balise ;</keyword>
pour les balises fermantes.
On distingue dans le fichier SVG
ci-dessus l'utilisation de trois balises différentes :
<svg>
qui définit une image au formatSVG
, avec ses dimensions et la version du standardSVG
utilisée ;<circle>
qui décrit un cercle, avec les coordonnées de son centre et la taille de son rayon, en pixels ;<g>
qui permet de regrouper des éléments de l'image. Cette balise est utile pour factoriser des options qu'on aurait sinon du passer à chaque élément de l'image. Par exemple, ici, on n'indique pas quelle couleur de trait ou de remplissage utiliser pour dessiner les 8 cercles de l'image. On place ces cercles à l'intérieur d'un groupe (entre les balises<g>
et<\g>
), sur lequel on a positionné les attributs de couleur et d'épaisseur de trait, ainsi que la couleur de remplissage.
On remarque aussi qu'il existe une notation contractée pour certaines balises :
<keyword option=... />
La présence du /
juste avant le dernier chevron fait office de balise fermante.
Pour en savoir plus sur ces balises, et sur le format SVG
en général :
https://www.w3schools.com/graphics/svg_intro.asp
Étape 2 : écrire votre module python svg.py
Vous devez maintenant créer un module python que vous nommerez svg.py
, aidant à la génération d'images SVG
.
Le squelette du module est disponible ici et affiché ci-dessous:
"""
Un module pour générer des images au format SVG.
Ce module fournit diverses fonctions pour générer des éléments SVG
sous forme de chaînes de caractères.
Ces chaînes DOIVENT être écrites dans un fichier en respectant la
structure SVG pour obtenir une image valide.
"""
from collections import namedtuple
# Definition de la structure Point composée de deux attributs x et y
Point = namedtuple('Point', 'x y')
def genere_balise_debut_image(largeur, hauteur):
"""
Retourne la chaine de caractères correspondant à la balise ouvrante pour
décrire une image SVG de dimensions largeur x hauteur pixels. Les paramètres
sont des entiers.
Remarque : l’origine est en haut à gauche et l’axe des Y est orienté vers le
bas.
"""
# TODO
...
def genere_balise_fin_image():
"""
Retourne la chaine de caractères correspondant à la balise svg fermante.
Cette balise doit être placée après tous les éléments de description de
l’image, juste avant la fin du fichier.
"""
# TODO
...
def genere_balise_debut_groupe(couleur_ligne, couleur_remplissage, epaisseur_ligne):
"""
Retourne la chaine de caractères correspondant à une balise ouvrante
définissant un groupe d’éléments avec un style particulier. Chaque groupe
ouvert doit être refermé individuellement et avant la fermeture de l’image.
Les paramètres de couleur sont des chaînes de caractères et peuvent avoir
les valeurs :
-- un nom de couleur reconnu, par exemple "red" ou "black" ;
-- "none" qui signifie aucun remplissage (attention ici on parle de la chaîne
de caractère "none" qui est différente de l'objet None).
Le paramètre d’épaisseur est un nombre positif ou nul, représentant la
largeur du tracé d'une ligne en pixels.
"""
# TODO
...
def genere_balise_fin_groupe():
"""
Retourne la chaine de caractères correspondant à la balise fermante pour un
groupe d’éléments.
"""
# TODO
...
def genere_cercle(centre, rayon):
"""
Retourne la chaine de caractères correspondant à un élément SVG représentant
un cercle (ou un disque, cela dépend de la couleur de remplissage du groupe
dans lequel on se trouve).
centre est une structure de données de type Point, et rayon un nombre de
pixels indiquant le rayon du cercle.
"""
# TODO
...
Étape 3 : tester votre module python svg.py
Il faut maintenant tester votre module.
Pour cela, créez un fichier test_svg.py
qui importe votre module et l'utilise pour dessiner les cercles de vos rêves.
Si votre environnement de développement supporte les redirections, alors votre programme de test pourra simplement afficher le contenu de l'image SVG
sur la sortie standard.
Celle-ci sera ensuite redirigée dans un fichier au moment de l'exécution à l'aide d'une redirection.
Si votre environnement de développement ne supporte les redirections, alors votre programme de test devra directement écrire le contenu de l'image SVG
dans un fichier.
Pour cela vous utiliserez les fonctions open
, print
et close
vues dans l'exercice précédent.
Difficulté
Correction
Cliquez ici pour révéler la correction de l'exercice.
Corrigé du fichier svg.py
"""
Un module pour générer des images au format SVG.
Ce module fournit diverses fonctions pour générer des éléments SVG
sous forme de chaînes de caractères.
Ces chaînes DOIVENT être écrites dans un fichier en respectant la
structure SVG pour obtenir une image valide.
"""
from collections import namedtuple
# Definition de la structure Point composée de deux attributs x et y
Point = namedtuple('Point', 'x y')
def genere_balise_debut_image(largeur, hauteur):
"""
Retourne la chaine de caractères correspondant à la balise ouvrante pour
décrire une image SVG de dimensions largeur x hauteur pixels. Les paramètres
sont des entiers.
Remarque : l’origine est en haut à gauche et l’axe des Y est orienté vers le
bas.
"""
balise = "<svg xmlns='http://www.w3.org/2000/svg' version='{version}' " \
"width='{largeur}' height='{hauteur}'>"
return balise.format(version="1.1",
largeur=largeur,
hauteur=hauteur)
def genere_balise_fin_image():
"""
Retourne la chaine de caractères correspondant à la balise svg fermante.
Cette balise doit être placée après tous les éléments de description de
l’image, juste avant la fin du fichier.
"""
return "</svg>"
def genere_balise_debut_groupe(couleur_ligne, couleur_remplissage, epaisseur_ligne):
"""
Retourne la chaine de caractères correspondant à une balise ouvrante
définissant un groupe d’éléments avec un style particulier. Chaque groupe
ouvert doit être refermé individuellement et avant la fermeture de l’image.
Les paramètres de couleur sont des chaînes de caractères et peuvent avoir
les valeurs :
-- un nom de couleur reconnu, par exemple "red" ou "black" ;
-- "none" qui signifie aucun remplissage (attention ici on parle de la chaîne
de caractère "none" qui est différente de l'objet None).
Le paramètre d’épaisseur est un nombre positif ou nul, représentant la
largeur du tracé d'une ligne en pixels.
"""
balise = "<g stroke='{ligne}' fill='{remplissage}' stroke-width='{epaisseur}'>"
return balise.format(ligne=couleur_ligne,
remplissage=couleur_remplissage,
epaisseur=epaisseur_ligne)
def genere_balise_fin_groupe():
"""
Retourne la chaine de caractères correspondant à la balise fermante pour un
groupe d’éléments.
"""
return "</g>"
def genere_cercle(centre, rayon):
"""
Retourne la chaine de caractères correspondant à un élément SVG représentant
un cercle (ou un disque, cela dépend de la couleur de remplissage du groupe
dans lequel on se trouve).
centre est une structure de données de type Point, et rayon un nombre de
pixels indiquant le rayon du cercle.
"""
balise = "<circle cx='{x}' cy='{y}' r='{r}' />"
return balise.format(x=centre.x,
y=centre.y,
r=rayon)
Corrigé du fichier test_svg.py
#!/usr/bin/env python3
"""Un programme pour tester notre module svg"""
# On importe le module sys pour accéder à la sortie standard
import sys
# On importe tout le contenu du module svg
import svg
# Création de trois points
c1 = svg.Point(10, 10)
c2 = svg.Point(100, 100)
c3 = svg.Point(180, 50)
# On utilise un booléen pour choisir entre print
# sur la sortie standard ou dans un fichier.
# Il suffit donc de changer cette variable pour
# contrôler la sortie du programme de test.
SORTIE_STANDARD = True
# On choisit où est-ce que l'on va faire nos prints
if SORTIE_STANDARD:
out = sys.stdout
else:
out = open("mon_image.svg", "w")
# Print d'une image SVG avec 3 cercles sur la sortie standard ou dans
# un fichier.
# Pour pouvoir visualiser l'image il faudra donc utiliser une redirection
# si l'on utilise la sortie standard :
# ./test_svg.py > mes-cercles.svg
# Une fois l'image SVG enregistrée dans un fichier texte, on peut la
# visualiser à l'aide de n'importe quel programme supportant ce format
# d'image :
# eog mon-triangle.svg
# firefox mon-triangle.svg
# double-clic sur le fichier dans un explorateur de fichiers
print(svg.genere_balise_debut_image(200, 200), file=out)
print(svg.genere_balise_debut_groupe("black", "red", 5), file=out)
print(svg.genere_cercle(c1, 30), file=out)
print(svg.genere_balise_fin_groupe(), file=out)
print(svg.genere_balise_debut_groupe("red", "black", 5), file=out)
print(svg.genere_cercle(c2, 60), file=out)
print(svg.genere_cercle(c3, 10), file=out)
print(svg.genere_balise_fin_groupe(), file=out)
print(svg.genere_balise_fin_image(), file=out)
# On ferme le fichier si il a été créé
if SORTIE_STANDARD:
out.close()
Explications
En supplément de la correction textuelle ci-dessus, une correction plus détaillée en vidéo est disponible :