2021-06-14
Quand on distribue des images sur le web, il est important de les optimiser pour réduire la quantité de données transférées et le temps de chargement des pages. Tout le monde n'a pas la fibre, et tout le monde n'a pas un accès à internet illimité. Ce que j'entends ici par « optimiser », c'est donc de réduire le poids des fichiers au maximum, sans en réduire la qualité perceptible.
Pour vous donner un exemple, les images présentes dans cet article pesaient 673 ko avant optimisation et pèsent maintenant seulement 434 Ko après une petite séance de YOGA, soit une réduction de plus d'un tiers, sans aucun effort ! 😁️
Je travaille depuis quelques années maintenant sur un projet nommé YOGA. Il s'agit d'une bibliothèque et d'un logiciel en ligne de commande dont le but est, entre autres, de réduire le poids des images. Et depuis quelques mois je me suis lancé dans le développement d'une interface graphique pour en faciliter l'utilisation.
Aujourd'hui on va parler de YOGA Image Optimizer, dont je viens de sortir la toute première version, et qui est donc l'interface graphique pour YOGA.
En résumé : YOGA est la bibliothèque et l'outil en ligne de commande, et YOGA Image Optimizer l'interface graphique. Oui je suis très doué pour trouver des noms qui portent à confusion... 😅️
YOGA est un projet open source que je développe dans le cadre de mon travail chez Wanadev. Chez Wanadev (et WanadevStudio), on bosse sur des projets très variés : on fait aussi bien des sites web que des jeux vidéos en VR. L'une de nos activités est la réalisation d'applications 3D sur le web (WebGL), et c'est la raison pour laquelle on s'est lancé dans le développement de YOGA.
YOGA est une bibliothèque Python et un outil en ligne de commande permettant de convertir et d'optimiser nos images et nos modèles 3D :
Si vous voulez en apprendre plus à son sujet, vous pouvez lire l'article que j'ai publié il y a quelques mois sur le blog de Wanadev.
Logo de YOGA, dessiné par ma collègue Katia
YOGA Image Optimizer est simplement une interface graphique pour la partie « image » de YOGA, que je développe sur mon temps libre.
En effet, j'utilisais de plus en plus YOGA pour optimiser les images que je publie sur mon blog ou d'autres sites, et je commençais à en avoir marre de me taper des commandes à rallonge du style:
yoga image --output-format=jpeg --jpeg-quality=95 input.png output.jpeg
Je me suis donc lancé dans le développement d'une interface graphique en GTK, qui me permettrait de simplement « drag & drop » mes images dessus et de les optimiser en un clic.
Capture d'écran de YOGA Image Optimizer
Comme vous pouvez le voir sur la capture d'écran ci-dessus, le fonctionnement du logiciel est assez simple :
Notez que lorsque l'optimisation est lancée, un bouton "Arrêter" remplace le bouton "Optimiser". En cliquant dessus, vous annulerez les prochaines optimisations, mais celles déjà en cours continueront de s'exécuter (c'est un point sur lequel il faut encore que je travaille).
On ne va pas tourner autour du pot... Combien de place peut-on espérer gagner en optimisant les images ? Eh bah... Ça dépend du format dont on parle, puisque YOGA en supporte quatre en sortie, et ça dépend bien sûr de l'image en elle-même.
Au final il n'est pas facile de répondre a cette question avec précision mais on peut se faire une idée en effectuant un benchmark sur une série d'images. J'ai donc regroupé un échantillon composé de 11 images assez variées : des photos, des dessins, des captures d'écran et même du pixel art !
Les 11 images que nous allons utiliser pour nos tests...
Je vais encoder chacune de ces images avec des bibliothèques de référence (celles couramment utilisées par les logiciels qui manipulent des images) et avec YOGA afin de comparer la taille des fichiers ainsi obtenus.
Pour optimiser les JPEGs, YOGA s'appuie sur Guetzli, une bibliothèque développée par des ingénieurs de Google Research Europe [qui est situé à Zürich, ce qui explique le nom imprononçable de la bibliothèque, qui pour l'anecdote signifie « biscuit » en Suisse allemand 🤤️].
Si vous souhaitez savoir comment Guetzli s'y prend pour optimiser les JPEGs, vous trouverez à la fin de cet article des liens vers le papier de recherche et quelques autres ressources ; il serait trop long d'essayer d'expliquer tout ça ici. 😉️
Pour se faire une idée de l'efficacité de l'optimisation des JPEGs, on va encoder les 11 images de l'échantillon avec la libjpeg, qui servira de référence, et avec YOGA (donc Guetzli si vous avez bien suivis 😉️). Dans les deux cas j'ai mis la même valeur de q=95 pour paramètre de qualité des JPEGs et j'ai obtenu les résultats suivants :
+-----------------------------+---------------------+----------------------------+ | Image | libjpeg (référence) | YOGA JPEG | +=============================+=====================+=============+==============+ | drawing_gbdev11.png | 75,87 ko | 62,38 ko | -17,78 % | +-----------------------------+---------------------+-------------+--------------+ | drawing_keyblade.png | 61,40 ko | 33,75 ko | -45,03 % | +-----------------------------+---------------------+-------------+--------------+ | drawing_micropython.png | 64,11 ko | 52,23 ko | -18,53 % | +-----------------------------+---------------------+-------------+--------------+ | drawing_pixelart.png | 129,84 ko | 73,76 ko | -43,20 % | +-----------------------------+---------------------+-------------+--------------+ | photo_fire_oven.jpg | 341,75 ko | 289,29 ko | -15,35 % | +-----------------------------+---------------------+-------------+--------------+ | photo_forest.png | 6,68 Mo | 6,09 Mo | -8,79 % | +-----------------------------+---------------------+-------------+--------------+ | photo_fox.jpg | 791,32 ko | 568,25 ko | -28,19 % | +-----------------------------+---------------------+-------------+--------------+ | photo_gameboy.jpg | 3,36 Mo | 2,33 Mo | -30,55 % | +-----------------------------+---------------------+-------------+--------------+ | photo_park_plaza.jpg | 5,93 Mo | 4,04 Mo | -31,88 % | +-----------------------------+---------------------+-------------+--------------+ | screenshot_sdcc_install.png | 94,53 ko | 65,00 ko | -31,24 % | +-----------------------------+---------------------+-------------+--------------+ | screenshot_yoga.png | 128,56 ko | 99,30 ko | -22,76 % | +-----------------------------+---------------------+-------------+--------------+ | | **Moyenne** | **-26,66 %** | +---------------------------------------------------+-------------+--------------+
Sur l'échantillon d'images, on a obtenu des fichiers de 9 % à 45 % plus petits avec YOGA, pour une réduction moyenne de 27 %. Pas mal !
Pour mieux se rendre compte de ce que cela représente je vous ai fait un petit diagramme dans lequel j'ai représenté le poids du fichier en sortie de YOGA relativement à celui compressé avec libjpeg :
Comparaison du poids des JPEGs produits par YOGA par rapport à la référence (libjpeg) en pourcentage
Dans le diagramme ci-dessus on exprime les tailles des images produites par les différents encodeurs en pourcentage de la taille de l'image produite par l'encodeur de référence. Pour les images encodées par libjpeg, qui est notre encodeur de référence, on a donc toujours une taille de 100 %.
Si la taille obtenue est inférieure à 100 %, on a donc réduit le poids de l'image et si, à l'inverse, cette taille est supérieure à 100 % on a alors alourdi l'image (ce qui n'est pas trop le but recherché 😅️).
--------------------------------------------------------------------------------
📝️ Note:
--------------------------------------------------------------------------------
NOTE : Les limites de l'exercice
Le JPEG est un format de compression à perte dont la quantité de données à perdre est contrôlée par le paramètre de qualité (q).
Si on met ce paramètre à q=0, on aura une image plus légère mais dégueulasse, et si on le définit à q=100, on aura une image très lourde mais de très grande qualité.
Pour ce benchmark j'ai sélectionné pour les deux encodeurs testés une qualité de q=95, ce qui pose deux problèmes :
Quoi qu'il en soit, dans notre cas ça sera suffisant pour se faire une idée de l'efficacité de YOGA, mais gardez juste à l'esprit qu'on pourrait faire bien mieux. 😉️
--------------------------------------------------------------------------------
Passons à présent à l'optimisation des PNG. Cette fois encore, YOGA s'appuie sur les travaux d'ingénieurs de chez Google à travers les bibliothèques Zopfli et ZopfliPNG [eh oui, encore un nom à coucher dehors, mais cette fois-ci il n'est plus question de biscuits mais de brioche tressée 😅️].
Pourquoi est-ce que je cite deux bibliothèques aux noms très proches pour l'optimisation des PNGs ? Tout simplement par ce que l'encodage des PNGs se fait en deux phases distinctes :
Cette fois encore je ne vais pas rentrer plus dans les détails, je vous mets tous les liens utiles en fin d'article si vous voulez en apprendre plus. 😉️
Comme pour le benchmark précédent, on va encoder les images en PNG en utilisant une bibliothèque de référence, qui sera cette fois-ci libpng, et avec YOGA. Étant donné que YOGA dispose de deux presets ("PNG" et "PNG (slow)"), on testera les deux afin de les comparer au passage.
Voici donc les données obtenues :
+-----------------------------+--------------------+----------------------------+-------------------------+ | Image | libpng (référence) | YOGA PNG | YOGA PNG (slow) | +=============================+====================+=============+==============+==========+==============+ | drawing_gbdev11.png | 120,68 ko | 87,47 ko | -27,52 % | 87,44 ko | -27,54 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | drawing_keyblade.png | 98,94 ko | 77,33 ko | -21,84 % | 77,08 ko | -22,10 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | drawing_micropython.png | 54,05 ko | 41,73 ko | -22,80 % | 41,69 ko | -22,87 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | drawing_pixelart.png | 14,55 ko | 3,61 ko | -75,18 % | 3,61 ko | -75,18 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | photo_fire_oven.jpg | 1,99 Mo | 1,86 Mo | -6,86 % | 1,81 Mo | -8,97 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | photo_forest.png | 20,82 Mo | 20,33 Mo | -2,33 % | 20,30 Mo | -2,49 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | photo_fox.jpg | 2,88 Mo | 2,64 Mo | -8,21 % | 2,62 Mo | -9,21 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | photo_gameboy.jpg | 11,28 Mo | 9,52 Mo | -15,62 % | 9,33 Mo | -17,26 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | photo_park_plaza.jpg | 22,73 Mo | 22,79 Mo | +0,30 % | 22,78 Mo | +0,23 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | screenshot_sdcc_install.png | 9,50 ko | 8,00 ko | -15,77 % | 8,00 ko | -15,77 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | screenshot_yoga.png | 100,60 ko | 83,86 ko | -16,65 % | 83,33 ko | -17,17 % | +-----------------------------+--------------------+-------------+--------------+----------+--------------+ | | **Moyenne** | **-19,32 %** | | **-19,85 %** | +--------------------------------------------------+-------------+--------------+----------+--------------+
Et un petit diagramme pour y voir plus clair :
Comparaison du poids des JPEGs produits par YOGA par rapport à la référence (libpng) en pourcentage
On peut voir ici que YOGA a été en mesure de réduire le poids des images du corpus jusqu'à -75% par rapport à la bibliothèque de référence. En moyenne, le réglage par défaut a permis de réduire le poids des images de 19,32 %, et le réglage « slow » de 19,85 %.
Le réglage « slow » permet donc de gratter quelques octets... mais au prix d'un encodage 9 fois plus lent en moyenne (on en reparle un peu plus tard 😉️)...
--------------------------------------------------------------------------------
📝️ Note:
--------------------------------------------------------------------------------
NOTE : Et on peut également remarquer que YOGA a réussi à augmenter le poids de l'une des images du corpus de 0,30 % (Oops 😅️).
Ça arrive dans de rares cas, il faut encore que je travaille sur ce point pour conserver l'encodage original (soit le filtrage seul, soit le filtrage et la compression) lorsque l'image produite est plus grosse que celle en entrée.
--------------------------------------------------------------------------------
Derniers formats dont on doit parler, les WebP. Oui j'ai bien parlé DES formats WebP, car le WebP supporte deux modes de compression :
Dans YOGA je considère le WebP avec perte (lossy) et le WebP sans perte (lossless) comme deux formats distincts, c'est plus simple a gérer de cette façon.
Contrairement aux optimisations JPEG et PNG, je ne dispose pas ici d'une bibliothèque magique me permettant de « suroptimiser » les compressions WebP, j'utilise donc la libwebp, comme tout le monde...
Pour les WebPs, YOGA se contente donc de pousser au maximum les paramètres de compressions (en tout cas ceux qui lui sont accessibles) et s'assure de supprimer toutes les métadonnées. Cela lui permet d'obtenir des images un peu plus petites que celles générées en utilisant les paramètres par défaut de l'encodeur.
Mais pourquoi avoir ajouté le format WebP à YOGA si on ne peut pas l'optimiser plus que ça ?
Si on avait déjà un WebP en entrée, le traiter avec YOGA ne fera certes pas gagner grand-chose... Mais là où ça devient intéressant, c'est lorsque l'on convertit une image depuis un autre format vers WebP. Le format WebP est très efficace en termes de compression !
Je sens bien que vous voulez des chiffres alors je vous fais un petit benchmark pour résumer. Ici on va comparer ce qui est comparable ; on va donc :
Et voici ce que ça donne :
+-----------------------------------------+--+------------------------------------------+ | Encodeurs AVEC perte | | Encodeurs SANS perte | +---------------------+-------------------+ +----------------------+-------------------+ | | Réduction moyenne | | | Réduction moyenne | +=====================+===================+==+======================+===================+ | libjpeg (référence) | 0,00 % | | libpng (référence) | 0,00 % | +---------------------+-------------------+ +----------------------+-------------------+ | YOGA JPEG | -26,66 % | | YOGA PNG (slow) | -19,85 % | +---------------------+-------------------+ +----------------------+-------------------+ | YOGA WebP (lossy) | -48,38 % | | YOGA WebP (lossless) | -37,45 % | +---------------------+-------------------+--+----------------------+-------------------+
À gauche : Comparaison de la taille des images encodées avec les encodeurs avec perte de YOGA par rapport à libjpeg (en pourcentage) / À droite : Comparaison de la taille des images encodées avec les encodeurs sans perte de YOGA par rapport à libpng (en pourcentage)
En moyenne, le WebP avec perte réduit presque de moitié le poids des images par rapport à la libjpeg, et le WebP sans perte réduit quant à lui de plus d'un tiers le poids des images par rapport à la libpng.
Que ce soit pour l'encodage avec ou sans perte, on peut remarquer que le WebP est presque deux fois plus efficace que les autres encodeurs de YOGA... Plutôt intéressant ! 😎️
Sachant que le WebP est à présent supporté par tous les navigateurs majeurs, il faut très clairement se poser la question de son utilisation si votre chaîne de production le permet !
supporté par tous les navigateurs majeurs
C'est bien beau de vous montrer, graphiques à l'appui, à quel point YOGA est efficace pour faire perdre du poids à vos images... Mais il serait malhonnête de ne pas mentionner le coût de cette optimisation.
Si on se contentait des résultats de la section précédente, on pourrait se demander pourquoi les encodeurs de référence ne sont pas « meilleurs » dans la compression des images, ou pourquoi tout le monde n'utilise pas Guetzli et ZopfliPNG puisqu'ils font mieux...
La réponse est simple : ces encodeurs ne sont pas conçus avec les mêmes contraintes. Ils doivent non seulement compresser du mieux possible les images, mais ils doivent le faire rapidement et avec une empreinte mémoire raisonnable. Vous le voyez venir, c'est LÀ qu'est situé le coût des optimisations faites par YOGA.
Je ne vais pas vous montrer de jolis graphiques cette fois-ci : la quantité de mémoire et le temps d'optimisation dépendent de trop de paramètres différents. Mais on va quand même évoquer les points qui me semblent pertinents.
Le premier point dont on va parler est la durée nécessaire à la compression des images.
Pour une optimisation des JPEGs avec YOGA, il faut compter, à la louche, de 1 à 5 minutes par mégapixels en fonction de la complexité de l'image.
Pour vous faire une idée, YOGA a eu besoin de 18 secondes pour sa compression la plus rapide et jusqu'à 1 heure 11 minutes pour la plus longue, là ou la libjpeg n'a jamais dépassé la seconde ! 😵️
Pour les PNGs, libpng a mis jusqu'à 8 secondes pour compresser une image là ou YOGA à pu mettre jusqu'à près de 3 minutes... Ça va, c'est moins abominaffreux que pour les JPEGs ! 😅️
Avec le réglage « slow », YOGA s'est montré de 3 à 16 fois plus lent qu'avec le réglage « classique » (9 fois plus lent en moyenne), pour un temps de compression maximal de 20 minutes sur le corpus d'images. Étant donné le peu de gain apporté par ce réglage par rapport à son coût, j'envisage sérieusement de le supprimer dans une future version du logiciel.
Pour les WebPs avec perte, les réglages de YOGA se révèlent généralement de 2 à 4 fois plus lents que ceux par défaut de l'encodeur. Malgré cela, il ne lui a fallu que 6 secondes au maximum pour compresser une image.
Pour les WebPs sans perte, les réglages de YOGA se révèlent généralement de 6 à 11 fois plus lents que ceux par défaut de l'encodeur, pour un temps d'optimisation maximal de 1 minute et 30 secondes sur l'échantillon d'images, ce qui reste très raisonnable.
Parlons à présent de la quantité de mémoire RAM utilisée lors des optimisations.
La mémoire, c'est clairement le gros point faible de Guetzli, l'encodeur JPEG utilisé par YOGA. Dans les faits il faut compter de 200 Mo à 400 Mo du mégapixel, ce qui représente une jolie quantité de mémoire sur les images les plus grandes.
Pour vous donner une idée, là où libjpeg a utilisé, dans le pire des cas, 230 Mo de RAM, YOGA en a utilisé 3,7 Go ! 😱️
Pour les PNG, la quantité de mémoire utilisée est beaucoup plus raisonnable : YOGA avec son réglage par défaut consomme seulement 2 à 3 fois plus de mémoire que libpng, pour un maximum de 388 Mo sur le corpus d'images.
Le réglage « slow » ne consomme en moyenne que 14 % de mémoire en plus par rapport au réglage de base, ce qui ne représente pas une différence énorme (contrairement au temps de calcul).
Les réglages de YOGA consomment 4,5 fois plus de RAM que ceux par défaut de libwebp pour les WebP avec perte et 3 fois plus pour les WebP sans perte.
Sur notre sélection d'images, la compression WebP avec perte a consommé jusqu'à 291 Mo de RAM, et la compression WebP sans perte a consommé quant à elle jusqu'à 841 Mo de mémoire.
Au final, on voit qu'on peut facilement réduire la taille de ses images de 30 à 50 %, lorsque des formats modernes (comme le WebP) peuvent être utilisés en remplacement des traditionnels JPEG et PNG. L'optimisation des formats « classiques » reste cependant intéressante avec une moyenne de 27 % de gains sur les JPEGs et 20 % sur les PNGs.
On a pu voir que le coût lié à cette optimisation peut s'avérer très important, mais il convient toutefois de le relativiser. Certaines images de l'échantillon sont en effet très volumineuses (plus de 16 Mpix pour la plus grande), ce qui, forcément, fait exploser les temps de compression et la quantité de mémoire utilisée.
Dans la pratique, la plupart des images que je publie sur le Web font moins de 1 Mpix ; le coût de l'optimisation pour ces images est du coup beaucoup plus acceptable.
Si vous souhaitez creuser tout ça par vous-mêmes, vous retrouverez l'échantillon d'images, les données et les scripts utilisés pour le benchmark sur mon Github.
Pour le moment, YOGA Image Optimizer fonctionne sous Linux et un portage Windows est également disponible.
Pour l'installer sous Linux, si vous êtes sur ArchLinux, un paquet est déjà disponible sur AUR (il a été créé seulement quelques heures après la sortie du logiciel ! 😲️). Pour les autres, vous pouvez :
un paquet est déjà disponible sur AUR
Pour l'installer sous Windows, vous pouvez soit utiliser l'installeur automatique, soit télécharger un Zip contenant une version portable du logiciel. Vous les trouverez sur la page « Release » du dépôt Github :
https://github.com/flozz/yoga-image-optimizer/releases
Il n'y a pour le moment aucune version de prévue pour Mac OS : je ne connais pas assez bien ce système et je ne possède pas de Mac (mais les contributions sont les bienvenus ! 😉️).
Je vais bien sûr continuer à travailler sur YOGA et YOGA Image Optimizer.
Pour l'interface graphique, je vais surtout la rendre plus configurable (combien d'images optimiser en parallèle, formats et qualité de compression par défaut, etc.).
Pour YOGA, je vais travailler à améliorer son optimisation des PNGs, pour ne plus qu'il soit possible de se retrouver avec une image plus grande en sortie qu'en entrée. Je pense aussi à travailler sur des améliorations pour le format WebP, en utilisant libwebp en direct (plutôt qu'en l'utilisant à travers Pillow), afin d'avoir la main sur un plus grand nombre de paramètres de l'encodeur. Enfin, j'ai très envie de me pencher sur un nouveau format d'image, l'AVIF, qui semble très prometteur !
--------------------------------------------------------------------------------
Voici quelques liens si vous souhaitez aller plus loin avec les sujets abordés dans cet article.
YOGA et YOGA Image Optimizer :
Dépôt de YOGA Image Optimizer sur Github
Benchmark : images, données et scripts utilisés
Guetzli :
Annonce de la sortie de Guetzli sur le blog de Google AI
Papier de recherche écrit par les ingénieurs derrière Guetzli
Article de présentation de Guetzli très accessible écrit par « Les Numériques »
Zopfli / ZopfliPNG :
Annonce de la sortie de Zopfli sur le blog des développeurs de Google
Papier sur la compression de données avec Zopfli
Dépôt Github de Zopfli et ZopfliPNG
WebP :
--------------------------------------------------------------------------------