Servir des images WebP & AVIF avec Nginx
Vincent Bernat
WebP et AVIF sont deux formats d’image pour le web. Ils visent à produire des fichiers plus petits que les formats JPEG et PNG. Ils supportent tous deux la compression avec ou sans perte, ainsi que la transparence alpha. WebP a été développé par Google et est un dérivé du format vidéo VP81. Il est supporté par la plupart des navigateurs. AVIF utilise le format vidéo AV1, plus récent, pour obtenir de meilleurs résultats. Il est supporté par les navigateurs basés sur Chromium et a une prise en charge expérimentale pour Firefox.
Mise à jour (12.2021)
Firefox prend en charge AVIF de manière expérimentale depuis la version 77 et par défaut depuis la version 93.
Votre navigateur sait décoder les formats WebP et AVIF. Votre navigateur ne sait décoder aucun de ces formats. Votre navigateur ne sait décoder que le format WebP. Votre navigateur ne sait décoder que le format AVIF.
Sans JavaScript, je ne peux pas déterminer les formats reconnus par votre navigateur.
Conversion et optimisation des images#
Pour ce blog, j’utilise les commandes suivantes pour convertir et optimiser les images JPEG et PNG. Sautez à la section suivante si vous n’êtes intéressé que par la configuration de Nginx.
Images JPEG#
Les images JPEG sont converties au format WebP avec cwebp.
find media/images -type f -name '*.jpg' -print0 \ | xargs -0n1 -P$(nproc) -i \ cwebp -q 84 -af '{}' -o '{}'.webp
Elles sont converties au format AVIF avec avifenc
de libavif:
find media/images -type f -name '*.jpg' -print0 \ | xargs -0n1 -P$(nproc) -i \ avifenc --codec aom --yuv 420 --min 20 --max 25 '{}' '{}'.avif
Ensuite, elles sont optimisées avec jpegoptim compilé avec l’encodeur amélioré de Mozilla, via Nix. C’est une des raisons pour lesquelles j’aime Nix.
jpegoptim=$(nix-build --no-out-link \ -E 'with (import <nixpkgs>{}); jpegoptim.override { libjpeg = mozjpeg; }') find media/images -type f -name '*.jpg' -print0 \ | sort -z | xargs -0n10 -P$(nproc) \ ${jpegoptim}/bin/jpegoptim --max=84 --all-progressive --strip-all
Images PNG#
Les images PNG sont sous-échantillonnées vers une palette RGBA 8 bits avec pngquant. Cette conversion réduit considérablement la taille des fichiers tout en étant pratiquement invisible.
find media/images -type f -name '*.png' -print0 \ | sort -z | xargs -0n10 -P$(nproc) \ pngquant --skip-if-larger --strip \ --quiet --ext .png --force
Ensuite, elles sont converties au format WebP avec cwebp
dans le
mode sans perte.
find media/images -type f -name '*.png' -print0 \ | xargs -0n1 -P$(nproc) -i \ cwebp -z 8 '{}' -o '{}'.webp
Aucune conversion n’est effectuée vers AVIF : la compression sans
perte n’est pas aussi efficace que pngquant
et la compression avec
perte n’est que marginalement meilleure par rapport à ce que j’obtiens
avec WebP.
Garder uniquement les fichiers les plus petits#
Je ne garde les images WebP et AVIF que si elles sont au moins 10 % plus petites que le format original : le décodage est généralement plus rapide pour les JPEG et PNG et les images JPEG peuvent être décodées progressivement2.
for f in media/images/**/*.{webp,avif}; do orig=$(stat --format %s ${f%.*}) new=$(stat --format %s $f) (( orig*0.90 > new )) || rm $f done
Je ne garde les images au format AVIF que lorsqu’elles sont plus petites que le format WebP.
for f in media/images/**/*.avif; do [[ -f ${f%.*}.webp ]] || continue orig=$(stat --format %s ${f%.*}.webp) new=$(stat --format %s $f) (( $orig > $new )) || rm $f done
Nous pouvons comparer combien d’images sont conservées après conversion en WebP ou AVIF :
printf " %10s %10s %10s\n" Original WebP AVIF for format in png jpg; do printf " ${format:u} %10s %10s %10s\n" \ $(find media/images -name "*.$format" | wc -l) \ $(find media/images -name "*.$format.webp" | wc -l) \ $(find media/images -name "*.$format.avif" | wc -l) done
AVIF se comporte mieux que MozJPEG pour la plupart des fichiers JPEG alors que WebP ne fait mieux que pour un fichier sur deux :
Original WebP AVIF PNG 64 47 0 JPG 83 40 74
Informations complémentaires#
Je n’ai pas détaillé mes choix pour les paramètres de qualité et il n’y a pas beaucoup de science là-dedans. Voici deux ressources qui donnent plus de détails sur AVIF :
- Jake Archibald compare WebP et AVIF avec des exemples,
- Daniel Aleksandersen compare WebP et AVIF à la même qualité visuelle grâce à DDSIM.
Servir WebP & AVIF avec Nginx#
Pour servir les images WebP et AVIF, il existe deux possibilités :
- utiliser
- utiliser la négociation de contenu pour laisser le serveur envoyer le format le mieux supporté.
J’utilise la deuxième approche. Elle repose sur l’inspection de
l’en-tête HTTP Accept
dans la requête. Pour Chrome, elle ressemble à
ceci :
Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
Je configure Nginx pour qu’il serve l’image AVIF, puis l’image WebP et se rabatte sur l’image JPEG/PNG originale en fonction de ce que le navigateur annonce3 :
http { map $http_accept $webp_suffix { default ""; "~image/webp" ".webp"; } map $http_accept $avif_suffix { default ""; "~image/avif" ".avif"; } } server { # […] location ~ ^/images/.*\.(png|jpe?g)$ { add_header Vary Accept; try_files $uri$avif_suffix$webp_suffix $uri$avif_suffix $uri$webp_suffix $uri =404; } }
Par exemple, supposons que le navigateur demande
/images/ont-box-orange@2x.jpg
. S’il prend en charge WebP mais pas
AVIF, $webp_suffix
est défini à .webp
tandis que $avif_suffix
est la chaîne vide. Le serveur essaie de servir le premier fichier
existant dans cette liste :
/images/ont-box-orange@2x.jpg.webp
/images/ont-box-orange@2x.jpg
/images/ont-box-orange@2x.jpg.webp
/images/ont-box-orange@2x.jpg
Si le navigateur prend en charge les deux formats d’image, Nginx parcourt la liste suivante :
/images/ont-box-orange@2x.jpg.webp.avif
(il n’existe jamais)/images/ont-box-orange@2x.jpg.avif
/images/ont-box-orange@2x.jpg.webp
/images/ont-box-orange@2x.jpg
Eugene Lazutkin explique plus en détails comment cela fonctionne. Je n’ai présenté ici qu’une variation prenant en charge à la fois WebP et AVIF.
-
VP8 est uniquement utilisé pour la compression avec perte. La compression sans perte utilise un autre algorithme. ↩︎
-
Le décodage progressif n’est pas prévu pour WebP mais pourrait être implémenté en utilisant des images de basse qualité pour AVIF. Voir cette question pour une discussion. ↩︎
-
L’en-tête
Vary
permet de s’assurer qu’un cache intermédiaire (un proxy ou un CDN) vérifie l’en-têteAccept
avant d’utiliser une réponse en cache. Internet Explorer a des difficultés avec cet en-tête et peut ne pas être en mesure de mettre la ressource en cache correctement. Il existe une solution de contournement, mais la part de marché d’Internet Explorer est désormais si faible qu’il est inutile de la mettre en œuvre. ↩︎