Blog - Sébastien Mouchet

Optimiser des images pour le Web

Publié le 12 janvier 2024, par Sébastien

———

Par « optimiser », je veux dire, s’assurer que vos images ont un poids « raisonnable ».

Avoir des images légères a des avantages pour les utilisateurs, les propriétaires de sites Web, et même pour d’autres personnes :

Les conseils de cet article s’appliquent principalement si vous uploadez des images sur votre propre site Web.

Si vous les uploadez sur des réseaux sociaux, elles seront ré-encodées, et vous devriez donc utiliser la qualité la plus élevée dont vous disposez. Certains services d’hébergement d’images ré-encodent également les images.

Images vectorielles / images matricielles

Il y a 2 sortes d’images : les images vectorielles, et les images matricielles (en anglais, on les appelle « bitmaps » ou images « raster »).

Les images matricielles sont les plus courantes. Pour faire simple, ce sont des grilles (matrices rectangulaires) de pixels. Par exemple, toutes les photos sont des images matricielles.
Les formats suivants servent tous à stocker des images matricielles : JPEG, PNG, GIF, WebP, AVIF, TIFF, BMP, TGA, …

De leur côté, les images vectorielles sont construites en donnant des instructions à l’ordinateur lui indiquant comment les dessiner. Par exemple, « dessine un cercle rouge ici, un rectangle bleu là, une courbe de Bézier, un dégradé, … ».
Les formats les plus répandus pour ce type d’images sont notamment AI (Adobe Illustrator) et SVG (Scalable Vector Graphics).
Si vous avez l’intention de publier une image vectorielle sur le Web, vous devrez utiliser le format SVG.

Le grand avantage des images vectorielles est qu’elles peuvent être agrandies sans se retrouver floues ni pixelisées.

Les images vectorielles peuvent être converties facilement en images matricielles, mais pas l’inverse.

Sachez que le SVG, étant un format basé sur du texte (XML), se compresse relativement bien en gzip (ou avec des algorithmes plus avancés).
Vous devriez donc activer la compression au niveau du serveur Web pour les fichiers SVG.

Ensuite, pour déterminer si ça vaut le coup de convertir votre SVG en image matricielle, il faut comparer sa taille compressée à la version matricielle.

Il est souvent préférable de les laisser au format SVG, surtout pour les grandes images.

Définition d’image

La définition d’affichage n’a pas d’impact sur la taille de fichier pour les images vectorielles, qui peuvent être mises à l’échelle librement.

En revanche, le nombre de pixels est un paramètre crucial pour la taille des images matricielles.

ℹ️ On parle souvent abusivement de « résolution », mais le terme exact pour désigner le nombre de pixels en français est « définition ». « Résolution » fait référence à une densité.

Par conséquent, vous devriez déterminer à l’avance la taille (en pixels) dont vous avez besoin, avant d’exporter votre image. Par exemple, si vous savez qu’elle sera affichée en 640 x 480, n’uploadez pas une version en 4000 x 3000…

Parfois, la mise en page réactive (« responsive web design ») empêche de connaître la définition à l’avance. Dans ce cas, vous pouvez en réalité définir plusieurs tailles dans l’attribut « srcset » de l’élément HTML <img>.

<img src="image-640x480.jpg"
     srcset="image-320x240.jpg 320w, image-640x480.jpg 640w"
     sizes="(max-width: 320px) 320px, 640px"
     alt="..."/>

L’attribut « srcset » permet également de définir des images différentes pour les écrans avec une densité de pixels plus élevée (écrans Retina, etc).

<img src="image-320x240.jpg"
     srcset="image-640x480.jpg 2x"
     alt="..."/>

Pour en savoir plus sur les images adaptatives :

Compression avec ou sans pertes

Les images matricielles peuvent être compressées soit dans des formats sans perte (« lossless »), soit avec pertes (« lossy »).

Comme son nom l’indique, un encodage sans perte conserve toutes les informations, ce qui garantit que l’image exportée sera identique à ce que vous voyez dans votre éditeur d’image.

L’encodage avec pertes élimine une partie de l’information (de préférence pas trop visible), permettant un gain d’espace plus poussé. C’est un compromis entre la taille de fichier et la qualité.

Gardez à l’esprit que, même si vous mettez le curseur de qualité à 100 %, les formats avec pertes (comme le JPEG) ne sont jamais mathématiquement sans perte.

D’ailleurs, ces curseurs de qualité sont arbitraires, et complètement dépendants de l’implémentation. Ne vous attendez pas au même niveau de qualité entre un JPEG à 80 % créé par Photoshop et un autre créé par GIMP, ou entre un JPEG à 80 % et un WebP à 80 %.

Il faut éviter de ré-encoder des images dans un format avec pertes plusieurs fois de suite, car cela conduit à une perte de qualité (cumulative) à chaque enregistrement.

Pour les photos, il vaut mieux toujours utiliser un format avec pertes, sans quoi elles prennent trop de place.

Par contre, les infographies et les logos peuvent souvent être plus légers en PNG (qui est sans perte) qu’en JPEG. De manière générale, les images avec de grandes zones de couleurs similaires se compressent plutôt bien avec des formats sans perte.

Avec pertes : JPEG

Le JPEG est le format avec pertes le plus courant. Il existe depuis 1992, et est donc très répandu et fonctionne dans tous les navigateurs.

Si votre éditeur d’image propose des options, lors de l’export en JPEG :

Astuce : Jpegcrop – une application pour Windows – vous permet de changer ces options après coup, sans perdre en qualité, contrairement à ce à quoi on pourrait s’attendre en ré-encodant un JPEG.
Comme le suggère son nom, elle propose d’autres fonctionnalités, avec notamment la possibilité de rogner (« crop »), faire des rotations, et plus encore, sans perdre en qualité.

Pour les photos, on utilise généralement un sous-échantillonnage de la chrominance (de type 4:2:0), ce qui permet de gagner de la place, en utilisant une résolution plus faible pour les informations de chrominance (couleurs) que de luminance (luminosité).

Formats avec pertes plus modernes : WebP et AVIF

Par le passé, les images avec pertes étaient uploadées sur le Web en JPEG exclusivement, mais de nouveaux formats avec pertes ont vu le jour, permettant d’avoir des fichiers plus petits pour un même niveau de qualité.

Le WebP a été lancé en 2010, et a été conçu pour surpasser le JPEG.
En ce début 2024, il est désormais très largement supporté par les navigateurs :

AVIF est encore plus récent, et offre généralement une meilleure compression que le WebP.
Il est aussi de mieux en mieux supporté (mais pas encore autant que le WebP) :

Par conséquent, renvoyer du WebP ou du AVIF – plutôt que du JPEG – aux navigateurs qui les prennent en charge peut faire économiser de la bande passante.

Je vous recommande fortement de mettre en place un « fallback » (solution de repli) vers le JPEG, au cas où le navigateur ne prenne en charge que ce format, ou au cas où vous n’auriez pas uploadé les versions WebP/AVIF de vos images.

Par exemple, voici la configuration Nginx que j’utilise sur le blog, et qui détecte le support du navigateur via l’en-tête HTTP « Accept » :

# Vérifie si le navigateur gère le WebP
map $http_accept $webp_suffix {
    default   "";
    "~*webp"  ".webp";
}

# Vérifie si le navigateur gère l’AVIF
map $http_accept $avif_suffix {
    default   "";
    "~*avif"  ".avif";
    # On se rabat sur le WebP si le navigateur supporte le WebP mais pas l’AVIF
    "~*webp"  ".webp";
}

server {
    # ...

    location ~* \.(?:jpg|jpeg)$ {
        add_header Vary Accept;
        # Lors d’une requête à image.jpg,
        # on essaye d’abord image.jpg.avif et image.jpg.webp
        try_files $uri$avif_suffix $uri$webp_suffix $uri =404;
    }
}

Là encore, pour éviter d’accumuler les pertes de qualité, encodez toujours vos versions WebP et AVIF à partir de la meilleure qualité possible, pas à partir de vos JPEGs déjà optimisés.

Sans perte : PNG

Le PNG est le format sans perte principal sur le Web.

Je vous recommande de traiter vos fichiers PNG avec un outil d’optimisation comme OptiPNG, qui peut les recompresser pour qu’ils soient plus petits, sans perdre d’informations :

« pngquant » est un autre programme qui peut s’avérer utile :

« pngquant » fonctionne différemment, et n’est en fait pas sans perte : il convertit les PNG en une version 256 couleurs, en utilisant une palette optimisée.
Cette astuce peut parfois diminuer significativement la taille du fichier, et n’est pas trop visible sur les images ayant quelques centaines de couleurs uniques (jusqu’à 1 000 ou 2 000, peut-être).

Ce sont tous deux des programmes en ligne de commande, mais ils sont plutôt faciles à utiliser.

Format sans perte plus moderne : WebP

En plus d’être une alternative au JPEG, le WebP propose aussi un mode lossless, qui génère des fichiers plus petits que le PNG.

Là aussi, vous pouvez renvoyer du WebP aux navigateurs qui le supportent, et vous rabattre sur le PNG dans le cas contraire.

AVIF possède aussi un mode sans perte, mais il n’est pas aussi optimisé que le WebP, et ne vaut donc pas la peine d’être utilisé (tous les navigateurs qui prennent en charge l’AVIF supportent aussi le WebP).

Comparaisons de tailles de fichiers

Pour vous donner un exemple concret, examinons les 2 images que j’ai intégrées dans l’article suivant :

Le diagramme (680 x 508) faisait :

Comme vous pouvez le voir, le PNG et le WebP sans perte sont tout à fait utilisables pour des graphiques et des infographies, et peuvent parfois tirer profit d’une palette de 256 couleurs.

La photo était initialement un fichier JPEG de 3,6 Mio en 4032 x 3024 – beaucoup trop gros.

Après l’avoir redimensionné en 640 x 480, il pesait :

Comme mentionné précédemment, les formats sans pertes ne devraient pas être utilisés pour les photos.

Images animées : GIF

Les images animées forment une catégorie à part, et ne peuvent pas être encodées en JPEG.

Le PNG animé existe (APNG = « Animated PNG »), mais le standard de fait pour les images animées est le format GIF (vous prononcez ça comme vous voulez 😜).

Sachez que le GIF est un format très peu efficace (d’un point de vue compression) et est par ailleurs limité à seulement 256 couleurs.

Il est acceptable pour des petites icônes de chargement, par exemple, mais, s’il vous plaît, évitez de l’utiliser pour de grandes images, ou pour du contenu vidéo.

Si votre image GIF se retrouve à faire plusieurs megaoctets (Mo), vous devriez probablement utiliser un vrai format vidéo. Les images animées encodent chaque « frame » image par image, tandis que les codecs vidéos peuvent utiliser des techniques plus sophistiquées, permettant des gains significatifs sur la taille du fichier.

Formats plus modernes : WebP et AVIF

Le WebP et L’AVIF ont également des version animées, qui sont supérieures au GIF à tout point de vue.
C’est une bonne idée de les utiliser, là encore, en se rabattant sur le GIF lorsque nécessaire.

Mise en cache

Réduire la taille des fichiers n’est pas le seul moyen d’économiser de la bande passante :
Il faut aussi mettre en place une stratégie de cache appropriée.

Sur ce blog, j’utilise une stratégie très simple, mais redoutablement efficace :

1. Les images sont mises en cache navigateur pendant une durée maximale d’un an.
Voici la config Nginx correspondante :

add_header Cache-Control "public, max-age=31536000";
add_header Cache-Control immutable;

Une fois qu’une image est dans le cache navigateur, aucune requête supplémentaire n’est effectuée pour cette image.

Si j’ai vraiment besoin de mettre à jour le contenu d’une image, je mettrais à jour son URL dans le code HTML – par exemple, en ajoutant un paramètre de chaîne de requête (« query string », ex : « ?version=2 »). Cette technique permet de forcer le navigateur à faire une nouvelle requête.

Cette stratégie de mise en cache peut en réalité être utilisée pour tous les « assets » (CSS, JavaScript, …), pas uniquement les images.

2. Les documents HTML demandent une revalidation, pour que le navigateur n’utilise pas une version en cache obsolète :

add_header Cache-Control no-cache;
# Équivalent à la syntaxe plus ancienne :
#add_header Cache-Control max-age=0, must-revalidate;

Ne laissez pas le mot-clé « no-cache » vous induire en erreur : il n’est pas équivalent à « no-store ».
Il signifie que le navigateur va vérifier s’il y a une version plus à jour, et si le document HTML n’a pas été modifié plus récemment que la version en cache, le serveur renverra un code « 304 Not Modified », avec un corps de réponse vide.

Réseau de diffusion de contenu

Les sites Web avec beaucoup de trafic peuvent bénéficier d’un réseau de diffusion de contenu (« Content Delivery Network » en anglais, ou CDN).

Un CDN est constitué de serveurs proxy déployés à plusieurs endroits géographiquement distincts.

C’est très utile lorsque le trafic devient supérieur à ce qui peut être absorbé par un seul serveur, et améliore également la vitesse de chargement pour les utilisateurs (latence et débit), grâce à leur présence tout autour du globe.

Quelques CDNs connus : Akamai, Cloudflare, Fastly et Amazon Cloudfront.

En plus de cela, il existe des entreprises qui fournissent des services d’optimisation d’images basé sur des CDNs.
Par exemple Cloudinary (basé sur Akamai) et Imgix (basé sur Fastly).
Si vous décidez d’utiliser ce genre de service, assurez-vous de leur donner la meilleure qualité possible en entrée, et d’ajuster la qualité d’encodage à vos besoins, sinon, les images en sorties pourraient être assez moches.

Le chargement différé (« lazy loading »)

Une dernière astuce que vous pouvez utiliser pour économiser de la bande passante, est de définir l’attribut « loading » de vos éléments <img> à « lazy ».

<img loading="lazy" src="image.jpg" alt="..." />

Cela permet de différer le chargement d’images initialement non visibles jusqu’à ce que l’utilisateur les rende visible en faisant défiler la page.

Bien sûr, vous n’économiserez de la bande passante que si les images en question sont en dehors de la zone visible au moment du chargement de la page, et si une partie de vos utilisateurs ne « scrollent » pas.

J’ajouterais que le fait de voir des images surgir lors du défilement peut être un peu perturbant.