Mesures de compression d'images : dix fixtures dans le navigateur

Dix fixtures, cinq mesures chacune, un navigateur. Ce sont des mesures d'encodeur prises sur le chemin de travail réellement publié, y compris les cas où le résultat ne nous flatte pas.

Publié par tinyimg. Le harnais, les fixtures et les fichiers de résultats sont dans le dépôt : la méthode et les chiffres sont donc vérifiables.

Qu'a-t-on mesuré, et comment ?

Dix cas issus de sept fichiers, cinq mesures par cas : le benchmark mesure le temps de compression et la taille de sortie. La photo de 12 MP est encodée en JPEG, WebP et AVIF, et la capture de 2 MP en PNG et WebP.

Le harnais emprunte le chemin publié. Il importe src/lib/pool.js et src/lib/worker.js — le même pool et le même worker que l'application pour JPEG, PNG, WebP, AVIF et JXL. Pour MotionPhoto, il importe src/lib/motion-photo/pipeline.js, le chemin que l'application suit pour ce format ; les MotionPhoto ne passent pas non plus par le pool en production. Il n'y a pas de compresseur réservé au benchmark ni de seconde implémentation qui pourrait diverger.

Chaque cas tourne une fois en échauffement, puis cinq fois en mesure. Les cas sont exécutés en séquence, donc ils ne se disputent pas le pool de quatre workers. Le temps reporté est la médiane des cinq échantillons. Le p95 utilise le rang le plus proche : avec cinq échantillons, c'est l'exécution la plus lente — sans interpolation, et sans prétendre que cinq échantillons décrivent la queue.

Le navigateur est Chrome 155 sans interface, avec un profil temporaire neuf à chaque exécution ; les extensions et le réseau en arrière-plan sont désactivés. La page est servie depuis localhost, et le chronomètre part une fois le pool et les modules WASM prêts. L'exécution n'active volontairement pas l'isolation cross-origin : cela ferait rapporter des threads par wasm-feature-detect et basculerait oxipng sur sa compilation threadée, qui n'est pas le chemin de production.

La machine est un Apple M4 à 10 cœurs et 16 Go de mémoire, sous macOS arm64. Le navigateur annonce 10 threads matériels ; l'application plafonne son pool à quatre workers.

Quels fichiers ont été testés ?

Sept fichiers, d'un JPEG de 0,5 MP à une photo de 24 MP, plus un MotionPhoto de 12 MP.

FichierPixelsEntréeCe qu'il représente
photo-12mp.jpg4000×30004.92 MBPhoto détaillée
shot-2mp.png1920×10801.36 MBCapture d'interface avec bords nets et barres imitant du texte
flat-1mp.png1000×1000118.1 KBGraphique plat et peu détaillé
alpha-4mp.png2000×20008.26 MBImage RGBA avec un vrai canal alpha
large-24mp.jpg6000×40009.66 MBPhoto de 24 MP
tiny-0.5mp.jpg800×600219.6 KBPetite image ; le coût par appel y est visible
motion-12mp.jpgImage fixe 4000×3000 + vidéo de 6 s en 1280×7209.98 MBMotionPhoto synthétique, assemblé par l'outil d'écriture du dépôt

Toutes les fixtures sont générées par ffmpeg à partir de sources intégrées. Le SHA-256 de chaque fichier est consigné dans bench/fixtures/manifest.json et vérifié avant que le navigateur ne le charge : une fixture modifiée est un échec, pas une base de référence déplacée en silence.

Quels sont les résultats ?

Dix cas, des ratios de 1,5 % à 69,3 % et des médianes de 42,0 ms à 1 983,2 ms. Le ratio est sortie ÷ entrée ; plus bas signifie plus petit. La médiane et le p95 sont le temps réel du cas complet, aller-retour vers le worker compris. ms/MP divise la médiane par le nombre de mégapixels.

Le tableau et les deux graphiques lisent le même fichier, bench/published.json (id d'exécution, date, environnement) ; en cas de désaccord, le fichier fait foi.

Taux de compression par casGraphique à barres horizontales. Le taux est la taille de sortie divisée par la taille d'entrée ; plus bas est mieux. De 1.5 % pour shot-2mp-webp à 69.3 % pour motion-12mp.Taux de compression par cas0255075photo-12mp-jpgphoto-12mp-webpphoto-12mp-avifshot-2mp-pngshot-2mp-webpflat-1mp-pngalpha-4mp-aviflarge-24mp-jpgtiny-0.5mp-webpmotion-12mp8.8%6.5%2.4%43.2%1.5%51.7%4.4%8.6%9.3%69.3%sortie ÷ entrée (%)plus bas est mieux
Plus petit : shot-2mp-webp, 1.5 % de l'original. Plus grand : motion-12mp, 69.3 %.
Temps d'encodage par mégapixelGraphique à barres horizontales de la médiane du temps d'encodage par mégapixel, dans le même ordre de cas que le graphique du taux. De 51.2 ms/MP pour large-24mp-jpg à 516.2 ms/MP pour flat-1mp-png.Temps d'encodage par mégapixel0200400600photo-12mp-jpgphoto-12mp-webpphoto-12mp-avifshot-2mp-pngshot-2mp-webpflat-1mp-pngalpha-4mp-aviflarge-24mp-jpgtiny-0.5mp-webpmotion-12mp56.483.1165.3296.273.1516.2387.951.287.5111.2ms par mégapixel (médiane de 5 exécutions)même ordre de cas que le taux
Plus rapide par mégapixel : large-24mp-jpg, 51.2 ms/MP. Plus lent : flat-1mp-png, 516.2 ms/MP.
CasFormatMPEntréeSortieRatioMédianep95ms/MP
photo-12mp-jpgJPEG → JPEG124.92 MB444.4 KB8.8%677.0733.456.4
photo-12mp-webpJPEG → WebP124.92 MB325.2 KB6.5%997.51021.783.1
photo-12mp-avifJPEG → AVIF124.92 MB121.8 KB2.4%1983.22026.8165.3
shot-2mp-pngPNG → PNG2.071.36 MB600.6 KB43.2%613.1618.1296.2
shot-2mp-webpPNG → WebP2.071.36 MB21.0 KB1.5%151.3156.173.1
flat-1mp-pngPNG → PNG1118.1 KB61.0 KB51.7%516.2562.9516.2
alpha-4mp-avifPNG → AVIF48.26 MB370.1 KB4.4%1551.71626.3387.9
large-24mp-jpgJPEG → JPEG249.66 MB846.1 KB8.6%1228.11231.051.2
tiny-0.5mp-webpJPEG → WebP0.48219.6 KB20.4 KB9.3%42.043.487.5
motion-12mpMotionPhoto129.98 MB6.92 MB69.3%1334.01377.3111.2

Que disent les chiffres ?

Sur la photo de 12 MP, AVIF est 3,65× plus petit que JPEG et 2,93× plus lent ; WebP se situe entre les deux, et PNG est l'exception sans perte à 296,2 ms/MP.

Combien AVIF est-il plus petit que JPEG ?

Environ un tiers de JPEG : 2,4 % contre 8,8 % sur la photo de 12 MP, soit 121,8 KB contre 444,4 KB. WebP produit 325,2 KB. AVIF est 3,65× plus petit que JPEG et 2,67× plus petit que WebP. C'est aussi le plus lent : 1 983,2 ms de médiane, 2,93× le temps de JPEG et 1,99× celui de WebP. Par mégapixel, 165,3 ms contre 56,4 pour JPEG et 83,1 pour WebP.

Voilà l'échange en une ligne : un peu plus du quart des octets de JPEG pour trois fois le temps d'encodage. Sur un lot de centaines d'images, ce temps est réel ; sur une seule image principale, il peut ne pas compter.

Combien WebP est-il plus petit et plus lent que JPEG ?

26,8 % plus petit et 1,47× plus lent : 325,2 KB contre 444,4 KB, et 997,5 ms contre 677,0 ms sur la même source de 12 MP. Face à AVIF, il est 2,67× plus gros et 1,99× plus rapide. Il n'est le meilleur à aucun des deux bouts ; il est au milieu dans les deux directions.

Quelle est la vitesse de JPEG ?

677,0 ms pour la photo de 12 MP (56,4 ms/MP) et 1 228,1 ms pour celle de 24 MP (51,2 ms/MP), le plus rapide des trois codecs ici. Le coût par mégapixel est presque plat, ce qui est l'allure d'un codec par blocs rapide. Sa sortie est plus grosse que celle des deux formats modernes, mais c'est le format à battre en temps, et sur cette machine aucun ne l'a battu.

Pourquoi PNG est-il le plus lent par mégapixel ?

296,2 ms/MP sur la capture de 2,07 MP, 4,05× les 73,1 ms/MP de WebP, parce que l'optimisation sans perte doit chercher des filtres et des stratégies DEFLATE pour chaque pixel. La capture a pris 613,1 ms en PNG et 151,3 ms encodée en WebP. Le PNG plat de 1 MP est pire par pixel : 516,2 ms, parce que le coût fixe de l'optimiseur est réparti sur un seul mégapixel.

PNG est sans perte. Chaque pixel est conservé, et oxipng passe son temps à chercher des filtres et des stratégies DEFLATE pour un flux plus petit ; la recherche est le but, et c'est aussi le coût. Un codec avec perte peut s'arrêter quand la qualité cible est atteinte. Un optimiseur sans perte doit établir qu'il ne reste rien à retirer, et le nombre de combinaisons croît avec l'image.

L'écart de taille est tout aussi direct : la capture pèse 600,6 KB en PNG et 21,0 KB en WebP. Ce ne sont pas des sorties équivalentes — l'une est sans perte, l'autre non — mais elles expliquent pourquoi PNG est le bon choix pour un schéma que vous allez modifier et le mauvais pour une photo que vous allez publier.

Un niveau oxipng plus élevé donne-t-il toujours un PNG plus petit ?

Non : le niveau 3 était 9,6 à 13,6 % plus petit sur les trois captures, mais n'a économisé que quatre octets sur le PNG photographique de 8,29 MP, pour 8,2× le temps. Le niveau PNG a été mesuré séparément avec bench/png-levels.mjs, sur les mêmes fixtures, avec un échauffement et cinq exécutions par niveau. Sur les trois fixtures de capture (2,07, 8,29 et 15,73 MP), le niveau 3 a pris 7,8 à 8,5× le temps du niveau 1 et produit des fichiers 9,6 à 13,6 % plus petits. Sur le PNG photographique de 8,29 MP, il a produit quatre octets de moins que le niveau 1 — 15 474 918 contre 15 474 922 — pour 8,2× le temps. Sur l'image RGBA, il a économisé 2,0 % pour 7,1× le temps. La recherche supplémentaire n'achète presque rien quand l'image est déjà photographique.

C'est pourquoi le worker publié utilise désormais le niveau 2 par défaut et descend au niveau 1 au-dessus de 4 MP. À 16 MP, le niveau 2 était 1,0 % plus gros que le niveau 1 et environ deux fois plus lent : un niveau plus élevé n'est donc pas automatiquement un fichier plus petit. Le coût du changement est visible sur les PNG plats et peu détaillés : la fixture plate de 1 MP pèse 62 501 B au niveau 2 contre 48 138 B au niveau 3 — 29,8 % de plus — mais s'encode en 437,9 ms au lieu de 2 028,3 ms, 4,6× plus vite. Les captures paient moins : à 2,07 MP, le fichier de niveau 2 est 7,2 % plus gros que celui de niveau 3 et 4,1× plus rapide. Le site a choisi le défaut plus rapide et accepte les octets.

MotionPhoto compresse-t-il aussi la vidéo ?

Non : seule l'image fixe est réencodée, et la vidéo est copiée octet par octet ; la fixture synthétique de 12 MP a donc baissé de 30,7 % alors que la moitié n'a jamais été touchée. Un MotionPhoto est un seul fichier : une image JPEG fixe avec une vidéo ajoutée, et sur certains téléphones une gain map. Le chemin publié décode et réencode l'image fixe, puis reconstruit le conteneur autour d'elle. Elle n'est ni décodée, ni réencodée, ni proposée comme cible. La gain map, lorsqu'elle existe, est aussi copiée, et son décalage absolu dans le MPF est repointé pour que l'affichage HDR continue de fonctionner.

Le ratio du fichier complet est donc dominé par la part que représente la vidéo. Dans la fixture synthétique de 12 MP, l'image fixe occupe 5 156 418 B sur 10 463 084 B d'entrée (49,3 %) et la vidéo 5 306 666 B (50,7 %) ; il n'y a pas de gain map. L'image fixe sort à 1 944 569 B, soit 62,3 % de moins, et le fichier complet passe de 9,98 MB à 6,92 MB — 30,7 % de moins. L'économie de l'image fixe est importante ; celle du fichier est limitée par la moitié qui n'a jamais été touchée.

La vérification sur échantillon réel utilise un MotionPhoto d'un OnePlus 13 : image fixe 7 140 978 B (44,7 %), vidéo 7 968 544 B (49,9 %), gain map 848 042 B (5,3 %), total 15 957 564 B. Après le chemin image fixe seule : image 1 862 140 B (73,9 % de moins), vidéo et gain map identiques octet par octet, total 10 712 466 B — de 15,96 MB à 10,71 MB, 32,9 % de moins. Le fichier reconstruit a été reconnu comme MotionPhoto et lu sur le téléphone. C'est le chemin de la gain map vérifié sur du matériel réel, pas déduit de la fixture synthétique.

Quelles sont les limites ?

Six réserves, à commencer par les fixtures synthétiques et une seule machine.

Comment reproduire ces chiffres ?

Cinq commandes, de npm ci à npm run bench.

npm ci
npm run build
npm run bench:fixtures
npm run bench:build
npm run bench

L'exécution écrit un JSON dans bench/results/ et imprime le tableau ci-dessus sur stdout. La comparaison des niveaux PNG est npm run bench:png-levels. Les fixtures sont vérifiées contre bench/fixtures/manifest.json avant chargement par le navigateur ; un fichier modifié est un échec dur, pas une nouvelle base silencieuse.

Questions fréquentes

Quel format compresse le plus ?

AVIF : sur la photo de 12 MP, il produit 121,8 KB, soit 2,4 % de l'entrée, contre 6,5 % pour WebP et 8,8 % pour JPEG.

Quel format encode le plus vite ?

JPEG : 677,0 ms pour la photo de 12 MP, contre 997,5 ms pour WebP et 1 983,2 ms pour AVIF.

Pourquoi PNG est-il plus lent par mégapixel ?

Parce qu'il est sans perte : oxipng cherche des filtres et des stratégies DEFLATE pour chaque pixel. La capture de 2,07 MP prend 296,2 ms/MP en PNG et 73,1 ms/MP en WebP, 4,05× moins.

MotionPhoto compresse-t-il la vidéo ?

Non. La vidéo est copiée octet par octet et seule l'image fixe est réencodée ; dans la fixture synthétique de 12 MP, l'image fixe baisse de 62,3 % et le fichier complet de 30,7 %, de 9,98 MB à 6,92 MB.

Quel est l'effet du niveau 2 par rapport au niveau 3 sur la taille du PNG ?

Le niveau 3 est plus petit mais beaucoup plus lent. Le PNG plat de 1 MP pèse 62 501 B au niveau 2 contre 48 138 B au niveau 3, soit 29,8 % de plus, mais s'encode en 437,9 ms au lieu de 2 028,3 ms.

Combien d'exécutions par cas ?

Un échauffement et cinq exécutions mesurées ; le tableau donne la médiane de ces cinq, et le p95 est l'échantillon le plus lent.

À propos de cet article

Publié par tinyimg. Le harnais, le générateur de fixtures et les fichiers de résultats sont dans le dépôt, et le JSON brut de cette exécution est écrit dans bench/results/. La signature est le projet, pas une personne : ce qui est affirmé ici n'est pas une autorité, mais le fait que la méthode est écrite et que les chiffres peuvent être reproduits.

← Retour à tinyimg

en · zh-CN · ja · ko · es · fr · ru · ar