Aller au contenu

Images

processImage et createThumbnail redressent, redimensionnent et réencodent une image avec sharp, chargé à la demande, avec des réglages bornés.

Une photo prise au téléphone arrive couchée sur le côté, avec sa position GPS dans ses métadonnées, et un fichier de quelques kilo-octets peut annoncer assez de pixels pour remplir la mémoire du serveur. processImage et createThumbnail traitent ces trois cas au même endroit pour tous les modules : la médiathèque y optimise ce qu'elle reçoit, le drive y dessine ses miniatures.

Import

import {
  processImage, createThumbnail, isImageProcessingAvailable,
  DEFAULT_MAX_PIXELS, DEFAULT_CONCURRENCY,
} from '@fraza/core/storage/image';

Module serveur. Il repose sur sharp, une dépendance de pair facultative du core, à installer dans le projet :

npm install sharp

sharp n'est importé qu'au premier traitement. Un site qui ne traite aucune image s'en passe, et l'absence de sharp ne fait échouer ni le build ni le démarrage : seul l'appel lève, avec le code SHARP_MISSING.

API

SignatureRetourRôle
processImage(buffer, options?)Promise<Résultat>Redresse, redimensionne et réencode une image.
createThumbnail(buffer, options?)Promise<Résultat>Miniature redressée, sans métadonnées, WebP par défaut.
isImageProcessingAvailable()Promise<boolean>true quand sharp est installé.

Les deux fonctions retournent le même objet :

ChampContenu
bufferL'image produite.
formatjpeg, png, webp, avif ou tiff.
contentTypeLe type MIME correspondant, image/webp par exemple.
width, heightDimensions de l'image produite, après redressement.
sizeTaille de buffer, en octets.

processImage(buffer, options?)

OptionDéfautRôle
maxDimensionaucuneCôté le plus long, en pixels. L'image n'est jamais agrandie.
formatcelui de l'imagejpeg (ou jpg), png, webp, avif, tiff.
quality80De 1 à 100. PNG l'ignore et compresse sans perte au niveau 9.
stripMetadatatruefalse garde EXIF, ICC, XMP et IPTC, avec l'orientation remise à 1 puisqu'elle a été appliquée.
limitInputPixelsFRAZA_IMAGE_MAX_PIXELS, 40000000Pixels que l'image source peut avoir.
import { processImage } from '@fraza/core/storage/image';

const result = await processImage(buffer, { maxDimension: 2560, quality: 82 });
await uploadFile({ key, body: result.buffer, contentType: result.contentType });

L'orientation EXIF est appliquée avant tout le reste : une photo en portrait reste debout une fois ses métadonnées retirées. Le résultat est toujours retourné, même plus lourd que l'original. Garder l'original quand il est plus léger garderait aussi sa position GPS, le numéro de série de l'appareil et son étiquette d'orientation, ce que le traitement devait retirer. Un appelant qui veut comparer les tailles dispose de size.

Une image GIF, HEIC ou SVG ne se réécrit pas dans son propre format : sans format, l'appel lève avec le code IMAGE_FORMAT_UNSUPPORTED. Seule la première image d'un fichier animé est gardée : laisser les GIF de côté pour préserver leur animation.

createThumbnail(buffer, options?)

OptionDéfautRôle
size512Côté le plus long, en pixels. L'image n'est jamais agrandie.
formatwebpFormat de la miniature.
quality72De 1 à 100.
limitInputPixelsFRAZA_IMAGE_MAX_PIXELS, 40000000Pixels que l'image source peut avoir.
import { createThumbnail, isImageProcessingAvailable } from '@fraza/core/storage/image';

if (await isImageProcessingAvailable()) {
  const thumbnail = await createThumbnail(buffer, { size: 512 });
  await uploadFile({ key: `${key}.thumb.webp`, body: thumbnail.buffer, contentType: thumbnail.contentType });
}

Une miniature est toujours sans métadonnées. Une image légèrement abîmée en reçoit quand même une : c'est un aperçu, et un aperçu partiel vaut mieux qu'aucun. processImage, lui, refuse une image tronquée.

Variables d'environnement

VariableDéfautRôle
FRAZA_IMAGE_MAX_PIXELS40000000Pixels qu'une image source peut avoir.
FRAZA_IMAGE_CONCURRENCY2Images traitées en même temps, par processus.

Notes

La limite de pixels tombe avant le décodage. Une petite image PNG peut annoncer 50 000 × 50 000 pixels et faire réserver des gigaoctets au décodeur. 40 mégapixels laissent passer une photo de téléphone de 12 ou 24 Mpx, pas une photo de 48 Mpx en pleine résolution : relever FRAZA_IMAGE_MAX_PIXELS pour un site qui en reçoit, ou passer limitInputPixels à l'appel.

Les traitements attendent leur tour. libvips répartit déjà une image sur tous les cœurs : en traiter davantage à la fois ne finit pas plus tôt, cela multiplie la mémoire occupée. Au-delà de FRAZA_IMAGE_CONCURRENCY traitements en cours, les suivants attendent. Le cache d'opérations de sharp est coupé, parce qu'une image téléversée n'est jamais traitée deux fois.

Les erreurs remontent telles quelles. Une image illisible, trop grande ou d'un format inconnu fait lever l'appel. Un traitement facultatif, comme une optimisation à l'envoi, attrape l'erreur et garde le fichier reçu.

Sur cette page