Une illustration avec ChatGPT
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 :
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...
...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.
Une nouvelle proposition, et là je suis tombe de ma chaise, en 15 minutes, j'ai déjà un premier résultat concluant.
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.
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.
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.
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.
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.