Optimisez vos images avec YOGA Image Optimizer

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... 😅️

Qu'est-ce que YOGA exactement ?

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 :

  • Pour les images, il est capable d'ouvrir la plupart des formats courants et de les convertir vers les formats JPEG, PNG, et depuis peu WebP, le tout en les optimisant au maximum (on en reparle un peu plus bas 😉️).
  • Pour les modèles 3D, YOGA supporte en entrée un grand nombre de formats, qu'il pourra ensuite convertir en glTF et en GLB, qui sont des formats adaptés au web. Les modèles peuvent bien sûr bénéficier de quelques optimisations au passage, et leurs textures peuvent être traitées par la partie image de YOGA.

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

Logo de YOGA, dessiné par ma collègue Katia

Et YOGA Image Optimizer dans tout ça ?

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 :

  • Vous ajoutez une ou plusieurs images, soit en faisant un glisser-déposer, soit en cliquant sur le bouton [+] situé en haut à gauche.
  • Par défaut YOGA Image Optimizer va :
    • sélectionner le même format pour l'image en sortie que celui de l'image en entrée (si toutefois celui-ci est supporté en sortie, sinon JPEG sera sélectionné),
    • et nommer le fichier de sortie de la même manière que celui d'entrée, en le suffixant avec .opti (par exemple : image.pngimage.opti.png).
  • Vous n'avez alors plus qu'à cliquer sur le bouton "Optimiser" situé en haut à droite et YOGA commencera a optimiser toutes les images de la liste, deux par deux dans des processus dédiés (le nombre d'images traitées en parallèle n'est actuellement pas configurable mais ça le sera dans une future version).

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).

Optimisation, combien on gagne ?

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 !

Échantillon d'images

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.

Optimisation des JPEGs

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 :

Diagramme bâton

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 :

  • Premièrement ce paramètre de qualité ne signifie pas exactement la même chose pour les deux encodeurs, ils ne se basent pas sur la même définition de ce qu'est une « jolie » image. Dit autrement, le mettre à la même valeur ne veut pas dire que la qualité perceptible de l'image sera identique pour les deux encodeurs. Les chercheurs de Google ont une approche bien plus pointue dans leur papier de recherche.
  • Deuxième problème, en faisant varier ce paramètre, on constaterait très probablement des écarts dans les gains obtenus entre Guetzli et libjpeg.

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. 😉️

Optimisation des PNGs

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 :

  • Le filtrage, qui va organiser les données de manière plus efficace pour la compression. C'est là qu'intervient ZopfliPNG.
  • Puis la compression des données obtenues après l'étape de filtrage à l'aide de l'algorithme Deflate, le même qui est utilisé dans la zlib, dans gzip, les fichiers zip, etc. Et cette partie-là est réalisée par Zopfli.

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 :

Diagramme bâton

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.

Optimisation des WebPs

Derniers formats dont on doit parler, les WebP. Oui j'ai bien parlé DES formats WebP, car le WebP supporte deux modes de compression :

  • Une compression à perte, un peu comme le JPEG sauf qu'il supporte la transparence,
  • et une compression sans perte (comme le PNG).

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 :

  • comparer l'encodage WebP avec perte aux encodeurs JPEG, en prenant la libjpeg comme référence,
  • et comparer l'encodage WebP sans perte aux encodeurs PNG, en prenant la libpng comme référence.

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 %
Diagrammes bâton

À 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 !

Une optimisation, mais à quel prix ?

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 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.

Durée d'optimisation

Le premier point dont on va parler est la durée nécessaire à la compression des images.

JPEG

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 ! 😵️

PNG

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.

WebP

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.

Mémoire utilisée

Parlons à présent de la quantité de mémoire RAM utilisée lors des optimisations.

JPEG

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 ! 😱️

PNG

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).

WebP

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.

Conclusion du benchmark

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.

Disponibilité du logiciel

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 :

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 :

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 ! 😉️).

Et ensuite ?

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 :

Guetzli :

Zopfli / ZopfliPNG :

WebP :