Comme vous le savez peut-être, chez Wanadev on travaille sur beaucoup de projets WebGL ; la conversion et l'optimisation d'assets sont donc des problématiques récurrentes. Il existe bien sûr de nombreux outils et bibliothèques pour cela, mais aucun ne répondait à l'ensemble de nos besoins et ne permettait d'automatiser simplement cette tâche. C'est ainsi que commença le projet Yummy Optimizer for Gorgeous Assets, plus couramment nommé YOGA.

Logo de YOGA

Qu'est-ce que YOGA exactement ?

YOGA est à la fois un utilitaire en ligne de commande et une bibliothèque Python permettant :

  • De convertir, redimensionner et optimiser des images. Il supporte une large variété de formats en entrée, et prend actuellement en charge les formats JPEG et PNG en sortie.

  • De convertir et d'optimiser des modèles 3D. Là encore, il supporte une large variété de formats en entrée, et en sortie il propose les formats glTF et GLB.

YOGA est open source, vous pouvez donc l'utiliser librement dans vos projets. N'hésitez pas à jeter un coup d'œil à la page Github du projet si vous souhaitez l'utiliser ou contribuer :

Développement du projet

Notre histoire commence fin 2017. Depuis quelque temps nous commencions à utiliser le tout récent format glTF 2.0 pour nos modèles 3D. Il s'agit d'un format standardisé par le Khronos Group, les mêmes qui développent les normes OpenGL et WebGL. Il a pour avantage d'être normalisé, relativement simple et adapté au Web.

Pour être un peu plus spécifiques, nous nous intéressions surtout au format GLB, la version binaire du glTF 2.0, qui offre pas mal d'avantages :

  • comme il s'agit d'un format binaire, il produit des fichiers moins volumineux que le glTF qui est basé sur du JSON,

  • il permet d'intégrer des textures directement en binaire à l'intérieur du même fichier plutôt que de référencer d'autres fichiers (qu'il faudra alors télécharger par la suite) ou d'intégrer les images en base64 dans le JSON (ce qui est assez peu compatible avec une taille de fichier réduite).

Nous avions donc besoin d'un outil avec le cahier des charges suivant :

  • Permettre de convertir divers formats 3D vers GLB,
  • Si possible, permettre d'optimiser certaines choses sur les modèles (comme supprimer les maillages inutiles),
  • Permettre de convertir les formats d'images utilisés pour les textures (on est sur le Web, il faut donc utiliser des formats d'images supportés par les navigateurs, comme JPEG et PNG),
  • Permettre de redimensionner les textures (on a rarement besoin de textures 4K sur le Web, elles ne seraient de toute façon pas supportées par le PC de la plupart des utilisateurs),
  • Optimiser la compression des images en sortie (donc nos JPEGs et nos PNGs).

Nous n'avons, à l'époque, trouvé aucun outil permettant de faire tout cela facilement et de manière automatisable... Et à vrai dire, on avait trouvé assez peu d'outils prenant en charge le GLB... C'est pourquoi Alexis et moi-même nous sommes lancés dans le développement de YOGA.

Il a été décidé dès le départ que ce projet serait open source : on s'appuie en effet sur le travail d'énormément de monde, que ce soit à travers des spécifications ou des bibliothèques elles-mêmes open source ; ça nous semblait donc évident de partager notre travail avec la communauté et bien sûr de contribuer aux projets que nous allions utiliser.

Choix du langage de programmation

Pour développer YOGA, nous avons choisi d'utiliser le langage Python. Plusieurs raisons à cela :

  • Python est un langage très pratique lorsqu'il s'agit de manipuler des données,
  • il dispose d'une bonne communauté et de nombreuses bibliothèques de qualité,
  • il s'interface très facilement avec du C et du C++ (ce qui tombe bien puisque sous le capot, nous allons faire appel à des bibliothèques écrites dans ces langages-là),
  • pour l'un de nos projets, nous voulions pouvoir appeler les fonctionnalités de YOGA directement depuis des workers, eux-mêmes codés en Python. Ces workers prennent en charge la conversion et l'optimisation automatique des modèles 3D importés sur ce projet.

Au-delà du choix de Python, ce projet nécessitera tout de même l'écriture de certaines parties en C pour s'interfacer avec les bibliothèques dont nous allons parler par la suite.

Conversion et optimisation des images

Commençons par parler de la partie « Image » de YOGA.

Pillow : décodage et manipulation des images d'entrées

Avant de vouloir optimiser des images, il faut être en mesure de les ouvrir. Ici on n'a pas cherché bien longtemps : on a utilisé Pillow, la bibliothèque de manipulation d'images de référence en Python.

Pillow supporte énormément de formats d'images en entrée, je vous mets la liste ci-dessous pour que vous puissiez le constater par vous-mêmes :

BMP, EPS, GIF, ICNS, IM, JPEG, JPEG 2000, MSP, PCX, PNG, PPM, SPIDER, TIFF, WebP, XBM, CUR, DCX, FLI, FLC, FPX, GBR, CD, ICO, IMT, IPTC/NAA, MCIDAS, MIC, MPO, PCD, PSD (2.5, 3.0), SGI, TGA, WAL, XPM, PALM, PDF, XV Thumbnails

Ce sont donc les formats que YOGA supportera en entrée.

Optimisation des images

Maintenant qu'on sait ouvrir la plupart des formats d'image courants, parlons de l'optimisation. Pillow sait bien évidemment enregistrer des images dans les formats qui nous intéressent, mais il utilise pour cela des bibliothèques classiques comme libjpeg et libpng, qui ne fournissent pas la compression la plus efficace possible.

Ces bibliothèques sont en effet programmées avec d'autres contraintes que les nôtres : elles doivent certes essayer de sortir des fichiers les plus petits possibles, mais en conservant un temps de traitement et une empreinte mémoire raisonnable.

Comme vous vous en doutez, l'optimisation à l'extrême de la compression des images a un coût : ça prend du temps, et ça consomme beaucoup de CPU et de RAM. Dans notre cas il ne s'agit toutefois pas d'un problème majeur : on effectue l'optimisation qu'une seule fois lors de l'importation de l'asset au projet ; nos contraintes seraient tout autres si nous devions effectuer cette opération de manière répétée.

Guetzli : l'encodeur JPEG optimal

Au tout début du projet, nous pensions partir sur MozJPEG pour compresser les JPEG. Il s'agit d'une bibliothèque développée par Mozilla réduisant la taille des fichiers de 2% à 10%, à qualité visuelle équivalente, par rapport à une image encodée avec libjpeg.

Mais en creusant davantage le sujet, nous sommes tombés sur Guetzli (oui, c'est imprononçable, et au cas où vous vous le demanderiez, ça signifie « biscuit » en Suisse allemand... 😅️), un tout nouvel encodeur JPEG développé par Google. Cette bibliothèque promet une réduction moyenne de 35% du poids des JPEGs par rapport à un encodage effectué par libjpeg, et ce pour une qualité visuelle souvent meilleure !

Voici un exemple tiré de l'article de blog de recherche sur l'intelligence artificielle de Google :

Exemple de compression avec Guetzli #01

À gauche, l'image originale, sans aucune compression à perte, au centre l'image compressée avec un algorithme classique (tel que celui de la libjpeg), et à droite l'image compressée avec Guetzli.

On peut constater que cette dernière affiche des artefacts de compression moins marqués, alors que dans le même temps le fichier généré est plus petit (Google ne fournit cependant aucun chiffre pour cet exemple en particulier).

Nous avons donc décidé de tester cette bibliothèque et finalement de l'intégrer à YOGA. Il y avait cependant un petit souci : Guetzli est développée en C++, et comme ce projet était tout récent, personne n'avait encore effectué le travail nécessaire pour permettre son utilisation en Python...

Nous avons donc développé PyGuetzli, un binding Python pour la bibliothèque Guetzli. Vous retrouverez bien sûr ce projet sur Github et sur PyPI.

ZopfliPNG : optimisation des PNG sur plusieurs aspects

Pour bien saisir les leviers d'optimisation dont nous disposons pour le format PNG, il faut nous y intéresser d'un peu plus près ainsi qu'aux différentes étapes nécessaires à l'encodage d'une image dans ce format.

Le format PNG est un format de compression d'image sans perte. Il supporte divers modes de couleurs, de 1 bit par pixel (image monochrome) à 48 bits par pixel, avec ou sans transparence (canal alpha), et peut également utiliser un mode de couleurs indexées, c'est-à-dire basées sur une palette allant jusqu'à 256 couleurs.

L'encodage d'une image en PNG se fait en 2 temps :

  • le filtrage
  • et la compression.

L'étape de filtrage va tenter de définir la meilleure façon de décrire l'image tout en prenant le moins de place possible. Pour imager un peu tout ça (#budumTsss 🥁️), plutôt que d'écrire « là il y a un pixel vert, suivit d'un autre pixel vert, et d'encore un autre pixel vert » on pourrait écrire directement « là il y a 3 pixels verts ». Plutôt que de répéter les informations de 2 lignes identiques, on pourrait juste se contenter d'écrire que la ligne courante est identique à la précédente. Bon c'est évidemment un peu plus compliqué que ça en pratique mais vous voyez le principe 😉️... Cette étape est celle qui aura le plus gros impact sur la taille du fichier final.

Une fois l'étape de filtrage achevée, les données qui en sortent sont compressées avec l'algorithme deflate, le même que celui utilisé par gzip et par zlib.

Zopfli (oui, c'est encore un nom un peu étrange, qui dérive de Butterzopf qui est une brioche tressée suisse... décidément... 😂️) s'attaque à cette seconde étape, et permet d'augmenter la compression deflate de 3,7% à 8,3%. C'est déjà un bon début, mais comme je l'ai dit un peu plus tôt, c'est sur l'étape de filtrage qu'il y a le plus à gagner... Et c'est là qu'entre en jeu ZopfliPNG.

ZopfliPNG va commencer sélectionner le bon mode de couleur en fonction de l'image, supprimer des informations inutiles (typiquement les métadonnées et le canal alpha si aucune transparence n'est utilisée) et travailler à fournir le résultat le plus optimal possible lors de l'étape de filtrage grâce à divers algorithmes. Une fois tout ceci fait, il terminera en compressant les données obtenues à l'aide de Zopfli.

C'est grâce à la prise en compte de tous ces aspects (choix du bon mode de couleur, suppressions de données inutiles, optimisation du filtrage et de la compression) que ZopfliPNG est extrêmement efficace dans sa tâche. D'après mon expérience personnelle, il n'est pas rare de réduire le poids d'une image PNG de plus de 50 % avec cette bibliothèque !

Zopfli étant un peu plus ancienne que Guetzli, elle était cette fois-ci déjà disponible en Python à travers le package Zopflipy... Ça, c'était la cerise sur le gâteau la brioche ! 🍒️

Conversion des modèles 3D

Dernier élément nécessaire à notre outil : la conversion des modèles 3D. Cette fois-ci c'est vers la bibliothèque Assimp que l'on s'est tournés.

Cette bibliothèque supporte un grand nombre de formats 3D et permet de les convertir des uns vers les autres. Sur le papier ça sonne bien... Le problème, c'est que Assimp ne prenait pas totalement en charge le format glTF 2.0, et ne supportait pas du tout le format GLB... Pas de chance... Alexis s'est donc penché sur le sujet : il a implémenté la prise en charge du GLB, et effectué quelques autres contributions et corrections de bugs.

À travers Assimp, YOGA supporte aujourd'hui les formats 3D suivants en entrée :

3D, 3DS, 3MF, AC, AC3D, ACC, AMJ, ASE, ASK, B3D, BLEND, BVH, COB, CMS, DAE, DXF, ENFF, FBX, glTF 1.0 + GLB, glTF 2.0, HMB, IFC-STEP, IRR, IRRMESH, LWO, LWS, LXO, MD2, MD3, MD5, MDC, MDL, MESH, MESH.XML, MOT, MS3D, NDO, NFF, OBJ, OFF, OGEX, PLY, PMX, PRJ, Q3O, Q3S, RAW, SCN, SIB, SMD, STL, STP, TER, UC, VTA, X, X3D, XGL, ZGL

Et permet donc de générer des glTF et des GLB en sortie.

C'est cool tout ça, mais comment ça s'utilise ?

Pour utiliser YOGA il faut commencer par l'installer (sans blague 😜️), soit en passant par pip, le gestionnaire de paquets Python, soit en utilisant le binaire standalone si vous êtes sous Windows.

Ensuite c'est assez simple, YOGA fournit une ligne de commande permettant de faire tout ce dont vous avez besoin.

Par exemple pour optimiser une image PNG, vous pourrez utiliser la commande suivante :

yoga  image  input.png  output.png

Si vous souhaitez convertir une image en JPEG tout en contrôlant la qualité du résultat final, il vous faudra alors utiliser cette commande :

yoga  image  --ouput-format=jpeg  --jpeg-quality=84  input.png  output.jpg

Et pour convertir un modèle 3D, ça n'est pas plus compliqué :

yoga  model  input.fbx  output.glb

Vous retrouverez bien sûr toutes les informations utiles et divers exemples dans la documentation de YOGA.

Aujourd'hui, YOGA est utilisé sur pas mal de projets 3D / WebGL chez Wanadev, et personnellement je l'utilise régulièrement pour optimiser les images des sites sur lesquels je travaille ainsi que pour mon blog.

Nous espérons que ce projet pourra vous être utile pour optimiser vos assets, que vous travailliez sur de la 3D ou sur des sites Web. 😁️

Commentaires

Intéressant comme outil. Ça serait compliqué de l'intégrer à des softs tels que Blender par exemple ? Les graphistes n'aiment généralement point trop tâter de la ligne de commande, avoir un bouton à cliquer et des options à cocher (enfin une UI quoi ^^) faciliterait l'adoption. Enfin c'est déjà super de pratiquer l'open-source 👍🏾
Étant donné que YOGA est également une bibliothèque Python et que Blender se script en Python, il ne devrait pas être trop compliqué d'écrire un plugin Blender qui rajoute un bouton dans l'interface :)