SAASF - Aster Système Learn

Format PNG

Les bases du format PNG

Le problème du stockage d'image

Imaginez que vous ayez une image numérique quelconque à stocker. En général, l'image brute se présentera sous la forme de chaque pixel les un à côté des autres, quelque soit leut format (RGB, RGBA, nuance de gris...). Pour la stocker, il vous faut un format dans lequel stocker votre image. Le format le plus simple est le même que la façon dont votre image est présentée : chaque pixel les un à côté des autres. Cependant si votre image fait 5000 pixels sur 5000 pixels, cela représente en tout 25 millions de pixels. En partant du principe que chaque pixel soit en format RGBA et occupe 4 octets, soit 32 bits, votre image pésera 100 mégaoctets. C'est littéralement le poids d'une petite vidéo .mp4, ou d'une grande vidéo assez légère et bien traitée. Il peut être compliqué d'allouer toute cette place à une seule image, surtout à l'époque, où le stockage était beaucoup plus limité qu'aujourd'hui.

C'est le problème qu'avaient tous les développeurs de l'époque. Pour cela, l'organisation du World Wide Web Consortium, fondée par Tim Berners Lee (un des inventeurs du Web) le 1er octobre 1994, a décidé d'essayer de trouver un moyen de stocker plus efficacement les images. Le format, publié le 1er octobre 1996, est nommé "Portable Network Graphic", ou PNG.

Qu'est ce que le format PNG ?

Le format PNG est un format de stockage d'image, entièrement Open Source et sans pertes, contrairement au format JPEG. Comme son nom l'indique, il a été fait au départ pour l'échange d'images sur le Web. Il a été crée pour être extrêmement modulabe et portable. Il doit aussi offrir un système de compression fiable et efficace.

Comme tous les fichiers numérique, ce fichier est fait de binaire, donc de 0 et de 1. Plus précisement, il est composé d'octets précisement définis. Les 8 premiers octets représente la signature du fichier. Elle est exactement de : 137;80;78;71;13;10;26;10. Cette signature permet de différencier un fichier PNG d'un autre type de fichier. Tout le reste du fichier est découpé en chunks.

Qu'est ce qu'un chunk ?

Un chunk représente un bloc de données. Dans le format PNG, tous les chunks ont la même structure (heuresement) :

  • Les 4 premiers octets du chunk représentent la taille totale du chunk en octets, sans compter le nom du chunk. La représentation précise du nombre est un nombre entier de 4 octets non signé, en big endian.
  • Les 4 octets suivants du chunk représentent le nom du chunk. Ils forment une chaîne de caractère, équivalente au nom du chunk.
  • Les "taille du chunk" octets suivants du chunk représentent les données du chunk. Elles peuvent représenter beaucoup de chose, que nous verrons plus tard.
  • Les 4 derniers octets du chunk représentent le code CRC du chunk. Le code CRC est un code obtenu en passant toutes les données du chunks (sauf la taille et le code CRC du chunk) via un algorithme CRC. Nous réaliserons dans une autre page plus tard une explication de cette algorithme. En tout cas, sachez que cette algorithme utilise le pôlynome 32 bits 0x04C11DB7 (ici représenté en hexadécimal), avec les données d'entrée et de sortie inversée, une entrée 0xffffffff et un XOR final 0xffffffff.

Il existe officiellement 18 chunks. Cependant, 3 sont absoluments indispensables. Leur nom sont : IHDR, IDAT et IEND.

Le chunk IHDR

Le tout premier chunk d'un fichier PNG, situé juste après la signature, est le chunk IHDR, ou "Image Header". Il contient des informations de base sur le fichier. Sa taille est toujours de 13 octets.

Ce chunk est très précisément défini :

  • Les 4 premiers octets du chunk représentent la largeur de l'image en pixels. La représentation précise du nombre est un nombre entier de 4 octets non signé, en big endian.
  • Les 4 octets suivants du chunk représentent la hauteur de l'image en pixels. La représentation précise du nombre est un nombre entier de 4 octets non signé, en big endian.
  • L'octet suivant du chunk représente la pronfondeur de bit de l'image. La représentation précise du nombre est un nombre entier de 1 bit non signé. La profondeur de l'image est le nombre de bits qui composent une valeur d'un pixel dans le chunk. Avec le format RGB ou RGBA, la profondeur est le nombre de bits correspondant au R, G, B ou A individuellement. En tout, un pixel RGB fait 3 fois la profondeur de ce nombre, et un pixel RGBA fait 4 fois la pronfondeur de ce nombre. Le nombre maximum d'une profondeur est donnée par la formule 2 exposant profondeur. En général, ce nombre est de 8 bits.
  • L'octet suivant du chunk représente le type de couleur du chunk. La représentation précise du nombre est un nombre entier de 1 bit non signé. Dans chaque représentation, 0 représente le noir et le nombre maximum représente la valeur maximale de la couleur du nombre (blanc, rouge, vert...).
    • Si le nombre est de 0, chaque pixel est composé de 1 nombre correspondant au niveau de gris du pixel.
    • Si le nombre est de 2, chaque pixel est composé de 3 nombres RGB.
    • Si le nombre est de 3, chaque pixel est nombre défini dans une palette RGB.
    • Si le nombre est de 4, chaque pixel est nombre de 2 nombres, correspondant au niveau de gris du pixel et au canal alpha du pixel.
    • Si le nombre est de 6, chaque pixel est composé de 4 nombres, 3 RGB et un représentant le canal alpha du pixel.
  • L'octet suivant du chunk représente la méthode de compression de l'image. La représentation précise du nombre est un nombre entier de 1 bit non signé. Seul la valeur 0 est officiellement utile, qui représente la méthode de compression "deflate/inflate".
  • L'octet suivant du chunk représente la méthode de filtrage de l'image. La représentation précise du nombre est un nombre entier de 1 bit non signé. Seul la valeur 0 est officiellement utile, qui représente un filtrage adaptatif de l'image.
  • L'octet suivant du chunk représente la méthode d'entrelacement de l'image. La représentation précise du nombre est un nombre entier de 1 bit non signé. Il existe 2 valeurs d'entrelacement :
    • La valeur 0 représente un entrelacement avec les pixels les uns après les autres, de gauche à droite et de haut en bas.
    • La valeur 1 représente un entrelacement Adam7.
Tout cela représente la partie donnée de la structure de base du chunk.

Toutes ces données peuvent faire peur, mais pour rappel, le format est sencé être très modulable. En général, seule la taille de l'image change vraiment (et peut être le type de couleur dans certains cas).

Le chunk IDAT

Un autre chunk obligatoire est le chunk nommé IDAT. Ce chunk contient tout simplement le contenu de l'image. Toutes les données nécessaires pour traiter ces données, comme celles du chunk IHDR, doivent être définies avant ce chunk.

La partie données du chunk contient l'image compressée. La méthode de compression est définie par la valeur de l'octet dédié dans le chunk IHDR (donc pour l'instant, que la méthode "inflate/deflate"). Pour décompresser/compresser ces données, vous pouvez soit coder votre propre bibliothèque de compression, soit utiliser une bibliothèque déjà faite. La plus connue, et la plus utilisées, est la bibliothèque ZLib. Elle est présente en C++, Python, Java...

Les données décompressées représentent tout simplement l'image avec les pixels les uns après les autres, selon la méthode d'entrelacement. Le contenu d'un pixel dépend de la profondeur de l'image et du type de couleur. Le contenu est découpé en "scanlines", chaque scanlines commençant par la valeur de la méthode de filtrage a appliquer à la ligne. Si il n'y a pas d'entrelacement (la valeur de l'octet dans IHDR est de 0), une scanline représente une ligne de l'image. Sinon, une scanline représente une passe de l'entrelacement.
Par exemple, pour une image en 2*2 en RGB sans entrelacement, un contenu décompréssé peut être :
0;255;0;0;0;255;0;0;0;0;255;0;0;0
Ou, représenté de manière plus estéthique :
0 - 255;0;0 - 0;255;0
0 - 0;0;255 - 0;0;0
Le premier nombre de chaque ligne représente la méthode de filtrage de la ligne. Les 6 nombres suivants correspondent à 2 fois les valeurs RGB du pixel.

Il existe 5 méthodes de filtrages différentes pour une scanline.
Voici les méthodes pour des données qui doivent être compressées.

  • Si la valeur est de 0, aucun filtrage n'est appliqué.
  • Si la valeur est de 1, la valeur finale de chaque partie du pixel est la valeur de la partie correspondante du pixel actuellement traité soustraite à la valeur de la partie correspondante du pixel d'avant. Si le pixel actuellement traité est le premier de la scanline, alors le même filtrage que pour la valeur "0" est effectué.
  • Si la valeur est de 2, la valeur finale de chaque partie du pixel est la valeur de la partie correspondante du pixel actuellement traité soustraite à la valeur de la partie correspondante du pixel au dessus de celui là. Si la scaline actuellement traitée est la première ou si l'image est entrelacée, alors le même filtrage que pour la valeur "0" est effectué.
  • Si la valeur est de 3, la valeur finale de chaque partie du pixel est la valeur de la partie correspondante du pixel actuellement traité soustraite à la valeur moyenne de la partie correspondante du pixel d'avant et de la partie correspondante du pixel d'au dessus. Si le pixel actuellement traité est le premier de la scanline ou si la scaline actuellement traitée est la première ou si l'image est entrelacée, alors le même filtrage que pour la valeur "0" est effectué.
  • Si la valeur est de 4, la valeur finale de chaque partie du pixel est la valeur de la partie correspondante du pixel actuellement traité soustraite à la valeur de la fonction de Paeth entre la partie correspondante du pixel d'avant, la partie correspondante du pixel d'au dessus et la partie correspondante du pixel d'au dessus d'avant. Si le pixel actuellement traité est le premier de la scanline ou si la scaline actuellement traitée est la première ou si l'image est entrelacée, alors le même filtrage que pour la valeur "0" est effectué.
Voici les méthodes pour des données qui viennent d'être décompressées.
  • Si la valeur est de 0, aucun filtrage n'est appliqué.
  • Si la valeur est de 1, la valeur finale de chaque partie du pixel est la valeur de la partie correspondante du pixel actuellement traité additionée à la valeur de la partie correspondante du pixel d'avant. Si le pixel actuellement traité est le premier de la scanline, alors le même filtrage que pour la valeur "0" est effectué.
  • Si la valeur est de 2, la valeur finale de chaque partie du pixel est la valeur de la partie correspondante du pixel actuellement traité additionée à la valeur de la partie correspondante du pixel au dessus de celui là. Si la scaline actuellement traitée est la première ou si l'image est entrelacée, alors le même filtrage que pour la valeur "0" est effectué.
  • Si la valeur est de 3, la valeur finale de chaque partie du pixel est la valeur de la partie correspondante du pixel actuellement traité additionée à la valeur moyenne de la partie correspondante du pixel d'avant et de la partie correspondante du pixel d'au dessus. Si le pixel actuellement traité est le premier de la scanline ou si la scaline actuellement traitée est la première ou si l'image est entrelacée, alors le même filtrage que pour la valeur "0" est effectué.
  • Si la valeur est de 4, la valeur finale de chaque partie du pixel est la valeur de la partie correspondante du pixel actuellement traité additionée à la valeur de la fonction de Paeth entre la partie correspondante du pixel d'avant, la partie correspondante du pixel d'au dessus et la partie correspondante du pixel d'au dessus d'avant. Si le pixel actuellement traité est le premier de la scanline ou si la scaline actuellement traitée est la première ou si l'image est entrelacée, alors le même filtrage que pour la valeur "0" est effectué.
Une fois que le filtrage est appliqué, alors vous avez votre image décodée, pixel par pixel, pour un usage à court terme.

Certains fichiers peuvent contenir plusieurs chunks IDAT. Dans ce cas là, les données finales compresser / à décompresser représente la concaténation de toutes les données des chunks IDAT.

Le chunk IEND

Pour une fois, le chunk IEND est extrêment simple, puis ce qu'il ne contient pas de données (appart la structure obligatoire d'un chunk). Il marque la fin de l'analyse du fichier PNG.

Les chunk sRGB et pHYs

Ces deux chunks sont des chunks secondaires. Il ne sont donc en théorie pas obligatoires. Or, j'ai remarqué que l'absence de ces chunks rend l'encodage ou le décodage d'une image impossible sous Windows. Donc j'ai décidé d'en parler brièvement.

Le chunk sRGB est souvent présent dans certaines images venant d'Internet. Il ne contient que 1 octet, si l'image utilise la norme sRGB (1) ou non (0). En général, il est totalement inutile de créer ce chunk dans un encodeur. Cependant, il faut y penser dans un décodeur. En effet, cette norme est actuellement un standard mondiale, et pas besoin de le rajouter.

Le chunk pHYs est nécessaire pour certains décodeurs. Il sert à connaître la taille de l'image dans la vraie vie. Il contient 9 octets, que voici :

  • Les 4 premiers octets du chunk représentent le nombre de pixels en largeur pour obtenir une certaine taille dans la vraie vie. La représentation précise du nombre est un nombre entier de 4 octets non signé, en big endian. Par exemple, si ces 4 octets contiennent le nombre "1500", alors une unité de valeur dans la vraie vie équivaut à 1500 pixels de l'image.
  • Les 4 octets suivant du chunk représentent le nombre de pixels en hauteur pour obtenir une certaine taille dans la vraie vie. La représentation précise du nombre est un nombre entier de 4 octets non signé, en big endian. Par exemple, si ces 4 octets contiennent le nombre "1500", alors une unité de valeur dans la vraie vie équivaut à 1500 pixels de l'image.
  • Le dernier octet du chunk représente l'unité de valeur dans la vraie vie de ces mesures. Il peut être soit 0 (unité inconnue), soit 1 (l'unité est le mètre).
Il est OBLIGATOIRE de mettre ce chunk avant le premier chunk IDAT.

Et... c'est tout

Avec tout ça, vous avez de quoi créer un décodeur ou encodeur d'image PNG; Cependant, si vous voulez faire un encodeur plus puissant et plus avancé, il faut d'autres informations plus complexes. Ces informations seront rajoutées plus tard, dans la suite de la page.

Annexes

Sources

Plusieurs sites webs ont été utilisés pour acquérir ces informations :