Comment Apple a créé un codec d'animation en JPEG

Cela a commencé par une discussion avec un ami lors du réveillon du 31 décembre, qui, après trois cocktails, me soutenait encore qu’il y avait du Flash sur le site d’Apple. Ma curiosité piquée et quelques recherches plus tard, je vous propose un article en forme de clin d’œil, décalé, sur le nouveau standard HTML5 de la vidéo qui pourrait être malmené par le “codec” d’animation JPG créé en Javascript par Apple pour son site.

Avec le HTML5 et son élément  le multimédia est censé avoir enfin trouvé sa place. Alors pourquoi ne pas en profiter pour ajouter quelques animations bien sympathiques dans les pages Web en se passant de Flash. Voyez par exemple cette page du site d’Apple : animation du « Unlock » de l’iPhone 5, animation des écouteurs en mode Quicktime VR (manipulables à la souris), HTML5 fait la preuve de sa dimension multimédia tant attendue.

Sauf que les choses ne sont ni aussi triviales, ni aussi évidentes : un click droit sur la zone des écouteurs ou sur la zone de l’iPhone à « unlocker » fait apparaître que l’élément est en fait une simple image. Étonnant. Un coup d’œil sur le code source HTML et le DOM en mémoire ne montre aucune balise vidéo, ni flux vidéo chargé. Mais un élément existe. Pourquoi Apple, créateur du H.264, ne l’utilise pas et ne met pas de vidéo sur cette page de son site ? Comment ont-ils pu contourner cette contrainte ?

Codecs… Ho… Nan !

Entrons dans les histoires de codecs vidéo. Il existe 3 grands codecs vidéos qui concernent les navigateurs ; deux sont «gratuits», Ogg Theora et WebM, et un est soumis à de multiples brevets de la part de Apple et Microsoft: le H.264/MPEG-4. Résultat, à ce jour il n’existe pas un codec qui soit pris en charge par les principaux navigateurs (lire ici sur wikipedia). Par exemple Opera ne supporte pas H.264 quand Safari ne supporte que H.264 (sauf si utilisation de plugins) ou quand Firefox supporte H.264, mais pas sous MacOS, pendant que IE ne supporte que H.264… un vrai sac de nœud.

Dans ces conditions, difficile d’utiliser l’élément HTML5 quand on s’adresse au public le plus large. Une fois constatée cette impasse, la solution naturelle qui s’imposerait serait d’utiliser Flash. Mais on se souvient tous de la lettre de Steve Jobs  dans laquelle il livre ses réflexions sur Flash, et on imagine aisément que personne d’intellectuellement sain chez Apple n’a du se risquer à cette proposition peu corporate…

Reste donc un rare dénominateur commun pour effectuer des animations et des sorties graphiques maîtrisées: l’élément associé à des images. Une vidéo n’est-elle pas une succession d’images ? En téléchargeant N images puis en effectuant une copie de ces images sur le canvas à intervalle régulier, on arrive au résultat escompté. Oui mais… Oui mais rapidement le téléchargement peut devenir conséquent, à raison de 25 images/sec… Solution viable, donc, mais coûteuse en terme de bande passante. Reste finalement une seule solution cohérente dans l’esprit d’Apple: écrire son « codec » vidéo en Javascript. Un codec qui doit être simple à manipuler, qui ne doit pas dégrader l’image (à la différence de ce que fait le DivX par exemple) et qui doit rester efficace quel que soit le navigateur, depuis IE7 (il existe de quoi émuler le ) jusqu’à iOS, Chrome ou Opera. Pour un puriste le terme « codec » n’est peut-être pas exact mais il reste conforme dans le contexte, on s’en contentera.

Sans rentrer dans les détails, examinons de plus près comment Apple s’y est pris pour faire en javascript une animation optimisée à partir d’images JPG.

Codec, la ration en Javascript et JSON

Nous pendrons comme exemple le « Unlock » animé vu précédemment sur le site d’Apple. L’idée de base du codec est très simple : on part d’une image initiale clé I1 (une « keyframe » en matière de vidéo), à laquelle on applique ensuite la différence nécessaire pour atteindre l’image suivante I2. On procède ainsi par récurrence jusqu’à atteindre l’image finale de l’animation qui est aussi implémentée comme une keyframe. En observant les images chargées par le navigateur on découvre ainsi:

  • unlock_keyframe.jpg
  • unlock_001.jpg,
  • unlock_002.jpg ,
  • unlock_endframe.jpg.

Techniquement les images unlock_001.jpg et unlock_002.jpg forment un flux continu de données pour les images intermédiaires. (je n’ai mis qu’une copie d’écran partielle, j’expliquerai plus loin pourquoi il y a deux images et pas une seule, ainsi que leurs formes carrées…).

Il nous faut maintenant savoir quelle partie du flux unlock_001 et unlock_002 il faut copier pour passer de l’image In à l’image In+1. En observant les ressources chargées par le navigateur on découvre un fichier manifest unlock_manifest.json chargé via AJAX.

Ce manifest décrit parfaitement notre animation : elle nécessite 2 images de données (imagesRequired:2, nos deux jpg unlock_NN.jpg) pour réaliser les mises à jour entre les images (frames)  et elle sera composée de 91 images (frames) intermédiaires. Quant au blockSize (blockSize:8), on verra plus tard sa signification, mais partons du principe que l’unité de mise à jour d’une image est un bloc de 8×8 pixels. Nous allons maintenant examiner les données du tableau des frames. Pour cela observons comment le codec fonctionne, bref soulevons le capot du code Javascript mis en place par Apple.

Apple fournit divers fichiers de script nommés ac_XXXX.js (pourquoi AC ? A pour Apple, C pour quoi? Codec ? Suis pas sûr) qui prennent en charge la problématique habituelle des différents navigateurs (pas de jQuery dans cette page) en mettant en œuvre un namespace AC. On trouve donc des objets AC.Element, AC.Function, AC.Object, AC.RegExp etc…

A noter que les fichiers JS sont optimisés et minimisés, donc le code vu dans le navigateur est souvent peu lisible : je recréerai quelques noms de variables pour la bonne compréhension.

Intéressons nous au fichier ac_flow.js qui est celui qui implémente le codec et sa manipulation. Il définit notamment les classes suivantes qui concernent notre exemple:

  • AC.Flow: la classe principale chargée de jouer l’animation
  • AC.Flow.Manifest: la classe qui gère le manifeste associé à l’animation
  • AC.Flow.Manifest.Keys: les infos issues du manifeste pour construire chaque image intermédiaire
  • AC.Flow.SharedMethods: les fonctions diverses

Une fois chargé via AJAX, le fichier manifest est parsé ainsi:

Une frame du fichier JSON (exemple de la frame 1) : AAxACABhABAFgABAKAABA1yACA11ABA2jADA2vABA3SADA3dACA4BAD“) est donc décomposée par bloc de 5 caractères codés en base64 : les 3 premiers caractères indiquent la position où doit avoir lieu la modification sur le canvas cible (location), les 2 suivants le nombre de blocs à lire pour la mise à jour (length). “AAxAC” s’interprète donc ainsi : lire 2 blocs (AC) à la position actuelle dans le flux (unlock_NN.jpg) et effectuer la mise à jour sur le canvas en position 49 (AAx). Simple et efficace. On pourra juste noter que Apple a adapté le décodage Base64, qui normalement est toujours constitué de blocs de 4 caractères pour coder 3 octets, (soit 6 bits par caractère – ce point précis est d’ailleurs respecté ici).

On anime le canvas…

Maintenant que le manifest est décodé et les blocs identifiés il reste à jouer l’animation en copiant les blocs concernés à partir du flux. Ainsi, on trouve des méthodes habituelles play, pause, showFrame etc… Elles effectuent leurs sorties sur le canvas cible. Mais la méthode la plus intéressante est __applyDiffRange chargée d’appliquer les modifications entre deux images (note : j’ai injecté quelques commentaires et noms de variables pour clarifier le code):

On retrouve les notions vues plus hauts, à savoir la lecture de n blocs à partir de l’image de diff en cours (unlock_xx) qui sont ensuite copiés vers le canvas à la position décodée à partir du manifest. Ces traitements sont visibles dans la console du navigateur:

De même, les structures Base64 des 91 frames qui renseignent sur le nombre de blocs à mettre à jour sont celles-ci, une fois réduites en taille:

Les 60 premières mises à jour sont limitées, elle concernent l’animation de la zone du “unlock”. Puis ensuite les choses s’accélèrent avec les 30 dernières mises à jour conséquentes en nombre, correspondant à l’apparition des icônes de l’iPhone et à leurs déplacements  jusqu’à ce qu’elles trouvent leur place sur l’écran et se superposent à la keyframe finale.

Si on continue à lire ce fichier JS, on note encore l’existence d’une classe AC.Flow.VR qui émule Quicktime VR (oui oui, vous lisez bien), utilisée d’ailleurs dans le cas de la rotation des écouteurs. De même on découvre que Apple a dû fournir précédemment une version de l’animation composée basiquement d’un ensemble de n images brutes enchainées à partir d’un élément ! Peut-être la première version.

Enfin on découvre que la description de l’animation actuellement dans le fichier manifest JSON est prévue aussi pour être intégrée dans un fichier image PNG, ce qui améliorerait certainement les performances pour coder/décoder les informations (plus besoin de passer par du Base64, lecture en masse).

Avant de refermer cet article, revenons sur la notion de blockSize vue dans le fichier manifest. Les images de données ont ces dimensions: unlock_01.jpg, w=1624 , h=1624 et unlock_02.jpg, w= 160 , h=160. Le format des images est du JPG. La norme JPG nous renseigne qu’un JPG est décomposé en blocs de 8×8 pixels ou 16×16 pixels. Or dans notre cas le blockSize inscrit dans le manifest est de 8 pixels : Apple a donc créé des JPG en veillant à respecter cette caractéristique de compression, sans perte de qualité, ce qui permet une relecture parfaite des pixels du block. On constate bien d’ailleurs que les images unlock_NN.jpg sont constituées de bandes de 8 pixels de haut et de largeur un multiple de 8 (1624=8x 203, 160=8×20). Astucieux.  Pour être complet, le fait que les images soient carrées simplifie les calculs (mais sans plus), et impose donc de fabriquer deux images pour optimiser la taille globale en Ko qui devra être téléchargée .

En conclusion, alors que le HTML5 semble devenir pour beaucoup un standard enfin respecté par les navigateurs, qu’il dispose des moyens modernes pour jouer des animations et gérer l’interactivité souhaitée, Apple nous prend à contre-pied et nous montre si besoin était, que le chemin sera encore long avant de pouvoir fédérer définitivement les acteurs.

En attendant, les animations sont faites à partir d’images JPG !