Dans le cadre de notre projet d’art génératif en Première NSI, nous avons décidé de concevoir un paysage en perspective qui représente Paris pendant les Jeux Olympiques de 2024. Notre objectif est de mêler des éléments architecturaux emblématiques de la ville avec l’ambiance olympique qui y règne.
Sommaire
Origine des JO
Cet été 2024 à Paris a eu lieu les JO. Les JO sont des événements sportifs internationaux majeurs, regroupant les sports d’été ou d’hiver, auxquels des milliers d’athlètes participent à travers différentes compétitions tous les quatre ans, pour chaque olympiade moderne.
Pour plus d’informations, nous vous conseillons cette vidéo explicative de l’origine des JO.
Le Projet
Revenons au projet. Nous avons choisi de créer une image des Champs Elysées pendant la période des JO 2024. Pour cela, nous utilisons les modules turtle
et random
. Le module turtle
permet de dessiner l’image, tandis que le module random
génère aléatoirement la taille et la position des étoiles dans le ciel. Nous avons également intégré un script pour exporter l’image générée par turtle en format .png, que vous pouvez retrouver ici.
Structure du script
Nous avons structuré le script en séparant chaque élément de l’image (fond dégradé, étoiles, route, trottoirs, arc de triomphe, anneaux olympiques, bâtiments, arbres) dans des fonctions distinctes. Ensuite, nous avons appelé ces fonctions dans un ordre spécifique, permettant ainsi à chaque élément de se superposer correctement dans son propre plan.
Analyse du script
Nous allons donc analyser notre script.
On commence d’abord par l’appel des modules utilisés et la mise en place turtle
.
from turtle import * from random import randint # Vérification des modules importés try: from PIL import Image pillow_installed = True except: print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :") print("https://nsi.xyz/py2png") pillow_installed = False # Définir le titre de la fenêtre de turtle titre = "Perspective Paris 2024 - construite avec turtle" title(titre+" | Au lycée, la meilleure spécialité, c'est la spé NSI") colormode(255) # Permet l'utilisation de couleurs rgb setup(1280, 720) # Définir la taille de la fenêtre en 720p speed(0) # La tortue va à la vitesse du lièvre hideturtle() # La tortue active sa cape d'invisibilité flash = True # False par défaut, on peut mettre True sinon, ou mieux 0x2A if flash: wn = Screen() wn.tracer(0)
On utilise les modules turtle
et random
, en se concentrant uniquement sur la fonction randint
de random
. Le script inclut également une partie pour exporter une image générée par turtle
. Cela permet de vérifier si l’utilisateur a installé le module PIL ; si ce n’est pas le cas, un message d’erreur s’affiche avec un lien pour l’installer, sans interrompre l’exécution du script. Ensuite, on définit le titre de la fenêtre qui affichera le rendu ainsi que sa taille. On configure également le type de couleurs utilisées (R,G,B), la vitesse de la tortue et on cache la tortue pour une présentation plus esthétique.
Puis, voici notre première fonction : le fond dégradé sur la partie haute de l’image.
def fond(): red, green, blue = 0, 0, 100 penup() hauteur = 0 penup(), goto(-641, hauteur) while hauteur != 360: pendown() pencolor(round(red), round(green), round(blue)) forward(1280) hauteur += 1 goto(-640, hauteur) blue = blue - float(100 / 360)
Pour le fond, nous avons donc choisi d’utiliser un dégradé. Le script fait avancer la tortue sur une ligne d’un pixel de large et à la fin de chaque ligne, la tortue est envoyée à la ligne suivante grâce à un goto
. À chaque déplacement, on ajuste progressivement les valeurs des couleurs, de façon arrondie (dans notre cas, on ajuste seulement la valeur du bleu) en fonction de la différence entre la couleur de début et celle de fin du dégradé.
Ensuite, vient la deuxième fonction, celle des étoiles dans le ciel.
def etoiles (x_max1, x_max2, y_max1, y_max2, r_max1, r_max2, nb_etoiles): for i in range(nb_etoiles): x, y, r = randint(x_max1,x_max2), randint(y_max1,y_max2), randint(r_max1,r_max2) pencolor("#FFFFFF"), begin_fill() penup(), goto(x, y), pendown() circle (r) fillcolor("#FFFFFF"), end_fill()
Pour les étoiles, leur position dans le ciel et leur taille, variant entre 1 et 3 pixels de rayon, sont déterminées aléatoirement avec la fonction randint
du module random
. Ensuite, un cercle est tracé à chaque position générée avec la taille correspondante. Ce processus est répété 100 fois dans une boucle for i in range
, afin de créer 100 étoiles au total.
Vient ensuite la fonction pour dessiner la route et les trottoirs (le script suivant n’est qu’une partie du code complet, long et répétitif, il n’est donc pas nécessaire de commenter la suite).
def base_route(xi, yi, xf, yf, col_route, larg_debut_route, larg_fin_route): penup(), goto(xi, yi), pendown() pencolor(col_route) begin_fill(), fillcolor(col_route) goto(xi - (1 / 2 * larg_debut_route), yi), goto(xi + (1 / 2 * larg_debut_route), yi), goto(xf + (1 / 2 * larg_fin_route), yf) goto(xf - (1 / 2 * larg_fin_route), yf), goto(xi - (1 / 2 * larg_debut_route), yi) end_fill() def ligne_entre_routes(xi, yi, xf, yf, col_lignes): penup(), goto(xi, yi), pendown() left(90) size = 20 for i in range (357): pensize(round(size)) size = size - float(15 / 360) pencolor(col_lignes) forward(1) def marquage_gauche(xi, yi, xf, yf, col_lignes, long_marquages, larg_debut_route): longueur = 360 penup(), goto(xi - 1 / 4 * larg_debut_route, yi), pendown() pensize(7), pencolor(col_lignes) right(40) k = 0 while k * long_marquages < longueur - long_marquages: k += 2 pendown(), forward(long_marquages), penup(), forward(long_marquages) def trottoir_droite(xi, yi, xf, yf, col_trottoir, larg_debut_route, larg_fin_route, larg_debut_trot, larg_fin_trot): penup(), goto(xi + (1 / 2 * larg_debut_route), yi), pendown() pencolor(col_trottoir) pensize(2) begin_fill(), fillcolor(col_trottoir) goto(xi + (1 / 2 * larg_debut_route) + (1 / 2 * larg_debut_trot), yi), goto(xf + (1 / 2 * larg_fin_route) + (1 / 2 * larg_fin_trot), yf) goto(xf + (1 / 2 * larg_fin_route), yf), goto(xi + (1 / 2 * larg_debut_route), yi) end_fill() penup(), goto(xi + (1 / 2 * larg_debut_route) + 20, yi), pendown() pensize(3), pencolor("#6C757D") begin_fill(), fillcolor("#6C757D") goto(xf + (1 / 2 * larg_fin_route) + 7.5, yf), goto(xf + (1 / 2 * larg_fin_route), yf) goto(xi + (1 / 2 * larg_debut_route), yi), goto(xi + (1 / 2 * larg_debut_route) + 20, yi) end_fill() def route_et_trottoir(xi, yi, xf, yf, col_route, col_trottoir, col_lignes, col_pavage, long_marquages, larg_debut_route, larg_fin_route, larg_debut_trot, larg_fin_trot): base_route(xi, yi, xf, yf, col_route, larg_debut_route, larg_fin_route) ligne_entre_routes(xi, yi, xf, yf, col_lignes) marquage_droit(xi, yi, xf, yf, col_lignes, long_marquages, larg_debut_route) marquage_gauche(xi, yi, xf, yf, col_lignes, long_marquages, larg_debut_route) trottoir_droite(xi, yi, xf, yf, col_trottoir, larg_debut_route, larg_fin_route, larg_debut_trot, larg_fin_trot) trottoir_gauche(xi, yi, xf, yf, col_trottoir, larg_debut_route, larg_fin_route, larg_debut_trot, larg_fin_trot)
Pour la route et les trottoirs, nous commençons par tracer la base de la route en perspective en utilisant la fonction goto
, puis nous ajoutons les marquages au sol. La ligne centrale entre les deux voies est progressivement affinée pour renforcer l’effet de perspective. Ensuite, les trottoirs de chaque côté sont tracés avec goto
, en appliquant les mêmes proportions pour respecter la perspective. Enfin, une fonction finale regroupe toutes les fonctions constituant notre route et nos trottoirs.
Nous poursuivrons ensuite avec le codage de l’Arc de Triomphe (le script est encore long, il ne sera donc pas intégral ici).
def pilier_gauche(x, y, larg, long, col): pencolor(col) begin_fill(), fillcolor(col) penup(), goto(x - larg,y), pendown() right(70) for i in range(2): forward(larg), left(90), forward(long - 40), left(90) end_fill() def atique_arc_de_triomphe(x, y, larg, long, col): pencolor(col) penup(), goto(x + larg, y + long - 40), left(90), pendown() begin_fill() circle((1 / 2 * larg), 180) for i in range(2): right(90), forward(larg) right(90), forward(3 / 2 * (long)) for i in range(2): right(90), forward(larg) fillcolor(col), end_fill() def rajout_atique(x, y, larg, long, col): pencolor(col) penup(), goto(x + (long), y + ((long - 40) + larg)), pendown() begin_fill(), fillcolor(col) right(90) for i in range(2): forward(larg - (1 / 2 * larg)), left(90), forward((long) + larg), left(90) end_fill() def details_arc_de_triomphe(x, y, larg, long, col_detail): penup(), goto(x + (long), y + (long - 40 + larg)), pendown() pensize(5), pencolor(col_detail) left(90), forward(long + larg) def arc_de_triomphe(x, y, larg, long, col): pilier_gauche(x, y, larg, long, col) pilier_droite(x, y, larg, long, col) atique_arc_de_triomphe(x, y, larg, long, col) rajout_atique(x, y, larg, long, col) details_arc_de_triomphe(x, y, larg, long, "#9C7F6E")
Pour l’Arc de Triomphe, nous avons choisi de le coder étape par étape : d’abord les piliers, puis l’attique, et enfin les détails. Les piliers sont représentés par de simples rectangles colorés, en utilisant les commandes begin_fill
, fillcolor
, et end_fill
. Pour l’attique, nous avons dessiné un arc de cercle avec circle(rayon, 180)
pour représenter la courbure. Enfin, toutes les fonctions constituant l’Arc de Triomphe sont appelées dans une fonction unique pour générer l’ensemble du monument.
Passons maintenant au code pour générer les anneaux olympiques.
list_col_anneau = ["#0D79FF", "#FFD900", "#000000", "#12C52D", "#FF0F0F"] def anneau_haut(x,col,rayon,size): penup(), forward(x), pendown() pencolor(col), pensize(size), circle(rayon) def anneau_bas(x,col,rayon,size): penup(), left(90), forward(x), pendown() pencolor(col), pensize(size), circle(rayon) def anneaux(xf,yf,rayon,size): penup(), goto(xf,yf), pendown() anneau_haut(0,list_col_anneau[0],rayon,size) anneau_haut(2 * rayon + (size + 5),list_col_anneau[2],rayon,size) anneau_haut(2 * rayon + (size + 5),list_col_anneau[4],rayon,size) penup(), goto(xf - rayon - (1 / 5 * rayon), yf - rayon), right(90), pendown() anneau_bas(0,list_col_anneau[1],rayon,size) right(90) anneau_bas(2 * rayon + (size + 5),list_col_anneau[3],rayon,size)
Nous avons commencé par définir une liste des couleurs pour chaque anneau. Ensuite, les anneaux de la rangée supérieure ont été dessinés, suivis de ceux de la rangée inférieure, en utilisant la fonction circle
. Enfin, nous avons regroupé le tout dans une fonction unique qui positionne les anneaux correctement.
Nous poursuivrons ensuite avec le script dédié à la lune et à ses cratères (le code des cratères étant répétitif, il ne sera pas présenté dans sa totalité).
def cratere1 (x, y, rayon_lune, col): x_crat1 = x y_crat1 = y + rayon_lune + (1 / 4 * rayon_lune) taille_crat1 = 1 / 3 * rayon_lune penup(), goto(x,y), goto(x_crat1,y_crat1), pendown() begin_fill(), pencolor(col) circle(taille_crat1) fillcolor(col), end_fill() def crateres(x, y, rayon_lune, col): cratere1 (x, y, rayon_lune, col) cratere2 (x, y, rayon_lune, col) cratere3 (x, y, rayon_lune, col) cratere4 (x, y, rayon_lune, col) cratere5 (x, y, rayon_lune, col) def lune(x, y, rayon_lune, col_lune): left(180) begin_fill() penup(), goto(x,y), pendown() pencolor(col_lune), circle(rayon_lune), fillcolor(col_lune), end_fill() crateres(x, y, rayon_lune, "#DEDCDA")
A propos de la lune et de ses cratères, premièrement, chaque fonction (cratere1
à cratere5
) dessine un cratère à des positions spécifiques autour de la lune, en utilisant le rayon de la lune pour ajuster la taille et la position. Deuxièmement, la fonction crateres
regroupe tous les cratères en une seule fonction. Et au final, la fonction lune
dessine la lune en tant que cercle rempli, puis appelle la fonction crateres
pour ajouter les cratères à la surface de la lune.
Nous allons maintenant aborder le codage des bâtiments.
def bat_droite(xi, yi, xf, yf, col, col_trace, hauteur): pencolor(col_trace), pensize(3) begin_fill(), fillcolor(col) penup(), goto(xi, yi), pendown() goto(xf, yf), goto(xf, hauteur), goto(1282, hauteur), goto(xi, yi) end_fill() penup(), goto(xf, hauteur), pendown() right(15), forward(500) def bat_gauche(xi, yi, xf, yf, col, col_trace, hauteur): pencolor(col_trace), pensize(3) begin_fill(), fillcolor(col) penup(), goto(xi, yi), pendown() goto(xf, yf), goto(xf, hauteur), goto(-1282, hauteur), goto(xi, yi) end_fill() penup(), goto(xf, hauteur), pendown() left(210), forward(500)
Pour les bâtiments, les fonctions dessinent des structures à droite et à gauche, en utilisant des coordonnées spécifiées, une couleur de remplissage, une couleur de contour et une hauteur. Chaque fonction remplit le bâtiment et trace un toit incliné, créant ainsi un effet de perspective qui enrichit visuellement l’image.
Nous allons maintenant découvrir le script qui permet de dessiner des arbres le long des trottoirs.
def tronc(x, y, larg_tronc, col_tronc, long_tronc): begin_fill() penup(), goto(x,y), pendown() pencolor(col_tronc) for i in range(2): forward(larg_tronc), left(90), forward(long_tronc), left(90) fillcolor(col_tronc), end_fill() def feuilles(x, y, col_feuilles, ray_feuilles, larg_tronc, long_tronc, col_tronc): begin_fill() penup(), goto(x + (1 / 2 * larg_tronc),y + long_tronc), pendown() pencolor(col_feuilles) for i in range(5): begin_fill() circle(ray_feuilles), left(72), forward(10) fillcolor(col_feuilles), end_fill() def arbre(x, y, col_feuilles, ray_feuilles, larg_tronc, long_tronc, col_tronc): tronc(x, y, larg_tronc, col_tronc, long_tronc) feuilles(x, y, col_feuilles, ray_feuilles, larg_tronc, long_tronc, col_tronc) # On place les arbres sur le trottoir def arbres(): x = -275 y = -25 ray_feuilles = 20 larg_tronc = 22 long_tronc = 80 right(195) for i in range(14): arbre(x, y, "#009B1A", ray_feuilles, larg_tronc, long_tronc, "#7F2C00") x -= 25 y -= 25 ray_feuilles += float(2.5) larg_tronc += float(2.5) long_tronc += 10 x = 258 y = -25 ray_feuilles = 20 larg_tronc = 22 long_tronc = 80 for i in range(14): arbre(x, y, "#009B1A", ray_feuilles, larg_tronc, long_tronc, "#7F2C00") x += 22 y -= 25 ray_feuilles += float(2.5) larg_tronc += float(2.5) long_tronc += 10
Concernant les arbres, tout d’abord, la fonction tronc
dessine le tronc d’un arbre en utilisant des coordonnées spécifiques, une largeur, une couleur et une longueur. Elle utilise une boucle pour créer un rectangle rempli représentant le tronc. Après, la fonction feuille
dessine les feuilles de l’arbre. Elle positionne le curseur au-dessus du tronc et utilise une boucle pour créer plusieurs cercles qui représentent les feuilles, disposés en étoile autour de la partie supérieure du tronc. Suite à quoi la fonction arbre
combine les fonctions tronc
et feuilles
pour dessiner un arbre complet à des coordonnées données. Pour couronner le tout, la fonction arbres
place plusieurs arbres le long des trottoirs. Elle commence à des coordonnées spécifiques et utilise une boucle pour dessiner 14 arbres de chaque côté. La position, la taille des feuilles, la largeur et la longueur du tronc augmentent progressivement pour créer un effet de perspective, tandis que les arbres se déplacent vers le bas et de manière symétrique des deux côtés.
Enfin, nous appelons chaque fonction dans un ordre spécifique pour produire l’image finale.
# Appel de toutes les fonctions fond() etoiles(-640, 640, 0, 360, 1, 3, 100) route_et_trottoir(0, -360, 0, 0, "#424345", "#ADB5BD", "#CECDC9", "#6C757D", 30, 800, 300, 1000, 375) arc_de_triomphe(-50, 0, 100, 200, "#D4C4B0") anneaux(70,300,30,7) lune(250, 200, 50, "#F4F1ED") bat_droite(900, -360, 337.5, 0, "#eae2b7", "#d4a373", 250) bat_gauche(-900, -360, -337.5, 0, "#eae2b7", "#d4a373", 250) arbres()
Et pour exporter l’image finale en .png il y a la suite du script pour exporter une image générée par turtle en .png utilisé au début du script.
if flash: wn.update() # Enregistrement de l'image finale image = getcanvas() nom_du_fichier_sans_extension=titre+"_"+hex(randint(2**30+2**25,2**30+2**25+2**24-1))[2:] image.postscript(file=nom_du_fichier_sans_extension+".ps", colormode='color') try: psimage = Image.open(nom_du_fichier_sans_extension+".ps") psimage.load(scale=2) psimage_resized = psimage.resize((1280, 720)) psimage.save(nom_du_fichier_sans_extension+".png") print(nom_du_fichier_sans_extension+".png", psimage.size, "sauvegardé dans le dossier") # Vérification des modules importés except: if not pillow_installed: print("Oops! - ModuleNotFoundError: No module named 'PIL' - RTFM :") print("https://nsi.xyz/py2png") else: print("Oops! - 'ghostscript' not installed- RTFM :") print("https://nsi.xyz/py2png") exitonclick()
Le script va donc générer une image en .ps et la convertir en .png avec un nom généré aléatoirement.