Elke stap van het decoderen van een PNG

Chunking

PNG-bestanden worden opgeslagen als een reeks 'chunks'. Elke chunk heeft een lengte van 4 bytes, een naam van 4 bytes, de eigenlijke data en een CRC (Cyclic Redundancy Check). De meeste chunks bevatten metadata over de afbeelding, maar de IDAT-chunk bevat de eigenlijke gecomprimeerde beeldata. De hoofdlettercombinatie van de naam van de chunk bepaalt hoe onverwachte chunks behandeld moeten worden:

HoofdlettersKleine lettersBetekenis
KritiekHulp (Ancillary)Is deze chunk vereist om de afbeelding correct weer te geven?
PubliekPrivéIs dit een private, applicatie-specifieke chunk?
GereserveerdGereserveerd-
Onveilig om te kopiërenVeilig om te kopiërenMoeten programma's die de PNG-data wijzigen deze chunk begrijpen?

Hieronder volgen de chunks in de voorbeeldafbeelding:

Signature

Dit identificeert de afbeelding als een PNG. Het bevat newline-tekens en andere tekens die vaak corrupt raken, om er zeker van te zijn dat de binaire beeldata niet beschadigd is. 89 50 4E 47 0D 0A 1A 0A

Header (IHDR)

Deze bevat metadata over de afbeelding.

  • Lengte: 00 00 00 0D = 13 bytes lang
  • Type: 49 48 44 52 = IHDR
  • Data: 00 00 01 00 00 00 01 00 08 02 00 00 00
  • Breedte: 256
  • Hoogte: 256
  • Bitdiepte: 8
  • Kleurruimte: Truecolor RGB
  • Compressiemethode: 0
  • Filtermethode: 0
  • Interlacing: uitgeschakeld
  • CRC: D3 10 3F 31

Gamma (gAMA)

Deze bevat de gamma van de afbeelding, gebruikt om nauwkeuriger kleuren weer te geven.

  • Lengte: 00 00 00 04 = 4 bytes lang
  • Type: 67 41 4D 41 = gAMA
  • Data: 00 00 B1 8F = 45455
  • CRC: 0B FC 61 05

Informatie over kleurruimte (cHRM)

Deze bevat data over waar in de volledige CIE-kleurruimte de kleuren in de afbeelding zich bevinden, zodat monitoren die kleuren buiten de standaard sRGB-ruimte ondersteunen de afbeelding beter kunnen weergeven.

  • Lengte: 00 00 00 20 = 32 bytes lang
  • Type: 63 48 52 4D = cHRM
  • Data: 00 00 7A 26 00 00 80 84 00 00 FA 00 00 00 80 E8 00 00 75 30 00 00 EA 60 00 00 3A 98 00 00 17 70
  • CRC: 9C BA 51 3C

Fysieke afmetingen (pHYs)

Deze bevat de fysieke afmetingen van de afbeelding, zodat deze indien mogelijk in de juiste fysieke grootte kan worden weergegeven.

  • Lengte: 00 00 00 09 = 9 bytes lang
  • Type: 70 48 59 73 = pHYs
  • Data: 00 00 0B 13 00 00 0B 13 01 = 2835 pixels = 1 meter
  • CRC: 00 9A 9C 18

Laatste wijzigingsdatum (tIME)

Deze bevat het tijdstip waarop de afbeelding is gemaakt.

  • Lengte: 00 00 00 07 = 7 bytes lang
  • Type: 74 49 4D 45 = tIME
  • Data: 07 E5 05 0A 0A 2F 2C = 2021-05-10, 10:47:44 (UTC)
  • CRC: 00 53 9E DD

Achtergrondkleur (bKGD)

Deze bevat de achtergrondkleur van de afbeelding die wordt weergegeven terwijl de afbeelding wordt geladen.

  • Lengte: 00 00 00 06 = 6 bytes lang
  • Type: 62 4B 47 44 = bKGD
  • Data: 00 FF 00 FF 00 FF = wit
  • CRC: A0 BD A7 93

Beeldata (IDAT)

Deze bevat de gecomprimeerde beeldata.

  • Lengte: 00 00 F5 51 = 62801 bytes lang
  • Type: 49 44 41 54 = IDAT
  • Data: ... (de gecomprimeerde en gefilterde bytes van de afbeelding)
  • CRC: BF EB 1B 15

Tekst (tEXt)

Dit kan willekeurige getagde tekstuele data over de afbeelding opslaan.

  • Lengte: 00 00 00 27 = 39 bytes lang
  • Type: 74 45 58 74 = tEXt
  • Data: 46 69 6C 65 00 2F 68 6F 6D 65 2F 73 6D 69 74 2F 50 69 63 74 75 72 65 73 2F 69 63 6F 6E 33 2F 33 64 2E 62 6C 65 6E 64
  • key: "File", value: "/home/smit/Pictures/icon3/3d.blend"
  • CRC: 88 DA 55 E7

Tekst (tEXt)

  • Lengte: 00 00 00 13 = 19 bytes lang
  • Type: 74 45 58 74 = tEXt
  • Data: 52 65 6E 64 65 72 54 69 6D 65 00 30 30 3A 31 32 2E 31 35
  • key: "RenderTime", value: "00:12.15"
  • CRC: 5E 2F 7A B4

Trailer (IEND)

Deze lege chunk geeft het einde van het PNG-bestand aan.

  • Lengte: 00 00 00 00 = 0 bytes lang
  • Type: 49 45 4E 44 = IEND
  • CRC: AE 42 60 82

Het extraheren van de beeldata

We nemen alle IDAT-chunks en voegen deze samen. In deze afbeelding is er slechts één IDAT-chunk. Sommige afbeeldingen hebben meerdere IDAT-chunks; de inhoud hiervan wordt door de PNG-decoder samengevoegd. Dit is gedaan zodat streaming-encoders de totale datalengte niet vooraf hoeven te weten, aangezien de datalengte aan het begin van elke chunk staat. Meerdere IDAT-chunks zijn ook nodig voor het coderen van afbeeldingen waarbij de beeldata een lengte heeft die groter is dan de grootste mogelijke chunk-grootte (2554).

Wanneer we de gecomprimeerde data behandelen als ruwe beeldata, ziet het eruit als willekeurige ruis. Dit betekent dat het compressie-algoritme goed werk heeft geleverd. Gecomprimeerde data zou een hogere entropie moeten hebben dan de data met lagere entropie die het codeert. Bovendien is het minder dan een derde van de hoogte van de eigenlijke afbeelding: dat is een goede compressie!

Decompresseren

De belangrijkste chunk is de IDAT-chunk, die de eigenlijke beeldata bevat. Om de gefilterde beeldata te verkrijgen, voegen we de data in de IDAT-chunks samen en decompresseren we deze met zlib. Er is hier geen sprake van een afbeelding-specifiek compressiemechanisme: gewoon normale zlib-compressie.

Naast het feit dat de kleuren er helemaal verkeerd uitzien, lijkt de afbeelding ook horizontaal verschoven te zijn. Dit komt doordat elke regel van de afbeelding een filter heeft als eerste byte. Filters verminderen de grootte van de afbeelding niet direct, maar brengen deze in een vorm die beter comprimeerbaar is door zlib.

Defiltering

We nemen de gedecomprimeerde data en maken het filter op elke regel ongedaan. Dit levert ons de gedecodeerde afbeelding op, identiek aan het origineel. Hieronder staat de populariteit van elk filtertype:

  • None: 0
  • Subtract: 23
  • Up: 77
  • Average: 134
  • Paeth: 22

Interlacing

PNG's kunnen optioneel 'interlaced' zijn, wat de afbeelding opsplitst in 7 verschillende afbeeldingen, waarvan elke afbeelding een niet-overlappend gebied van de totale afbeelding beslaat.

Elk van de 7 afbeeldingen wordt opeenvolgend geladen, waarbij steeds meer detail aan de afbeelding wordt toegevoegd terwijl deze wordt geladen. Deze reeks afbeeldingen wordt Adam7 genoemd.

De data voor elk van de 7 afbeeldingen worden na elkaar in het beeldbestand opgeslagen. Als we het voorbeeldbeeld nemen en interlacing inschakelen, zien we de ruwe ongecomprimeerde beeldata. De 7 passes die we uit die beeldata kunnen extraheren, zien eruit als gedownscalde versies van de afbeelding.

Omdat sommige van die sub-afbeeldingen rechthoeken zijn terwijl de eigenlijke afbeelding vierkant is, zullen er bij het laden van de afbeelding meer details horizontaal dan verticaal zichtbaar zijn. Dit is omdat horizontale details belangrijker zijn voor de menselijke visuele perceptie dan verticale details.

Bonus: bugs

Dit is wat er gebeurt wanneer er een bug zit in het 'Average'-filter, waardoor overflows onjuist worden behandeld (de manier waarop integer overflow in de filterwaarde-berekening is gespecificeerd, is namelijk iets anders dan bij de rest).