Depuis quelques mois, ChatGPT est devenu un assistant plutôt indispensable pour mes "side-projects". Il relit et améliore mon code, guide mes réflexions, et parfois met même la main à la pâte pour coder avec moi. Je lui trouvais en revanche assez peu d'utilité dans mon travail quotidien de designer. Jusqu'à hier...

La première graine

Il est toujours délicat d'illustrer l'Écologie ou le Climat sans tomber dans les clichés faciles. Nos Gestes Climat mise largement sur l'utilisation des emojis, pour illustrer chacune des nombreuses facettes de notre empreinte carbone.

Pour une refonte de page d'accueil, j'avais cette idée vague d'utiliser une composition de ces nombreux émojis, qui pourraient accompagner la promesse du site. Expérimenter avec autant de pictogrammes devient vite très laborieux : sélectionner les bonnes icônes, ajuster leur disposition, taille, orientation, opacité... Chaque nouveau test nécessite de retravailler chaque image individuellement.

Je me décourageais d'avance, et puis la solution m'a sauté aux yeux. Une composition d'icônes n'est qu'un assemblage d'images SVG ; il ne doit pas être si complexe de coder la composition de l'image. Et ChatGPT est généralement particulièrement performant sur ce type de tâches simples et circonscrites.

Un premier pas timide

Pour l'expérience, un simple script Javascript (NodeJs) me convient très bien. Mais ChatGPT peut travailler tout aussi bien en Python, en PHP, en Ruby ou tout langage capable de manipuler des images. Je tente un premier "prompt" et en quelques secondes, ChatGPT me fait une première proposition :

Hey! Can you create a Node script that combine random svg file (from a /svg/ folder) into a new svg file? On the new svg, can you dispose all svg pieces into a circle that's 200px from the center?

Certainly! Below is an example Node.js script that combines random SVG files from a specified folder into a new SVG file. [...]

const fs = require("fs");const path = require("path");// Path to the folder containing the SVG filesconst svgFolderPath = path.join(__dirname, "svg");// Path to the new SVG fileconst newSvgFilePath = path.join(__dirname, "combined.svg");// Reading the SVG files from the folderfs.readdir(svgFolderPath, (err, files) => {  if (err) {    console.error("Unable to read the SVG folder:", err);    return;  }  // Filter SVG files  const svgFiles = files.filter((file) => path.extname(file) === ".svg");  // Randomly select SVG files  const selectedSvgFiles = svgFiles.sort(() => 0.5 - Math.random()).slice(0, 2);  // Combine selected SVG files into a single SVG file  let combinedSvgContent = '<svg xmlns="http://www.w3.org/2000/svg">\n';  selectedSvgFiles.forEach((file) => {    const svgContent = fs.readFileSync(path.join(svgFolderPath, file), "utf-8");    // Extract inner SVG content (between <svg> and </svg>)    const innerSvgContent = svgContent.match(/<svg[^>]*>([\s\S]*?)<\/svg>/i)[1];    combinedSvgContent += innerSvgContent + "\n";  });  combinedSvgContent += "</svg>";  // Write the combined SVG content to a new file  fs.writeFileSync(newSvgFilePath, combinedSvgContent, "utf-8");  console.log("SVG files combined successfully!");});

A ce stade, pas vraiment besoin de chercher à tout comprendre. J'essaie plutôt de valider le concept. Dans le dossier de mon projet, je crée un dossier svg avec quelques émojis exportés depuis Figma, et un fichier index.js dans lequel je copie-colle (sauvagement) la proposition de ChatGPT. Je lance ce premier script depuis le terminal avec node index.js et...

Première tentative de ChatGPT

Première tentative de ChatGPT

...ce n'est pas fou 😄 Cela-dit, c'est techniquement juste: 5 images disposées le long d'un cercle. Ma consigne n'était pas si claire. Je poursuis la discussion en lui demandant des ajustements.

Can you do the same but with 25 pieces around the circle ? If there aren't 25 svg files, it's ok to use the same one twice

Sure! To arrange SVG pieces in a circle, we'll need to calculate [...]

Une nouvelle proposition, et là je suis tombe de ma chaise, en 15 minutes, j'ai déjà un premier résultat concluant.

Deuxième tentative de ChatGPT

Une collaboration fructueuse

Un monde vient de s'ouvrir à moi et j'ai déjà beaucoup trop d'idées. Dans la même conversation, j'affine la consigne avec de nouveaux éléments, petit à petit, en vérifiant le rendu à chaque à étape avec un simple copier-coller.

Can you decrease the size of each piece by 20% (a make this percentage easily tweakable?)

Perfect! Now I'd like to add a second circle, this time 300px from the circle, width 30 pieces, doing the same thing.

Notez que j'ai pris l'habitude de poser mes questions en (mauvais) anglais, mais que ChatGPT comprendrait tout aussi bien des consignes en Français.

// [...]placeSvgsInCircle(25, 200);placeSvgsInCircle(30, 300);//[...]

Ici, ChatGPT devine qu'on va ajuster les résultats, et factorise de lui même son code, ce qui me laisse tout le loisir d'expérimenter de mon côté. Lorsque je suis content de mes résultats je lui propose de mettre à jour son code avec mes propres tests.

Please from now on, use this code instead

placeSvgsInCircle(20, 200); // 1st circle with 20 pieces and radius 100px placeSvgsInCircle(25, 250); // 2nd circle with 30 pieces and radius 200px placeSvgsInCircle(30, 300); // 3rd circle with 40 pieces and radius 300px placeSvgsInCircle(35, 350); // 4th circle with 50 pieces and radius 400px placeSvgsInCircle(40, 400); // 5th circle with 60 pieces and radius 500px

Now could you rotate each piece to point to the center?

Of course! [...]

Chaque idée prend vie instantanément, c'est assez magique. À chaque étape, ChatGPT reformule la consigne et m'explique ce qu'il fait, avec un petit côté "premier de la classe" pas déplaisant. Je tente beaucoup de choses avec des résultats pas toujours probants.

Now can you rotate each svg piece randomly from -10% to 10%

Le script final :

const fs = require("fs");const path = require("path");const svgFolderPath = path.join(__dirname, "svg-cute");const newSvgFilePath = path.join(__dirname, "cute.svg");if (fs.existsSync(newSvgFilePath)) {  fs.unlinkSync(newSvgFilePath);  console.log("Existing svg deleted.");}fs.readdir(svgFolderPath, (err, files) => {  if (err) {    console.error("Unable to read the SVG folder:", err);    return;  }  const svgFiles = files.filter((file) => path.extname(file) === ".svg");  if (svgFiles.length < 1) {    console.error("No SVG files in the folder.");    return;  }  let combinedSvgContent =    '<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200">\n';  const centerX = 600;  const centerY = 600;  // Define the scaling factor (e.g., 0.8 for 80%, meaning a 20% reduction in size)  const scalingFactor = 0.6;  const placeSvgsInCircle = (numPieces, radius, opacity = 1) => {    for (let i = 0; i < numPieces; i++) {      const randomIndex = Math.floor(Math.random() * svgFiles.length);      const svgContent = fs.readFileSync(        path.join(svgFolderPath, svgFiles[randomIndex]),        "utf-8"      );      const innerSvgContent = svgContent.match(        /<svg[^>]*>([\s\S]*?)<\/svg>/i      )[1];      const angle = (i / numPieces) * 2 * Math.PI;      const x = centerX + radius * Math.cos(angle);      const y = centerY + radius * Math.sin(angle);      let rotation = angle * (180 / Math.PI); // Convert angle to degrees      // Offset for the piece's geometric center      const offsetX = 32;      const offsetY = 32;      // If the piece is on the left side, add 180 degrees to the rotation      if (x < centerX) {        rotation += 180;      }      // Add a random rotation between -10% and 10% of the initial rotation      const randomRotation = rotation * (Math.random() * 0.2 - 0.1);      rotation += randomRotation;      combinedSvgContent += `<g transform="translate(${x},${y}) scale(${scalingFactor}) translate(${offsetX},${offsetY}) rotate(${rotation}) translate(${-offsetX},${-offsetY})"  style="opacity: ${opacity};">\n`;      combinedSvgContent += innerSvgContent + "\n";      combinedSvgContent += "</g>\n";    }  };  placeSvgsInCircle(30, 300); // 3rd circle with 40 pieces and radius 300px  placeSvgsInCircle(35, 350); // 4th circle with 50 pieces and radius 400px  placeSvgsInCircle(40, 400); // 5th circle with 60 pieces and radius 500px  placeSvgsInCircle(45, 450); // 1st circle with 20 pieces and radius 100px  placeSvgsInCircle(50, 500); // 2nd circle with 30 pieces and radius 200px  combinedSvgContent += "</svg>";  fs.writeFileSync(newSvgFilePath, combinedSvgContent, "utf-8");  console.log("SVG files combined successfully!");});

Aussi simple que ça... L'expérience est vraiment plaisante. Je peux tester chaque idée, apporter des corrections, revenir en arrière, essayer autre chose. On mêle créativité et technique en ping-pong jusqu'à arriver à un résultat satisfaisant.

Conclusion

Pour tester une nouvelle librairie d'icônes, il me suffit de les déposer dans un dossier. Comme chaque génération possède un part d'aléatoire, il m'arrive de regénérer la même illustration plusieurs fois et de récupérer les éléments qui me semblent les plus cohérents. Je conserve chaque tentative afin de pouvoir les comparer.

Les résultats en image

La cerise sur le gâteau, c'est que l'illustration finale est en SVG, parfaitement éditable dans Figma si je souhaite remplacer un émoji ou corriger une orientation.

ChatGPT deviendra-t-il un compagnon récurrent sur mes projets de design ? Probablement pas, le cas d'usage reste très spécifique. Cependant, c'est clairement un outil malin auquel je penserai plus spontanément à l'avenir.