PNG

Der /FlateDecode Filter kann PNG Bilddaten dekomprimieren. Im Unterschied zu /DCTDecode für JPEG versteht /FlateDecode aber das Dateiformat von PNG nicht. Wir müssen also nicht nur die Daten für das Dictionary des Bildobjekts ermitteln, sondern auch die Bilddaten selbst aus der Datei extrahieren.

Dateiaufbau

Eine PNG Datei beginnt immer mit der sogenannten PNG Signatur. Als Hexstring sieht diese so aus:

<89 50 4E 47 0D 0A 1A 0A>

Diese Sequenz hat zwei Nutzen: Erstens ist eine Datei so immer zweifelsfrei als PNG erkennbar. Zweitens sind die Werte so gewählt, dass bei den meisten häufigen Übertragungsfehlern mindestens ein Byte geändert wird. So lässt sich eine beschädigte Datei schon ganz zu Anfang erkennen.

Nach der Signatur folgen die eigentlichen Daten. Alle Zahlen, die mehr als ein Byte benötigen, sind dabei in Big-Endian Anordnung. Folgende Datentypen kommen vor:

char String mit 8-Bit Kodierung
uint8 vorzeichenlose 8-Bit Zahl
uint32 vorzeichenlose 32-Bit Zahl

Die Daten sind in Blöcke aufgeteilt. Diese stehen direkt hintereinander in der Datei, und sind alle folgendermassen aufgebaut:

Position Grösse Typ Inhalt
04 Byte uint32 Länge Blockdaten
44 Byte char Blocktyp
8Länge Blockdaten * Blockdaten
Länge Blockdaten + 84 Byte uint32 Prüfsumme

Die Länge der Blockdaten umfasst nur die Blockdaten selbst, ohne die Bytes für Länge, Typ und Prüfsumme.

Der Typ des Blocks ist als 4 ASCII Zeichen abgelegt.

Die Blockdaten hängen vom Blocktyp ab.

Die Prüfsumme ist eine CRC-32 Summe über Blocktyp und Blockdaten.

Mit diesen Informationen gerüstet können wir die einzelnen Blöcke erkennen, für uns uninteressante Blöcke überlesen, und (sofern wir eine CRC-32 Implementation haben) die Blöcke auf Übertragungsfehler prüfen.

Von allen möglichen Blocks interessieren uns vier Typen. der „Image Header“ (Code IHDR), die Palette (Code PLTE), die Bilddaten (Code IDAT) und das Bildende (Code IEND).

Image Header

Der Image Header Block wird durch den Code „IHDR“ gekennzeichnet. Es gibt pro Datei genau einen, und er enthält die wichtigsten Metadaten des Bildes.

Die Blockdaten sind immer 13 Bytes lang, und bestehen aus folgenden Informationen:

PostionGrösse Typ Inhalt
04 Byte uint32 Bildbreite in Pixeln
44 Byte uint32 Bildhöhe in Pixeln
81 Byte uint8 Bittiefe
91 Byte uint8 Farbmodell
101 Byte uint8 Kompressionsart
111 Byte uint8 Prediktor
121 Byte uint8 Interlacing

Die Bittiefe kann 1, 2, 4, 8 oder 16 sein. PDF 1.4 erlaubt nur 1, 2, 4 und 8 Bit. PDF 1.5 erlaubt auch 16 Bit.

Das Farbmodell kann 0, 2, 3, 4 oder 6 sein. Dabei steht 0 für Graustufen, 2 für RGB, 3 für indexiert, 4 für Graustufen mit Alphakanal, und 6 für RGB mit Alphakanal. PDF (ab 1.4) erlaubt zwar einen Alphakanal, aber leider nicht in der Art und Weise, wie es in PNG umgesetzt ist.

Die Kompressionsart ist immer 0, was „Deflate“ bedeutet.

Der Prediktor ist immer 0, was „PNG Prediktor“ bedeutet. Dies ist eine Erweiterung des Deflate-Algorithmus, die wir im Bildobjekt in /DecodeParms aktivieren müssen.

Interlacing ist entweder 0 für „kein Interlacing“ oder 1 für „Adam7 Interlacing“. PDF unterstützt kein Interlacing.

Beispiel:

<00 00 00 0D 49 48 44 52 00 00 03 20 00 00 02 58 08 02 00 00 00 15 14 15 27>

Die ersten 4 Bytes sagen uns, dass die Blockdaten 0x0D (=13) Bytes lang sein werden. Danach folgen 4 Bytes mit den ASCII Codes für „IHDR“, also ist es ein Image Header. Es folgen die Bildbreite von 0x320 (=800), die Höhe von 0x258 (=600), die Bittiefe von 8, das Farbmodell 2 (RGB), die Kompression 0 (Deflate), der Prediktor 0 (PDF Prediktor) und das Interlacing 0 (kein Interlacing). Zuletzt haben wir noch 4 Bytes mit der CRC32 Prüfsumme.

Palette

Einen Palettenblock gibt es nur bei Farbraum 3 (indexiert). Er wird durch den Code „PLTE“ markiert. Seinen Inhalt müssen wir nicht dekodieren, sondern nur auslesen, und für später bereithalten.

Bilddaten

In einer PNG Datei kann es ein oder mehrere Bilddatenblöcke geben. Diese sind mit dem Code „IDAT“ markiert. Deren Inhalt müssen wir nicht dekodieren, sondern nur auslesen, und für später bereithalten. Falls mehrere Blöcke vorhanden sind, müssen wir die Inhalte aneinanderhängen in der Reihenfolge, in der sie in der Datei aufgetaucht sind.

Bildende

Der Bildendeblock ist mit dem Code „IEND“ markiert, und enthält keine Daten. Er markiert lediglich das Ende des Bildes. Wenn wir ihn finden, können wir also die Verarbeitung der Bilddatei abbrechen.

das Bildobjekt

Mit diesen Angaben können wir nun wiederum ein Bildobjekt zusammenstellen. Dies ist ein klein wenig komplizierter als bei JPEG.

Ist im Image Header als Farbmodell 0 oder 2 angegeben, so muss /ColorSpace logischerweise auf /DeviceGray bzw. /DeviceRGB gesetzt werden. Beim Farbmodell 3 hingegen brauchen wir nicht einen einzelnen Namen, sondern ein Array aus 4 Einträgen:

[/Indexed /DeviceRGB Farben-1 Palette]

Hier brauchen wir die Blockdaten des Palettenblocks. Die Länge der Blockdaten in Byte dividiert mit 3 ergibt die Anzahl Farben. Ziehen wir davon 1 ab, erhalten wir den dritten Wert des Arrays. Den vierten Wert erhalten wir, indem wir die Blockdaten als Hexstring darstellen.

Ein weiterer Punkt, den wir bei Farbmodell 3 beachten müssen, ist das Procset Array. Es braucht in diesem Fall neben dem Eintrag /ImageC auch noch einen Eintrag /ImageI.

Desweiteren müssen wir natürlich den /FlateDecode Filter setzen. Zusätzlich brauchen wir einen Eintrag /DecodeParms. Dieser muss für /FlateDecode ein Dictionary mit folgenden Einträgen enthalten:

/Predictor immer 15
/Colors 3 bei RGB, sonst 1
/BitsPerComponent wie im Hauptdictionary
/Columns die Bildbreite in Pixeln

/Colors steht auf 3 für RGB, aber auf 1 sowohl für Graustufen, wie auch für das indexierte Farbmodell. /BitsPerComponent und /Columns entsprechen /BitsPerComponent und /Width aus dem Hauptdictionary des Bildobjekts.

Verbleiben noch die Streamdaten selbst. Hierbei handelt es sich um die Daten aus dem Image Data Block, bzw. um die aneinandergehängten Daten aus den Image Data Blocks, falls mehrere existieren.

Beispiele:

10 0 obj
<<
/Type /XObject
/Subtype /Image
/Width 800
/Height 600
/ColorSpace /DeviceRGB
/BitsPerComponent 8
/Interpolate true
/Filter [/ASCII85Decode /FlateDecode]
/DecodeParms [null << /Predictor 15 /Colors 3 /BitsPerComponent 8 /Columns 800 >>]
/Length 461808
>>
stream
...~>
endstream
endobj

Hier haben wir ein „normales“ PNG mit Vollfarben.

11 0 obj
<<
/Type /Xobject
/Subtype /Image
/Width 96
/Height 96
/ColorSpace [/Indexed /DeviceRGB 3 <FF000000FF000000FFFFFFFF>]
/BitsPerComponent 2
/Interpolate true
/Filter [/ASCII85Decode /FlateDecode]
/DecodeParms [null << /Predictor 15 /Colors 1 /BitsPerComponent 2 /Columns 96 >>]
/Length 154
>>
stream
...~>
endstream
endobj

Dies ist ein Beispiel mit indexiertem Farbmodell. Die Palette ist 12 Byte lang, und enthält folglich 4 Farben. Der entsprechende Eintrag in der /ColorSpace Liste ist 4 - 1 = 3.