Canvas
Configuration de @napi-rs/canvas
@napi-rs/canvas est un outil de manipulation d'images qui vous permet de modifier des images avec du code. Nous allons explorer comment utiliser ce module dans une commande slash pour créer une commande de profil.
Ce guide a été testé en dernier avec @napi-rs/canvas^0.1.25, assurez-vous d'avoir cette version ou une version similaire après
l'installation.
Assurez-vous d'être familier avec des choses comme async/await et destruction d'objets avant de continuer, car nous allons utiliser ces fonctionnalités dans les sections à venir.
Installation du package
Exécutez la commande suivante dans votre terminal :
npm install @napi-rs/canvasCommencer
Voici le code de base que vous utiliserez pour commencer :
const { AttachmentBuilder, Client, Events, GatewayIntentBits } = require('discord.js');
const Canvas = require('@napi-rs/canvas');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, (readyClient) => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
client.on(Events.InteractionCreate, (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'profile') {
// ...
}
});
client.login('your-token-goes-here');Si vous avez suivi la section gestionnaire d'événements de ce guide, envisagez de mettre cela dans votre gestionnaire d'interactions à la place !
N'oubliez pas d'enregistrer les commandes slash avant de continuer cette section du guide. Vous pouvez voir comment faire cela ici.
Chargement d'images de base
L'objectif final sera d'afficher l'avatar et le surnom de l'utilisateur.
Après avoir importé le module @napi-rs/canvas et l'avoir initialisé, vous devez charger les images. Avec @napi-rs/canvas, vous devez d'abord spécifier d'où vient l'image, naturellement, puis spécifier comment elle est chargée dans le Canvas réel en utilisant context, que vous utiliserez pour interagir avec Canvas.
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'profile') {
// Créez un canvas de 700x250 pixels et obtenez son contexte
// Le contexte sera utilisé pour modifier le canvas
const canvas = Canvas.createCanvas(700, 250);
const context = canvas.getContext('2d');
// ...
}
});Maintenant, vous devez charger l'image que vous souhaitez utiliser dans Canvas.
Nous allons utiliser cette image comme arrière-plan dans l'image de bienvenue, mais vous pouvez utiliser ce que vous voulez. Assurez-vous de télécharger le fichier, de le nommer wallpaper.jpg et de le sauvegarder dans le même répertoire que votre fichier bot principal.
client.on(Events.InteractionCreate, async (interaction) => {
const canvas = Canvas.createCanvas(700, 250);
const context = canvas.getContext('2d');
const background = await Canvas.loadImage('./wallpaper.jpg');
// Ceci utilise les dimensions du canvas pour étirer l'image sur tout le canvas
context.drawImage(background, 0, 0, canvas.width, canvas.height);
// Utilisez la structure de classe Attachment utile pour traiter le fichier pour vous
const attachment = new AttachmentBuilder(await canvas.encode('png'), { name: 'profile-image.png' });
interaction.reply({ files: [attachment] });
});Si vous obtenez une erreur comme Error: ENOENT: no such file or directory, le chemin du fichier fourni était incorrect.
Manipulation d'images
Ensuite, plaçons une bordure autour de l'image à titre de démonstration.
client.on(Events.InteractionCreate, async (interaction) => {
// ...
context.drawImage(background, 0, 0, canvas.width, canvas.height);
// Définir la couleur du trait
context.strokeStyle = '#0099ff';
// Dessinez un rectangle avec les dimensions de tout le canvas
context.strokeRect(0, 0, canvas.width, canvas.height);
// ...
});Un peu simple, non ? N'ayez pas peur, vous avez encore un peu plus à faire jusqu'à ce que vous terminiez. Puisque l'objectif de cette page de guide est axé davantage sur le code réel que sur la conception, plaçons un avatar carré de base pour l'instant sur le côté gauche de l'image. Par souci de complétude, vous le transformerez également en cercle par la suite.
const { request } = require('undici');
client.on(Events.InteractionCreate, async (interaction) => {
// ...
context.strokeRect(0, 0, canvas.width, canvas.height);
// Utilisation de undici pour effectuer des requêtes HTTP pour de meilleures performances
const { body } = await request(interaction.user.displayAvatarURL({ extension: 'jpg' }));
const avatar = await Canvas.loadImage(await body.arrayBuffer());
// Si vous ne vous souciez pas de la performance des requêtes HTTP, vous pouvez instead charger l'avatar en utilisant
// const avatar = await Canvas.loadImage(interaction.user.displayAvatarURL({ extension: 'jpg' }));
// Dessinez une forme sur le canvas principal
context.drawImage(avatar, 25, 0, 200, canvas.height);
context.drawImage(background, 0, 0, canvas.width, canvas.height);
// ...
});Ça fonctionne bien, mais l'image d'avatar elle-même semble un peu étirée. Réparez cela.
client.on(Events.InteractionCreate, async (interaction) => {
// ...
const { body } = await request(interaction.user.displayAvatarURL({ extension: 'jpg' }));
const avatar = await Canvas.loadImage(await body.arrayBuffer());
context.drawImage(avatar, 25, 0, 200, canvas.height);
// Déplacez l'image vers le bas verticalement et limitez sa hauteur à 200, pour qu'elle soit carrée
context.drawImage(avatar, 25, 25, 200, 200);
// ...
});Le but de cette petite section est de démontrer que travailler avec Canvas est essentiellement un flux de travail par essai et erreur où vous modifiez les propriétés jusqu'à ce qu'elles fonctionnent correctement.
Puisque nous avons couvert comment charger des images externes et corriger les dimensions, transformons l'avatar en cercle pour améliorer le style général de l'image.
client.on(Events.InteractionCreate, async (interaction) => {
// ...
context.strokeRect(0, 0, canvas.width, canvas.height);
// Soulevez le stylo
context.beginPath();
// Commencez l'arc pour former un cercle
context.arc(125, 125, 100, 0, Math.PI * 2, true);
// Posez le stylo
context.closePath();
// Coupez la région que vous avez dessinée
context.clip();
// ...
});Ajout de texte
Maintenant, parlons rapidement d'ajouter du texte à votre image. Cela aidera à rendre l'objectif de cette image évident car actuellement, c'est juste un avatar flottant sur un arrière-plan étoilé qui apparait de nulle part.
client.on(Events.InteractionCreate, async (interaction) => {
// ...
context.strokeRect(0, 0, canvas.width, canvas.height);
// Sélectionnez la taille et le type de police parmi l'une des polices disponibles en natif
context.font = '60px sans-serif';
// Sélectionnez le style qui sera utilisé pour remplir le texte
context.fillStyle = '#ffffff';
// Remplissez réellement le texte avec une couleur unie
context.fillText(interaction.member.displayName, canvas.width / 2.5, canvas.height / 1.8);
// ...
});Si vous obtenez une erreur comme Fontconfig error: Cannot load default config file, cela signifie que vous n'avez pas de polices
installées sur votre système. Sur Linux, vous pouvez exécuter la commande suivante pour résoudre ce problème : sudo apt-get install fontconfig.
Ceci peut également avoir besoin d'être installé si vous voyez des boîtes où le texte devrait être. Quant à Windows, vous devrez trouver
un moyen d'installer des polices.
Vous avez peut-être remarqué ou considéré que si le nom d'utilisateur d'un membre est trop long, la sortie ne sera pas très agréable. C'est parce que le texte dépasse du canvas, et vous n'avez aucune mesure en place pour cela. Prenons soin de ce problème !
// Passez l'objet Canvas entier car vous aurez besoin d'accéder à sa largeur et son contexte
const applyText = (canvas, text) => {
const context = canvas.getContext('2d');
// Déclarez une taille de base de la police
let fontSize = 70;
do {
// Attribuez la police au contexte et décrémentez-la pour qu'elle puisse être mesurée à nouveau
context.font = `${(fontSize -= 10)}px sans-serif`;
// Comparez la largeur en pixels du texte au canvas moins la taille approximative de l'avatar
} while (context.measureText(text).width > canvas.width - 300);
// Retournez le résultat à utiliser dans le canvas réel
return context.font;
};
client.on(Events.InteractionCreate, async (interaction) => {
// ...
context.strokeRect(0, 0, canvas.width, canvas.height);
context.font = '60px sans-serif';
context.font = applyText(canvas, interaction.member.displayName);
context.fillStyle = '#ffffff';
context.fillText(interaction.member.displayName, canvas.width / 2.5, canvas.height / 1.8);
// ...
});Avant l'ajustement :
Après l'ajustement :
Déplaçons le texte de bienvenue à l'intérieur de l'image elle-même au lieu de l'ajouter à l'extérieur comme touche finale agréable.
client.on(Events.InteractionCreate, async (interaction) => {
// ...
context.strokeRect(0, 0, canvas.width, canvas.height);
// Texte légèrement plus petit placé au-dessus du nom d'affichage du membre
context.font = '28px sans-serif';
context.fillStyle = '#ffffff';
context.fillText('Profil', canvas.width / 2.5, canvas.height / 3.5);
// Ajoutez un point d'exclamation ici et ci-dessous
context.font = applyText(canvas, `${interaction.member.displayName}!`);
context.font = applyText(canvas, interaction.member.displayName);
context.fillStyle = '#ffffff';
context.fillText(`${interaction.member.displayName}!`, canvas.width / 2.5, canvas.height / 1.8);
context.fillText(interaction.member.displayName, canvas.width / 2.5, canvas.height / 1.8);
// ...
});Et voilà ! Nous avons couvert les bases de la manipulation d'images, de la génération de texte et du chargement à partir d'une source distante.