Skip to content

Module SVG

Énoncé

L'objectif de cet exercice est de créer un module python SVG que vous utiliserez tout au long du semestre. 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 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<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 :

1
[selvama@ensipc215]$ eog ma-premiere-image.svg

Félicitations, vous avez créé votre première image SVG qui doit ressembler à l'image suivante:

Ma première image SVG ^^

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 format SVG, avec ses dimensions et la version du standard SVG 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 : implémenter votre module python svg.py

Vous devez maintenant implémenter un module python que vous nommerez svg.py, aidant à la génération d'images SVG.

Le squelette du module, a compléter, est disponible ici et affiché ci-dessous. Vous pouvez commencer par l'implémentation des fonctions genere_balise_fin_image et genere_balise_fin_groupe qui sont les plus simples car elles ne nécessitent pas l'utilisation de f-strings.

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
"""
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 la méthode close du type str vues dans l'exercice précédent.

Cliquez ici pour révéler la correction.

Correction

Voici le code de correction du module svg.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""
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.
    """
    # Les parenthèses sont utilisées ici uniquement pour permettre
    # de "couper" la f-string en deux ligne afin de ne pas avoir
    # une ligne trop longue. Il existe d'autres moyen de faire,
    # mais l'utilisation de parenthèses est celui recommandé par
    # le guide de style officiel python.
    #
    # On notera également que la chaîne de caractère renvoyée
    # contient des guillemets doubles. Pour que celles-ci ne
    # soient pas considérées comme la fin de la f-string, on
    # utilise des guillemets simples pour délimiter cette
    # dernière.
    return (
        f'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" '
        f'width="{largeur}" height="{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.
    """
    return (
        f'<g stroke="{couleur_ligne}" fill="{couleur_remplissage}" '
        f'stroke-width="{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.
    """
    return f'<circle cx="{centre.x}" cy="{centre.y}" r="{rayon}" />'

Voici un exemple d'utilisation du module svg.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/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 not SORTIE_STANDARD:
    out.close()

En supplément de la correction textuelle ci-dessus, une correction plus détaillée en vidéo est disponible :

Difficulté

star star star