Format Quite OK Image (QOI) : simple, rapide, sans perte, mais pas sans défauts

Format Quite OK Image (QOI) : simple, rapide, sans perte, mais pas sans défauts

Basique, simple

Avatar de l'auteur
David Legrand

Publié dans

Logiciel

09/12/2021 4 minutes
24

Format Quite OK Image (QOI) : simple, rapide, sans perte, mais pas sans défauts

Depuis quelques jours, un projet open source fait parler de lui dans le domaine de la (dé)compression d'images : le format QOI, présenté comme très rapide et sans perte. De quoi inciter certains développeurs à l'implémenter dans différents langages. Mais en pratique, il vaut quoi ?

Fin novembre, Dominic Szablewski présentait un projet de format de (dé)compression d'images sans perte aux objectifs simples : un code léger (300 lignes de C), efficace, n'exploitant qu'un thread, n'effectuant qu'une seule passe, sans SIMD. « QOI ne touche chaque pixel qu'une seule fois, ils sont encodés d'une parmi quatre manières ».

Le résultat est présenté comme 20x à 50x plus rapide que des bibliothèques comme libpng ou stb pour la compression, 3x à 4x en décompression. Le tout avec une taille de fichier similaire. Le code source est disponible sous licence MIT, le développeur précisant que le projet n'est pas encore finalisé, bien que déjà fonctionnel.

D'autres sont d'ailleurs déjà nés de cette initiative, comme qoiview pour la visualisation d'images mais aussi des implémentations en C#, Elixir, Go, Python, Rust (1, 2, 3), Swift et même le nouveau langage à la mode : zig.

Téléchargement, compilation

Pour vérifier les chiffres annoncés par le développeur et le fonctionnement de QOI, nous avons lancé une machine virtuelle sous Debian (via Proxmox VE 7.1) avec 1 cœur de notre Xeon E-2388G et 1 Go de mémoire. L'installation était minimale, nous avons donc dû récupérer plusieurs applications pour télécharger et compiler le code source :

sudo apt update && sudo apt upgrade
sudo apt install build-essential curl git libpng++-dev

On récupère le code source de qoi et les images de test :

git clone https://github.com/phoboslab/qoi.git
curl -O https://phoboslab.org/files/qoibench/images.tar

On décompresse l'archive TAR contenant les images :

tar xf images.tar

On récupère les headers nécessaires pour l'utilisation de stb :

cd qoi
curl -LO https://github.com/nothings/stb/raw/master/stb_image.h
curl -LO https://github.com/nothings/stb/raw/master/stb_image_write.h

On compile les outils de mesure de performances et de conversion selon les recommandations du développeur :

gcc qoibench.c -std=gnu99 -lpng -O3 -o qoibench
gcc qoiconv.c -std=c99 -O3 -o qoiconv

Quels résultats ?

On peut alors lancer les tests avec 10 itérations sur chaque image que contient le dossier :

./qoibench 10 ../images/wallpaper/

Cela nous a donné les performances suivantes en moyenne : 

        decode ms   encode ms   decode mpps   encode mpps   size kb
libpng: 115.5 1619.0 81.15 5.79 9224
stbi: 122.7 1041.6 76.40 9.00 13299
qoi: 47.2 53.7 198.61 174.46 10640

On relève bien des écarts de 20x à 30x pour la compression et une division par un peu plus de deux pour la décompression. Le tout avec des fichiers légèrement plus gros par rapport à libpng, plus légers que via stbi. Ce qui est annoncé se vérifie donc bien en pratique. Mais comment vérifier que l'opération est bien sans perte ?

Taille des fichiers, le compte n'y est pas toujours

Pour le savoir, nous avons pris l'un des fonds d'écran (PNG) pour le convertir via qoiconv au format QOI avant de faire le chemin inverse et de retrouver une image au format PNG :

./qoiconv ../images/wallpaper/Hy23XKX.png Hy23XKX.qoi
./qoiconv Hy23XKX.qoi Hy23XKX_new.png

Si tout s'est bien passé, les deux fichiers PNG doivent être identiques, au pixel près. Et c'est effectivement le cas, ce qui est plutôt une bonne nouvelle.

Il y a néanmoins un bémol : le PNG calculé depuis le QOI pèse 9,58 Mo contre 6,38 Mo pour le fichier originel. Si le QOI pèse 7,47 Mo, la conversion inverse se fait avec une inflation importante. Un point à perfectionner.

Mais l'essentiel semble là puisque l'on a bien un format sans perte, plus rapide à (dé)compresser que les solutions concurrentes. Il sera donc intéressant de voir s'il est utilisé pour des usages particuliers où la performance est importante, malgré le besoin d'une qualité d'image non dégradée. L'avenir nous le dira.

Écrit par David Legrand

Tiens, en parlant de ça :

Sommaire de l'article

Introduction

Téléchargement, compilation

Quels résultats ?

Taille des fichiers, le compte n'y est pas toujours

next n'a pas de brief le week-end

Le Brief ne travaille pas le week-end.
C'est dur, mais c'est comme ça.
Allez donc dans une forêt lointaine,
Éloignez-vous de ce clavier pour une fois !

Fermer

Commentaires (24)


Il ne vaut donc mieux pas faire des cycles de décompression / compressions au risque de se trouver avec un fichier qui passe de qq Mo a qq Go? :transpi:


Non, tu restes à la même taille du PNG si tu refais la conversion. Je pense que c’est juste la conversion >PNG qui se fait de manière “inefficace” dans le script de conversion mais je n’ai pas regardé où ça pouvait être et si on peut résoudre le problème simplement.



Mais sur le fond, ça ne change rien à l’efficacité de la (dé)compression QOI



Attention à un point : le web et les navigateurs ne sont pas l’alpha et l’oméga. Le RAW n’est pas adapté au web, il existe très bien et est largement utilisé néanmoins ;)



Ici, ça peut être une solution très utile quand tu as un besoin d’efficacité dans le processus de (dé)compression sans perte. Par exemple pour GIMP qui enregistre un fichier on s’en fout. Par contre pour un script qui traite des millions d’images à la minute, un délai divisé par 20 ça peut faire une sacré différence.


Un format c’est comme un réseau social, ce qui compte c’est son adoption.



Pour les images la référence c’est un peu le web, si les navigateurs s’y mettent le format se démocratisera, sinon, non.


Donc a priori beaucoup moins de CPU mais un peu plus de mémoire nécessaires ?


J’ai pas relevé de gourmandise particulière là-dessus, mais il faudrait faire un benchmark spécifique pour le vérifier avec précision.


Côté mémoire, je parlais du côté du stockage, pas du calcul (le fichier décompressé étant plus gros que l’original).


Oui après quand tu fais le ratio entre la différence de taille et le temps CPU gagné… :D


David_L

Oui après quand tu fais le ratio entre la différence de taille et le temps CPU gagné… :D


Et dans les usages où il serait diablement pratique, il y a tout ce qui est client léger, jeu vidéo, cartographie embarquée (où les supports de masse ne valent plus rien, mais les procs peuvent encore peiner….)



Dans les tests, je serais curieux de savoir ce que donne une tentative de compression d’une grosse image en bruit blanc, le truc par définition le plus casse-burne à compresser voire, le plus mystique)


Pour le fait que le PNG ait “enflé” après reconversion, c’est peut-être du fait le la lib stb. La fonction utilisée pour l’écriture de PNG (stb_image_write) n’a pas l’air d’être optimisée (ce qu’on peut lire dans son header https://github.com/nothings/stb/blob/master/stb_image_write.h)


Je me suis posé la même question.
Le plus simple pour vérifier serait de reprendre le fichier “décompressé” (aka enflé), et refaire :
compression QOI -> décompression QOI
Et voir si il a re-enflé.



David_L a dit:


Attention à un point : le web et les navigateurs ne sont pas l’alpha et l’oméga. Le RAW n’est pas adapté au web, il existe très bien et est largement utilisé néanmoins ;)




Notons tout de même que RAW n’est pas un format à proprement parler. Chaque constructeur d’appareils photos a le sien, et même parfois différents suivant les modèles. Adobe a bien tenté d’harmoniser ça avec le format ouvert DNG, mais ça n’a pas pris tant que ça…



David_L a dit:


Non, tu restes à la même taille du PNG si tu refais la conversion. Je pense que c’est juste la conversion >PNG qui se fait de manière “inefficace” dans le script de conversion mais je n’ai pas regardé où ça pouvait être et si on peut résoudre le problème simplement.




Faire une passe de optipng pourrait valoir le coup pour savoir si c’est “juste” ça.




setaou2 a dit:


Notons tout de même que RAW n’est pas un format à proprement parler. Chaque constructeur d’appareils photos a le sien, et même parfois différents suivant les modèles. Adobe a bien tenté d’harmoniser ça avec le format ouvert DNG, mais ça n’a pas pris tant que ça…




Sachant que le RAW est sensé représenter la mosaïque du capteur directement (avec d’autres indications), c’est normal que ça change d’une marque à l’autre voir même d’un modèle à l’autre. Ca dépend juste du capteur :)


Je vois bien l’utilité pour de l’embarqué.
Par exemle j’ai fais un cadre photo avec epaper et un esp8266. J’y transfère les images en png via bluetooth. Si ce format permet un tel gain de place, ca veux dire transfert bien plus rapide ! Reste à voir si l’utilisation cpu est raisonable…


Je comprends l’intéret d’une unique passe par pixel pour être le plus léger/rapide possible. Par contre, quel est l’intéret de ne pas avoir de SIMD?


Sans doute montrer que la performance ne vient pas d’une optimisation particulière dépendante de telle ou telle architecture


David_L

Sans doute montrer que la performance ne vient pas d’une optimisation particulière dépendante de telle ou telle architecture


Ah, je comprends le point de vue. Dans ce cas, il faudrait enlever le -O3 lors de la compilation, gcc va essayer d’auto-vectoriser le code.


Roy_974

Ah, je comprends le point de vue. Dans ce cas, il faudrait enlever le -O3 lors de la compilation, gcc va essayer d’auto-vectoriser le code.


Comme dit dans l’article ces lignes de commande ne sont pas mon choix mais ceux recommandés dans les fichiers, l’idée étant simplement de reproduire les différentes étapes et de voir si on arrive au même résultat. Après il serait intéressant de regarder ce que l’on obtient avec plus ou moins d’optimisation du compilateur & co, mais ça peut se faire dans un second temps (ou quand le projet aura un peu plus avancé).


David_L

Comme dit dans l’article ces lignes de commande ne sont pas mon choix mais ceux recommandés dans les fichiers, l’idée étant simplement de reproduire les différentes étapes et de voir si on arrive au même résultat. Après il serait intéressant de regarder ce que l’on obtient avec plus ou moins d’optimisation du compilateur & co, mais ça peut se faire dans un second temps (ou quand le projet aura un peu plus avancé).


Effectivement, la documentation du code préconise -O3. J’ai compilé le code pour voir si certaines boucles étaient vectorisées. Aucune! Donc ça reste bien 1 thread, une seule passe et sans SIMD.
En bonus, j’ai le benchmark pour un Xeon CPU E5-2698 v3 :



## Totals (AVG) size: 0x0
decode ms encode ms decode mpps encode mpps size kb
libpng: 178.4 3123.4 52.54 3.00 9224
stbi: 211.6 1510.3 44.30 6.21 13299
qoi: 67.2 80.1 139.49 116.95 10640


(quote:1917377:Watom!)
Je vois bien l’utilité pour de l’embarqué. Par exemle j’ai fais un cadre photo avec epaper et un esp8266. J’y transfère les images en png via bluetooth. Si ce format permet un tel gain de place, ca veux dire transfert bien plus rapide ! Reste à voir si l’utilisation cpu est raisonable…




Le gain est sur la compression, donc ça ne servira pas à grand chose dans ce cas. Attention également compression 20x plus rapide ne veut pas dire que ça consomme 20x moins : Le CPU est peut-être simplement mieux gavé (donc il consomme plus mais moins longtemps)



David_L a dit:


Attention à un point : le web et les navigateurs ne sont pas l’alpha et l’oméga. Le RAW n’est pas adapté au web, il existe très bien et est largement utilisé néanmoins ;)




Un peu quand même, au hasard jpeg2000. Format ouvert largement supérieur au jpeg classique. L’absence de support des navigateurs a tué ce format qui avait pourtant un intérêt de dingue en 2000 où les JPEG et GIF faisaient bien ramer nos 56k avec leur embonpoint :)


Je suis agréablement surpris de voir un article sur le sujet. C’est un format qui semble extrèmement intéressant pour les bidouilleurs avec des petits microcontrôleurs qui veulent une compression d’image sans devoir se taper un format imbuvable et incompréhensible.
Car c’est un peu ce qui est reproché aux autres formats : même ceux où la spécification est ouverte sont complexes à mettre en œuvre sur un microcontroleur.
Quand il faut inclure une librairie de 2Mo sur 256ko de mémoire dispo, ça pose vite des petits problèmes !


Je me fais un peu de pub, mais si vous compressez le fichier QOI avec XZ, vous risquez de gagner beaucoup d’octets par rapport à un PNG, même optimisé avec zopflipng : https://github.com/phoboslab/qoi/issues/19



Alors évidemment, ce ne sont plus les mêmes performances… Mais ça fait assez longtemps que je compresse des images pour être surpris de ce résultat.


Normalement une compression de qualité est optimale et rend les données pseudo-aléatoires (entropie élevée, si je ne dis pas de bêtise), et une recompression (quel que soit l’algorithme) ne donne pas grand chose voire augmente légèrement la taille.


OlivierJ

Normalement une compression de qualité est optimale et rend les données pseudo-aléatoires (entropie élevée, si je ne dis pas de bêtise), et une recompression (quel que soit l’algorithme) ne donne pas grand chose voire augmente légèrement la taille.


Mais QOI ne prétend pas être optimal, mais vise la rapidité. Et il se trouve que dans beaucoup de cas, les données du format QOI sont bien alignés pour du LZMA, mieux qu’un PNG en tout cas.