Bilder

Neben Texten und Vektorgrafiken kann ein PDF auch klassische Bilder enthalten. Der korrekte Begriff ist Bitmap oder Pixmap. Bei diesen Bildern ist die Bildfläche in ein Raster von Bildpunkten aufgeteilt, und für jeden Bildpunkt die exakte Farbe festgehalten.

Ein weiterer Unterschied zu unserer bisherigen Praxis ist, dass wir für die Helligkeit (bei Graustufen) bzw. Farbintensität (bei RGB und CMYK) keine Dezimalbrüche sondern Ganzzahlen verwenden. Die möglichen Werte gehen dabei natürlich nicht von 0 bis 1, sondern typischerweise von 0 bis 255, wobei sich dies einstellen lässt.

Bildobjekte

Es ist zwar möglich, aber nicht empfehlenswert, ein Bild direkt in eine Seite zu schreiben. Statt dessen verwenden wir ein Bildobjekt, auf welches wir vom Inhaltsobjekt aus zugreifen. Bildobjekte sind spezielle Streams, welche im Dictionaryteil zusätzliche Informationen zum Bild enthalten.

das Dictionary

Neben dem ohnehin obligatorischen /Length sollten noch folgende Einträge vorhanden sein:

/Type immer /XObject
/Subtype immer /Image
/Width Anzahl Bildpunkte pro Zeile
/Height Anzahl Zeilen von Bildpunkten
/ColorSpace das Farbmodell
/BitsPerComponent die Farbtiefe
/Interpolate die Skalierungsmethode

Dem Eintrag /ColorSpace wird der Name /DeviceGray für Graustufen, /DeviceRGB für RGB oder /DeviceCMYK für CMYK zugeordnet.

Die Farbtiefe definiert die Anzahl Abstufungen, die für die Helligkeit bzw. Intensität zur Verfügung stehen. Es wird aber nicht die Anzahl Stufen angegeben, sondern die Anzahl Bits, welche zur binären Darstellung nötig sind. Mögliche Werte in PDF 1.4 sind:

  • 1 Bit (2 Abstufungen)
  • 2 Bits (4 Abstufungen)
  • 4 Bits (16 Abstufungen)
  • 8 Bits (256 Abstufungen)

Der weitaus häufigste Wert ist 8, was 256 Abstufungen (0 - 255) ergibt. Übrigens: Wenn in der Praxis die Rede von 24 Bit Bildern ist, so sind in der Regel RGB Bilder mit 8 Bit pro Farbkomponente gemeint. Da wir bei RGB ja drei Komponenten haben, ergibt dies pro Bildpunkt 24 Bit.

Der Eintrag /Interpolate schliesslich ist fakultativ. Wird er angegeben, und als Wert true eingesetzt, so wird das Anzeigeprogramm angewiesen, beim Skalieren des Bildes einen aufwendigeren Algorithmus einzusetzen. Das Bild sieht dann unter Umständen besser aus, dafür ist die Darstellung langsamer. Nicht alle Programme beachten diesen Eintrag.

der Stream

Im eigentlichen Stream sind die Farbinformationen der Bilddaten abgelegt. Dabei werden die einzelnen Bildpunkte von Links nach Rechts, von Oben nach Unten nacheinander aufgeführt. Bei RGB und CMYK werden dabei für jeden Bildpunkt die einzelnen Komponenten in der durch die Abkürzung angedeuten Reihenfolge notiert.

Die Zahlen müssen in binärer Form angegeben werden. Den korrekten Umgang mit binären Daten schauen wir uns im nächsten Kapitel an. Fürs erste verwenden wir die Möglichkeit, binäre Daten in Hexadezimalschreibweise notieren zu können. Die Schreibweise ist dabei dieselbe, wie bei Hexstrings, ausser, das auf die öffnende Klammer verzichtet wird. Das > als schliessende Klammer sollte hingegen angegeben werden, um verbindlich das Ende der Daten zu markieren.

Um die Daten in einem Stream komplett in Hexadezimalschreibweise angeben zu können, braucht das Dictionary einen zusätzlichen Parameter /Filter, der auf /ASCIIHexDecode gesetzt werden muss.

Beispiel:

5 0 obj
<<
/Type /XObject
/Subtype /Image
/Width 9
/Height 9
/ColorSpace /DeviceRGB
/BitsPerComponent 8
/Length 567
/Filter /ASCIIHexDecode
>>
stream
0000FF 0000FF 0000FF 0000FF FFFF00 0000FF 0000FF 0000FF 0000FF
0000FF 0000FF 0000FF FFFF00 0000FF FFFF00 0000FF 0000FF 0000FF
0000FF 0000FF FFFF00 0000FF 0000FF 0000FF FFFF00 0000FF 0000FF
0000FF FFFF00 0000FF 0000FF 0000FF 0000FF 0000FF FFFF00 0000FF
FFFF00 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF FFFF00
0000FF FFFF00 0000FF 0000FF 0000FF 0000FF 0000FF FFFF00 0000FF
0000FF 0000FF FFFF00 0000FF 0000FF 0000FF FFFF00 0000FF 0000FF
0000FF 0000FF 0000FF FFFF00 0000FF FFFF00 0000FF 0000FF 0000FF
0000FF 0000FF 0000FF 0000FF FFFF00 0000FF 0000FF 0000FF 0000FF>
endstream
endobj

Dies definiert einen 9×9 Pixel grossen gelben Rombus auf blauem Grund. Zur Veranschaulichung habe ich Leerschläge zwischen den Bildpunkten und Zeilenumbrüche zwischen den Bildpunktzeilen eingefügt. Dies ist aber nicht zwingend nötig. Achtung: Falls das PDF als Windows-Textdatei gespeichert wird, so ist die Länge der Streamdaten 575 Bytes (wegen der anderen Zeilenumbrüche).

Bei Bittiefen von 1, 2 oder 4 ist die Sache etwas komplizierter. In diesen Fällen sind in jedem Byte mehrere Werte abgelegt. Bei 4 Bit enthält jedes Byte 2 Werte: Den ersten im höherwertigen Halbbyte, den zweiten im niederwertigen Halbbyte (was der ersten und zweiten Stelle der Hexadezimalzahl entspricht). Bei 2 Bit sind es 4 Werte vom höchstwertigsten zum niederwertigsten Viertel. Bei 1 Bit sind es 8 Werte vom höchstwertigsten zum niederwertigsten Bit. Endet eine Zeile so, dass ein Byte nicht v??llig gefüllt ist, so wird das Byte mit Nullen aufgefüllt. Somit ist garantiert, dass jede Zeile mit einem neuen Byte beginnt.

Einbindung als Resource

Das von uns erstellte Bildobjekt muss nun noch als Resource registriert werden. Dazu muss es im X-Object Dictionary des Resourcendictionarys vermerkt werden. Als Name wird üblicherweise ein grosses I gefolgt von einer Nummer verwendet.

Desweiteren müssen wir sicherstellen, dass im ProcSet Array des Resourcendictionarys der Eintrag /ImageB (für Graustufen) bzw. /ImageC (für RGB und CMYK) vorhanden ist. Am Besten setzt man ohnehin alle Einträge.

Beispiel:

/Resources <<
/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
/Font <<
/F1 4 0 R
>>
/XObject <<
/I1 5 0 R
>>
>>

Einbindung in die Seite

Nachdem wir nun endlich alles definiert haben, können wir das Bild in die Seite einbinden. Hierzu dient die Anweisung Do. Diese verlangt einen Parameter mit dem Namen des Bildobjekts. Dasselbe Bild lässt sich so beliebig oft verwenden.

Manchen wird aufgefallen sein, dass wir den Namen des Bildes angegeben haben, aber nicht dessen Grösse und Position. Do zeichnet das angegebene Objekt grundsätzlich 1×1 Einheiten gross mit der linken, unteren Ecke auf dem Nullpunkt. Um das Bild vernünftig darzustellen, brauchen wir Transformationen. Konkret müssen wir den Nullpunkt in die linke, untere Ecke des Bereichs verschieben, in dem wir das Bild darstellen wollen, und anschliessend das Bild auf die Grösse des gewünschten Bereichs skalieren.

Beispiel:

q
1 0 0 1 72 670 cm
100 0 0 100 0 0 cm
/I1 Do
Q

Damit zeichnen wir das Bild 100x100pt gross links oben auf dem Blatt.