Un château parmi les cerisiers
Dans le cadre du premier projet de NSI de première, nous avons choisi de réaliser une image printanière au crépuscule, représentant un château japonais entouré de cerisiers en fleur. Nous espérons ainsi pouvoir vous faire profiter d’un petit voyage à notre humble échelle ! Cet article explique comment fonctionnent les principales parties du code utilisé pour produire l’image finale.
L’objectif du projet
Le projet a pour objectif de générer une image en python, à partir de l’art génératif. Ainsi, le module turtle
en python, mais également l’emploi de nos connaissances de cours, permettent de faire découvrir ce qu’il est possible de créer en ayant un thème libre qui laisse la place à l’imagination – dans la limite des 404 lignes imposées. Notre thème est donc un château d’inspiration japonaise, au coeur de la floraison des cerisiers.
Une progression pas à pas
La réalisation du projet a été progressive : avant même de s’atteler au code, nous sommes passés par une étape dessin papier, couplée à de nombreuses recherches. Nous pouvons donc affirmer que les cerisiers et le château sont le fruit d’une documentation minutieuse !
Ensuite, notre image, assemblée au fur et à mesure, s’est faite après l’ajout de nombreux éléments de décor. Ainsi, si la structure de base peut sembler simpliste, elle est néanmoins complétée méthodiquement afin de former le rendu final.
C’est donc en progressant à tâtons, réglant chaque élément au pixel près, et en relançant immanquablement le code des dizaines de fois que nous en sommes arrivés ici. Les traces les plus visibles de ce cheminement sont la structure elle même du code, majoritairement en fonction, puisqu’il est plus facile de changer les différents paramètres sous cette forme.
L’article qui suit commente et illustre donc étape par étape la formation du code générant le dessin.
Le ciel et la colline
Tout d’abord, nous avons généré le fond de l’image, composé de deux fonctions. Le ciel, succession de cercles de plus en plus grands, est tracé en quatre étapes : les cercles sont d’abord jaune, puis un dégradé mène la couleur vers le orange, progressivement rougeâtre, qui enfin rempli le reste de l’image. La colline est quand à elle tracée grâce à un très grand cercle vert, légèrement dégradé vers le jaune en haut. Les couleurs et la taille changent en utilisant l’incrémentation progressive par la fonction for in range ()
de l’indice i.
Une fonction placement
, prenant des coordonnées en argument, est employée dès le début, mais également tout au long du programme pour positionner la tortue sans laisser trace de son déplacement.
#fonction qui trace le fond et le dégradé elle est compactée autant que possible. def fond(r,g,b): pensize(4) nb_r=[150,260,237,300] for j in range(len(nb_r)): for i in range(nb_r[j]): circle(nb_r[0]*(j//(j-0.1))+(nb_r[1]/2)*((j/2)//1)+(nb_r[2]*((j)//3)//1)+(i*((((((j+1)/(j+1))/(j+1))*2+0.5)//1)/2))*((j//2)+1)) placement((0,0-nb_r[0]*(j//(j-0.1))-(nb_r[1]/2)*((j/2)//1)-(nb_r[2]*((j)//3)//1)-(i*((((((j+1)/(j+1))/(j+1))*2+0.5)//1)/2))*((j//2)+1))) color(int((255-int(79*(j//3)))-(i//((j//2)+2-(j//3)))*(j//2)),0+int(((209-130*(j//2)-(i//(j+1))*(j//(j-0.1))))*(1-((j/3)//1))),0) #fond détaillé ''' color(255, 209, 0) pensize(4) for i in range (150): circle(i) placement((0,0-i)) for i in range (260): circle(150+(i/2)) placement((0,-150-(i/2))) color(255, 209-i//2, 0) for i in range(237): circle(280+i) placement((0,-280-i)) color(255-i//3,79-i//3,0) for i in range(300): circle(517+i) placement((0,-517-i)) color(176-i//2,0,0) ''' #fonction qui trace la colline def colline(): pensize(5) up() fillcolor(84,148, 49) begin_fill() placement((0,-4000)) circle(1940) down() end_fill() for i in range(60): circle(1940+i//2) color(84+i,148+i//3,49) #une fonction qui simplifie le déplacement def placement (x): up() goto(x) down() fond(255,209,0) colline()
Le château
Le château est une fonction faisant appel à deux autres fonctions, respectivement celles qui tracent les toits et les murs. Partant du sommet de la structure, la fonction château
permet d’agrandir au fur et à mesure les étages. Elle calcule donc aussi progressivement les coordonnées des différents éléments selon le coefficient entré en paramètre. Les toits, deux segments reliés par des quarts de cercle, et les murs, des rectangles blancs, ne sont ainsi que de simples figures géométriques dont la taille varie.
#fonction qui trace les toits du chateau def toit(taille,rayon,x,y): color(60,60,60) placement((x,y)) fillcolor(60,60,90) begin_fill() circle(rayon,90) setheading(0) forward(taille) setheading(270) circle(rayon,90) backward(taille+(rayon*2)) end_fill() #fonction qui trace les murs du chateau def mur(hauteur,longueur,x,y): placement((x,y)) for _ in range(2): color("white") fillcolor(255,255,255) begin_fill() forward(longueur) left(90) forward(hauteur) left(90) end_fill() #structure globale du chateau def chateau(t): car_toit=[30*t,20*t,-((30*t)/2+20*t),215] car_mur=[20*t,40*t,-(40*t)/2,car_toit[3]-car_toit[1]] for i in range(4): toit(car_toit[0]+i*16*t,car_toit[1],car_toit[2],car_toit[3]) mur(car_mur[0]+2*i*t,car_mur[1]+i*15*t,car_mur[2],car_mur[3]) car_toit[2] = int(-((car_toit[0]+16*(i+1)*t+40*t)/2)) car_toit[3] = car_mur[3]-20*t car_mur[2] = int(-((car_mur[1]+15*(i+1)*t)/2)) car_mur[3] = car_toit[3]-(car_mur[0]+2*(i+1)*t) chateau(2)
Les finitions du château
Dans une volonté d’imiter les véritables château japonais, nous avons décidé de rajouter des éléments permettant une forme d’authenticité. Nous avons donc choisi de représenter les toits de face par des triangles isocèles de différentes tailles. Les fenêtres sont, quand à elles, de simples rectangles dont l’espacement et le nombre sont définis dans les arguments. De même, la porte principale est composée de deux rectangles marrons dont les contours sont gris. Une utilisation de la fonction toit
(voir la section le château), qui couvre l’entrée, achève de compléter le château.
#ornements du chateau def toit_face(x,taille): color(60,60,60) placement(x) fillcolor(60,60,90) setheading(0) begin_fill() forward(taille) left(150) forward(taille*0.6) left(60) forward(taille*0.6) end_fill() #fonction qui rajoute les fenetres du chateau def fenetres(x,y,espacement,nb_fenetre): color(60,60,60) placement((x,y)) for n in range(nb_fenetre): setheading(0) begin_fill() for i in range(2): forward(4) left(90) forward(7) left(90) end_fill() placement((x+(espacement*(i+n)),y)) #fonction qui trace la porte du chateau def porte(longueur,hauteur,x,y): pensize(4) color(60,60,60) placement((x,y)) for i in range(2): for i in range(2): fillcolor(71, 27, 12) begin_fill() forward(longueur) left(90) forward(hauteur) left(90) end_fill() placement((x+longueur,y)) toit_face((-45,91),45) toit_face((0 ,91),45) toit_face((-30,3),60) fenetres(-27,195,25,3) fenetres(-60,27,30,2) fenetres(28,27,30,2) porte(20,30,-20,-89) toit(36,12,-30,-59)
Les cerisiers
La fonction pour tracer un cerisier fait appel à deux autres fonctions. La première trace le tronc, tandis que la seconde complète l’arbre en y ajoutant les branches. Ces dernières sont toutes différentes : les arguments entrés permettent notamment de régler la taille, et la présence éventuelle d’une fleur. Des listes sont également employées afin d’enregistrer les positions au fur et à mesure, facilitant grandement le code. Enfin, les pétales tombés au sol sont placés aléatoirement, à l’aide du module random
. Chaque dessin tracé grâce à ce code est ainsi unique !
positions=[] pos2=[] #fonction qui trace le tronc d'un cerisier def tronc(t): positions.append(pos()) begin_fill() setheading(70) forward(25*t) left(20) forward(75*t) right(90) positions.append(pos()) forward(20*t) positions.append(pos()) right(90) forward(70*t) left(25) forward(30*t) placement(positions[0]) end_fill() placement(positions[2]) #fonction qui trace les branches du cerisier def branche(n,r,s,t): pos_fill=pos() begin_fill() setheading(r) forward((50/n*t)/2) if n==1: pos2.append(pos()) forward((50/n*t)/2) right(90*s) if n<3: positions.append(pos()) forward(8/n*t) if n<3: positions.append(pos()) right(90*s) forward((70/n*t)/2) if n==1: pos2.append(pos()) forward((70/n*t)/2) placement(pos_fill) end_fill() if n>=3: backward(70/n*t) color(244, 194, 194) pensize(10) forward(1) pensize(1) color(90,22,6) #fonction qui trace un cerisier def cerisier(x,t): ori_br=[35,77,102,125,150] fleur(x) color(90,22,6) fillcolor(90,22,6) placement(x) tronc(t) direc=1 for i in range(5): branche(1,ori_br[i],direc,t) placement(pos2[-2]) branche(3,ori_br[i]+35,1,t) placement(pos2[-1]) branche(3,ori_br[i]-35,-1,t) placement(positions[2]) if i<2: placement((pos()[0]-(i+1)*8,pos()[1])) else: direc=-1 placement((pos()[0]-16,pos()[1])) for j in range(2): for i in range(5+j*5): for k in range(2-j): placement(positions[(i*2)+(j*10)+3]) branche(2+j+k,ori_br[i//(1+j)]+30+(j*30)+(k*40),-1,t) placement(positions[(i*2)+(j*10)+4]) branche(2+j+k,ori_br[i//(1+j)]-20+(j*-20)+(k*-30),1,t) placement(((positions[(i*2)+(j*10)+3][0]+positions[(i*2)+(j*10)+4][0])/2,(positions[(i*2)+(j*10)+3][1]+positions[(i*2)+(j*10)+4][1])/2)) branche(3,ori_br[i//(1+j)],1,t) positions.clear() pos2.clear() #fonction qui trace les fleurs de cerisier sur le sol def fleur(pos): for i in range(10): placement((pos[0]+randint(-45,50),pos[1]+randint(-40,30))) color('#FFC0CB') pensize(10) forward(1) pensize(1) cerisier((-190,-150),0.75) cerisier((-310,-300),0.75) cerisier((-490,-240),0.75) cerisier((150,-280),0.75) cerisier((280,-160),0.75) cerisier((460,-220),0.75)
Une touche de réalisme
Enfin, le dessin se complète avec de légers détails dans le décor, comme les buissons et le chemin au sol, mais également les oiseaux dans le ciel. Si le chemin est un trapèze beige, les buissons sont tracés par une fonction combinant différents morceaux de cercles. Les oiseaux sont, quant à eux deux segments reliés rendu dissemblables par leur taille et leur angle variables. De même, les nuages, assemblages de grands cercles, sont de différentes tailles et formes selon les variables entrées lors de l’appel de la fonction nuage
.
#fonction qui trace le petit chemin def chemin(): color(0,0,0) fillcolor(210, 180, 140) placement((-45,-360)) begin_fill() placement((-20,-89)) placement((20,-89)) placement((45,-360)) placement((-45,-360)) end_fill() #fonction qui trace les nuages def nuage(taille,x,y,deformation): color(148, 0, 0) pensize(taille) placement((x,y)) setheading(deformation) for i in range (4): penup() forward(1) forward(taille/2) pendown() forward(1) right(90) placement((x-(taille//2),y-(taille//3))) forward(1) placement((x+(taille),y-(taille//3))) forward(1) #fonction qui trace les buisson def buisson(coordonne): pensize(1) placement(coordonne) setheading(270) fillcolor(46,102,42) begin_fill() circle(40,-60) circle(10,-80) left(110) circle(30,-120) left(110) circle(20,-90) circle(60,-40) circle(9,-85) circle(210,-34) circle(7,-85) end_fill() #fonction qui trace les oiseaux def oiseau(taille,battement,x,y): color('black') pensize(2) placement((x,y)) setheading(-battement) forward(taille) left(battement*2) forward(taille) chemin() buisson((-210,-285)) buisson((-380,-150)) buisson((225,-290)) buisson((490,-300)) nuage(50,200,270,12) nuage(60,400,100,23) nuage(35,550,300,-12) oiseau(15,20,-270,90) oiseau(10,30,-190,145) oiseau(10,25,-170,110) oiseau(12,35,-230,160)
Les difficultés
Cela peut sembler surprenant, mais notre premier et principal obstacle était la difficulté à trouver un thème, et par extension une difficulté à concevoir le rendu global de l’image. Méthodiquement, nous avons donc couché sur du papier toutes les idées qui nous venaient, puis nous avons fini par opter pour le château.
De plus, nous avons été confronté à l’opposition entre le dessin, qui se voulait réaliste, et ce que nos connaissances en python – et en particulier du module turtle
– nous permettaient de réaliser, autrement dit des figures géométriques essentiellement. Nous avons ainsi trouvé un compromis, en simplifiant le château et le décor, mais avons néanmoins cherché un semblant de réalité avec les détails des cerisiers, leurs pétales tombés aléatoirement au sol, et les nuages dont aucun n’est identique à un autre.