Fermer

février 21, 2025

Manipulation d’image avec des Nestjs et Sharp

Manipulation d’image avec des Nestjs et Sharp


Apprenez l’importance de l’optimisation de l’image et comment intégrer la bibliothèque Sharp aux NESTJ pour effectuer certaines techniques de manipulation d’images.

Dans cet article, nous créerons une application Web de manipulation d’image en utilisant Pointu bibliothèque et Faire. Nous apprendrons l’importance de l’optimisation de l’image et comment intégrer la bibliothèque Sharp avec les NESTJ pour effectuer des techniques de manipulation d’images comme le flou, le format d’image, la rotation, le retournement, etc.

Qu’est-ce que Sharp?

Sharp est facile à utiliser Node.js bibliothèque pour le traitement d’image. Il est largement utilisé en raison de sa vitesse, de sa qualité d’image de sortie et de ses besoins minimaux de code.

Par exemple, le code de redimensionnement d’une image ressemblerait à ceci:

async function resizeImage() {
  try {
    await sharp("house.jpg")
      .resize({
        width: 40,
        height: 107,
      })
      .toFile("resized-house.jpg");
  } catch (error) {
    console.log(error);
  }
}

Qu’est-ce que NESTJS?

NESTJS est un cadre backend Node.js largement aimé pour son architecture modulaire. Cette architecture favorise la séparation des préoccupations, permettant aux développeurs et aux équipes de créer rapidement des applications côté serveur évolutives et maintenables.

NESTJS dispose également de plusieurs modules intégrés pour gérer les tâches communes comme les téléchargements de fichiers et le service de fichiers statiques, que nous utiliserons pour intégrer Sharp dans notre application.

Importance de l’optimisation de l’image

Bien que les images améliorent les sites Web, les sites non optimisés peuvent les ralentir. Des images non optimisées entraînent des temps de chargement plus longs, une mauvaise expérience utilisateur et une augmentation des coûts de stockage du site Web. Cela peut être une différence significative entre un site étonnant et terrible.

L’optimisation de l’image signifie fournir des visuels de haute qualité dans un format et une taille efficaces. L’optimisation des images peut avoir un impact direct sur la rétention des utilisateurs et le classement des moteurs de recherche, donc l’optimisation des images est très importante.

Zones d’optimisation d’image

Certains domaines clés à considérer sont le format de fichier, la qualité et la taille de l’image.

Format de fichier

Lorsque vous choisissez un format de fichier, vous devez d’abord distinguer les formats plus anciens et plus largement pris en charge et les formats plus récents et plus performants.

Formats plus anciens:

  • Jpeg – Une option populaire pour les photos et les images détaillées. Pensez aux images sur un site Web de portefeuille de photographie.
  • PNG – Pensez aux graphiques, aux images avec des contrastes nets ou des arrière-plans transparents.

Formats plus récents:

  • Webp – C’est un bon choix lors de la priorisation des performances. Il réduit considérablement la taille du fichier et est pris en charge par la plupart des navigateurs modernes.
  • Avenues – C’est un autre bon choix pour les performances, offrant souvent une meilleure compression que WebP et production de fichiers plus petits au même niveau de qualité, bien qu’il ne soit pas aussi largement adopté que WebP.

Des formats plus récents comme WebP et AVIF sont de plus en plus préférés, et les images de secours peuvent être utilisées pour la compatibilité avec les navigateurs qui ne les soutiennent pas.

Qualité et taille

  • Optimiser pour le contenu – Qualité d’image de tailleur basée sur le but. Les photos peuvent tolérer plus de compression avec une qualité inférieure, tandis que les images avec du texte, des ridules ou des logos devraient avoir une meilleure qualité pour préserver la netteté.
  • Rédigez-vous toujours avant utilisation – Une astuce pro est de redimensionner les images aux dimensions exactes qu’elles seront utilisées. Cela évite les tailles de fichiers inutilement importantes, l’amélioration des vitesses de rendu et l’efficacité globale.

Configuration du projet

Pour commencer, vous devrez installer la CLI NESTJS si vous ne l’avez pas déjà fait. Exécutez la commande suivante pour l’installer:

npm i -g @nestjs/cli

Ensuite, utilisons la CLI NESTJS pour créer un nouveau projet en exécutant la commande suivante:

nest new sharp-demo

Vous serez invité à choisir un gestionnaire de packages. Pour cet article, nous utiliserons NPM (Node Package Manager).

Options de gestionnaire de packages

Une fois l’installation terminée, vous aurez un nouveau dossier appelé sharp-demo. Ce sera notre répertoire de projet, et vous pouvez y accéder en exécutant cette commande:

cd sharp-demo

Ensuite, exécutons la commande suivante pour échafaudager un module d’images:

nest g resource images

Sélectionnez ensuite l’API REST comme indiqué dans l’image ci-dessous.

Créer un module d'images

Installer des dépendances

Exécutez la commande suivante pour installer les dépendances dont nous aurons besoin pour notre projet:

npm i sharp && npm i --save @nestjs/serve-static && npm i -D @types/multer

La commande ci-dessus installe le sharp package, que nous utiliserons pour la manipulation d’images; les nestjs serve-static package, que nous utiliserons pour servir notre index.html; Et le Multer typings Package, que nous utiliserons dans notre intercepteur de fichiers pour extraire notre image.

Servir la page HTML

Pour le frontend de notre application, nous créerons une page HTML et la servirons avec les NESTJS ServeStaticModule.

Mettez à jour votre app.module.ts dossier avec les éléments suivants:

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { ImagesModule } from "./images/images.module";
import { ServeStaticModule } from "@nestjs/serve-static";
import { join } from "path";

@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, "..", "public"),
    }),
    ImagesModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Dans le code ci-dessus, nous configurons notre application pour servir des fichiers statiques. Nous soulignons également le répertoire public situé à un niveau au-dessus du répertoire actuel en tant que répertoire contenant nos fichiers statiques.

Ensuite, créez un dossier appelé public À la racine de votre projet, et dans l’informatique, créez deux fichiers nommés index.html et script.js.

Le répertoire du projet devrait maintenant ressembler à ceci:

Répertoire de projet

Mettre à jour le index.html dossier avec les éléments suivants:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Image Manipulation with NestJS and Sharp</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        margin: 20px;
      }
      .form-group {
        margin-bottom: 15px;
      }
      #output-container {
        margin-top: 20px;
        display: flex;
        gap: 20px;
      }
      img {
        max-width: 300px;
        max-height: 300px;
        object-fit: contain;
        border: 1px solid #ddd;
        padding: 5px;
      }
    </style>
  </head>
  <body>
    <h1>Image Manipulation with NestJS and Sharp</h1>
    <form id="imageForm">
      <div class="form-group">
        <label for="fileInput">Upload Image:</label>
        <input
          type="file"
          id="fileInput"
          name="file"
          accept="image/*"
          required
        />
      </div>
      <div class="form-group">
        <label for="technique">Choose Manipulation Technique:</label>
        <select id="technique" name="technique" required>
          <option value="blur">Blur</option>
          <option value="rotate">Rotate</option>
          <option value="tint">Tint</option>
          <option value="grayscale">Grayscale</option>
          <option value="flip">Flip</option>
          <option value="flop">Flop</option>
          <option value="crop">Crop</option>
          <option value="createComposite">Create Composite Image</option>
        </select>
      </div>
      <div class="form-group">
        <label for="format">Choose Output Format:</label>
        <select id="format" name="format" required>
          <option value="jpeg">JPEG</option>
          <option value="png">PNG</option>
          <option value="webp">WebP</option>
          <option value="avif">AVIF</option>
        </select>
      </div>
      <button type="submit">Submit</button>
    </form>

    <div id="output-container" style="display: none">
      <div>
        <h3>Original Image</h3>
        <img
          id="originalImage"
          width="300"
          height="300"
          src="#"
          alt="Original Image"
        />
      </div>
      <div>
        <h3>Manipulated Image</h3>
        <img id="manipulatedImage" src="#" alt="Manipulated Image" />
      </div>
    </div>

    <script src="script.js"></script>
  </body>
</html>

Mettre à jour également le script.js dossier avec les éléments suivants:

document.addEventListener("DOMContentLoaded", () => {
  const form = document.getElementById("imageForm");
  const manipulatedImage = document.getElementById("manipulatedImage");
  const outputContainer = document.getElementById("output-container");
  const originalImage = document.getElementById("originalImage");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();

    const formData = new FormData(form);
    const file = formData.get("file");
    if (!file) return;

    try {
      const response = await fetch("http://localhost:3000/images/process", {
        method: "POST",
        body: formData,
      });

      const result = await response.json();

      if (result.imageBase64) {
        manipulatedImage.src = result.imageBase64;

        const reader = new FileReader();
        reader.onloadend = function () {
          originalImage.src = reader.result;
        };
        reader.readAsDataURL(file);

        outputContainer.style.display = "flex";
      } else {
        alert("File upload failed");
      }
    } catch (error) {
      console.error("Error during upload:", error);
    }
  });
});

Dans le code ci-dessus, nous extraissons l’image traitée au format Base64 à partir de la réponse et la définissons comme source du manipulatedImage élément, ce qui lui permet d’être affiché dynamiquement.

Configurer le contrôleur d’images

Ensuite, mettons à jour notre ImagesController Pour gérer les téléchargements de fichiers et renvoyer l’image traitée au format Base64.

Mettre à jour le images.controller.ts dossier avec les éléments suivants:

import {
  Body,
  Controller,
  Post,
  UploadedFile,
  UseInterceptors,
} from "@nestjs/common";
import { ImagesService } from "./images.service";
import { FileInterceptor } from "@nestjs/platform-express";
import * as sharp from "sharp";

@Controller("images")
export class ImagesController {
  constructor(private readonly imagesService: ImagesService) {}

  @Post("process")
  @UseInterceptors(FileInterceptor("file"))
  async uploadAndProcessFile(
    @UploadedFile() file: Express.Multer.File,
    @Body()
    body: {
      technique: string;
      format: keyof sharp.FormatEnum;
    }
  ) {
    const base64Image = await this.imagesService.processImage(
      file,
      body.technique,
      body.format
    );
    return {
      message: "File uploaded and processed successfully",
      imageBase64: base64Image,
    };
  }
}

Dans le code ci-dessus, nous utilisons le FileInterceptor pour extraire le fichier téléchargé, tandis que le technique et format sont extraits du corps de la demande avec le @Body décorateur. Ces paramètres sont ensuite transmis au processImage méthode de ImagesService pour le traitement.

Configurer le service d’images

À la fin de cette section, votre images.service.ts Le fichier doit ressembler à ceci:

Fichier fini images.service.ts

Mettre à jour le images.service.ts dossier avec les éléments suivants:

import * as sharp from "sharp";
import { Injectable } from "@nestjs/common";

@Injectable()
export class ImagesService {
  async processImage(
    file: Express.Multer.File,
    technique: string,
    format: keyof sharp.FormatEnum
  ): Promise<Buffer> {
    try {
      const method = (this as any)[technique];
      if (typeof method !== "function") {
        throw new Error(
          `Method "${technique}" is not defined or not a function`
        );
      }
      return await method.call(this, file, format);
    } catch (error) {
      console.error(`Method "${technique}" is not defined or not a function`);
      throw new Error(
        `Failed to process image with technique "${technique}": ${error.message}`
      );
    }
  }
}

Dans le code ci-dessus, le processImage recherche et appelle un matching Méthode dans le ImagesService classe basée sur le technique passé. Cela donne la flexibilité d’utiliser différentes méthodes de manipulation d’images avec un seul itinéraire basé sur la valeur du technique dans le corps de la demande.

Brouiller une image

Maintenant que nous avons mis en place le processImage Méthode, nous pouvons ajouter toute autre méthode nécessaire pour la manipulation d’image au ImagesService classe, et il peut être utilisé en faisant passer son nom comme valeur de la technique dans le corps de la demande.

Ajoutons le blur() Méthode pour brouiller une image:

async blur(file: Express.Multer.File, format: keyof sharp.FormatEnum) {
    const processedBuffer = await sharp(file.buffer)
        .resize(300, 300)
        .blur(10)
        .toFormat(format)
        .toBuffer()
    return `data:image/${format};base64,${processedBuffer.toString('base64')}`;
}

Le code ci-dessus prend le tampon d’une image (les données d’image brutes) et la transmet à Sharp. Le resize() La méthode redimensionne alors l’image à 300×300 pixels. Ensuite, le blur() La méthode applique un effet flou avec une résistance de 9, bien que la méthode puisse prendre des valeurs de 0,3-1000.

Ensuite, nous convertissons l’image au format spécifié et générons l’image traitée sous forme de tampon. Enfin, le tampon est codé à Base64 et envoyé sous forme de chaîne d’URL de données.

Le résultat lorsqu’il est appelé ci-dessous:

Le résultat du brouillage de l'image

Faire tourner une image

Ensuite, nous ajouterons le rotate() Méthode pour faire pivoter une image:

async rotate(file: Express.Multer.File, format: keyof sharp.FormatEnum) {
  const processedBuffer = await sharp(file.buffer)
      .resize(300, 300)
      .rotate(140, { background: "#ddd" })
      .toFormat(format)
      .toBuffer()
  return `data:image/${format};base64,${processedBuffer.toString('base64')}`;
}

Le rotate() La méthode prend l’angle de rotation et peut également prendre une couleur d’arrière-plan personnalisée pour les angles non 90 °.

Lorsqu’il est appelé, le résultat est:

Le résultat de la redimensionnement et de la rotation de l'image

Il est important de noter que chaque transformation se produit sur l’image telle qu’elle existe à cette étape, donc si nous avions tourné l’image avant de le redimensionner, nous aurions un résultat différent, comme indiqué ci-dessous.

Le résultat de la rotation et du redimensionnement de l'image

Tenter une image

Ajoutons le tint() Méthode pour teinter une image:

async tint(file: Express.Multer.File, format: keyof sharp.FormatEnum) {
    const processedBuffer = await sharp(file.buffer)
        .resize(300, 300)
        .tint({ r: 150, g: 27, b: 200 })
        .toFormat(format)
        .toBuffer()
    return `data:image/${format};base64,${processedBuffer.toString('base64')}`;
}

Le tint() La méthode modifie la couleur d’une image en appliquant une teinte spécifiée basée sur les valeurs rouges, vertes et bleues (RVB). La plage pour chaque valeur est de 0 à 255.

Lorsqu’il est appelé, le résultat est:

Le résultat de la teinture de l'image

Convertir l’image en niveaux de gris

Ensuite, ajoutons le grayscale() Méthode pour convertir une image en niveaux de gris:

async grayscale(file: Express.Multer.File, format: keyof sharp.FormatEnum) {
  const processedBuffer = await sharp(file.buffer)
      .resize(300, 300)
      .grayscale() 
      .toFormat(format)
      .toBuffer()
  return `data:image/${format};base64,${processedBuffer.toString('base64')}`;
}

Le grayscale() et greyscale() Les méthodes suppriment toutes les informations de couleur et représentent l’image à l’aide de nuances de gris.

Lorsqu’il est appelé, le résultat est:

Le résultat de la conversion de l'image en niveaux de gris

Retourner une image

Ajoutons le flip() Méthode pour retourner une image:

async flip(file: Express.Multer.File, format: keyof sharp.FormatEnum) {
  const processedBuffer = await sharp(file.buffer)
      .resize(300, 300)
      .flip()
      .toFormat(format)
      .toBuffer()
  return `data:image/${format};base64,${processedBuffer.toString('base64')}`;
}

Le flip() La méthode inverse verticalement une image. Lorsqu’il est appelé, le résultat est:

Le résultat d'un retournement de l'image

Floping une image

Nous pouvons utiliser le flop() Méthode pour flop une image:

async flop(file: Express.Multer.File, format: keyof sharp.FormatEnum) {
  const processedBuffer = await sharp(file.buffer)
      .resize(300, 300)
      .flop()
      .toFormat(format)
      .toBuffer()
  return `data:image/${format};base64,${processedBuffer.toString('base64')}`;
}

Cette méthode inverse horizontalement une image. Lorsqu’il est appelé, le résultat est:

Le résultat de flop de l'image

Cramper une image

Ensuite, ajoutons le crop() Méthode pour recadrer une image:

async crop(file: Express.Multer.File, format: keyof sharp.FormatEnum) {
  const processedBuffer = await sharp(file.buffer)
      .extract({ left: 140, width: 1800, height: 1800, top: 140 })
      .resize(300, 300)
      .toFormat(format)
      .toBuffer()
  return `data:image/${format};base64,${processedBuffer.toString('base64')}`;
}

Le extract() La méthode nous permet de décrire une boîte à l’intérieur de l’image pour conserver le reste.

  • gauche – la position horizontale où la boîte doit commencer
  • largeur – la largeur de la boîte
  • haut – la position verticale à laquelle la boîte doit commencer
  • hauteur – la hauteur de la boîte

Lorsqu’il est appelé, le résultat est:

Le résultat de la recadrage de l'image

Comme mentionné ci-dessus, l’ordre lors du enchaînement des méthodes nets est importante. C’est pourquoi nous avons appelé extract() avant resize()– ailleurs, nous obtiendrions un résultat différent.

Création d’une image composite

Enfin, ajoutons le createComposite méthode:

async createComposite(file: Express.Multer.File, format: keyof sharp.FormatEnum) {
  const greyHouse = await sharp(file.buffer)
      .resize(150, 150)
      .grayscale()
      .toBuffer()
  const processedBuffer = await sharp(file.buffer)
      .composite([
          {
              input: greyHouse,
              top: 50,
              left: 50,
          },
      ])
      .resize(300, 300)
      .toFormat(format)
      .toBuffer()
  return `data:image/${format};base64,${processedBuffer.toString('base64')}`;
  }

Le composite() La méthode prend un tableau d’objets de superposition contenant input, top et left propriétés. Il positionne chaque superposition en utilisant le top et left propriétés.

Dans le code ci-dessus, nous créons une petite version en niveaux de gris de l’image à utiliser comme superposition. Lorsqu’il est appelé, le résultat est:

Le résultat de la création d'une image composite

Serveur de départ

Maintenant que nous avons terminé la configuration, nous pouvons démarrer notre serveur. Enregistrez tous les fichiers et démarrez le serveur NESTJS en exécutant la commande suivante:

  npm run start

Vous devriez voir ceci:

Démarrage du serveur réussi

Pour accéder à notre index.html Page, accédez à http: // localhost: 3000 / index.html.

Conclusion

Les images jouent un rôle important sur le Web. Par conséquent, les optimiser et utiliser d’autres techniques de manipulation d’images avec des bibliothèques comme des cadres nets et backend comme NESTJS est important.

Après avoir créé l’application Web de manipulation d’image, vous devriez être capable de brouiller, de tourner, de teinte, de vous convertir en niveaux de gris, flip, flop, recadrer, créer une image composite et convertir des images en un format différent.




Source link