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.
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 |
---|---|---|---|
0 | 4 Byte | uint32 | Länge Blockdaten |
4 | 4 Byte | char | Blocktyp |
8 | Länge Blockdaten | * | Blockdaten |
Länge Blockdaten + 8 | 4 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).
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:
Postion | Grösse | Typ | Inhalt |
---|---|---|---|
0 | 4 Byte | uint32 | Bildbreite in Pixeln |
4 | 4 Byte | uint32 | Bildhöhe in Pixeln |
8 | 1 Byte | uint8 | Bittiefe |
9 | 1 Byte | uint8 | Farbmodell |
10 | 1 Byte | uint8 | Kompressionsart |
11 | 1 Byte | uint8 | Prediktor |
12 | 1 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.
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.
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.
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.
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.
Diskussion