Bilder

Eingebundene Bilder wurden oft unabhängig vom restlichen Dokument erstellt, und haben ihre eigenen Farbkorrekturinformationen. Darum ist es in PDF möglich, die Farbkorrektur eingebundener Bilder separat einzustellen. Zudem kann man für die allfällige Konversion von RGB nach CMYK festlegen, ob möglichst auf exakte Farbumsetzung, Erhaltung der Farbsättigung, oder Erhaltung des photographischen Eindrucks geachtet werden soll.

Farbkorrektur

Um eine vom Dokumentstandard abweichende Farbkorrektur für das Bild festzulegen, müssen wir im Bildobjekt den /ColorSpace Eintrag ändern. Dieser war bisher auf /DeviceGray, /DeviceRGB, /DeviceCMYK oder ein Array mit einem /Indexed Farbmodell.

/DeviceGray, /DeviceRGB oder /DeviceCMYK werden einfach durch eine Umrechnungstabelle oder einen Verweis auf ein ICC-Profil ersetzt.

Beispiel:

10 0 obj
<<
/Type /XObject
/Subtype /Image
/Width 800
/Height 600
/ColorSpace [/ICCBased 11 0 R]
/BitsPerComponent 8
/Interpolate true
/Filter [/ASCII85Decode /DCTDecode]
/Length 35654
>>
stream
...~>
endstream
endobj

Bei indizierten Farbräumen ist die Sache ein klein wenig kniffliger. Diese sind bekanntlich als Arrays angegeben, von denen der erste Eintrag /Indexed ist. Der zweite Eintrag war in unserem Fall bisher immer /DeviceRGB. Diesen Eintrag müssen wir ersetzen.

Beispiel:

/ColorSpace [/Indexed [/ICCBased 11 0 R] 3 <FF000000FF000000FFFFFFFF>]

Vorsicht: Beim Alphakanal muss /ColorSpace auf /DeviceGray gesetzt werden. /CalGray und /ICCBased sind hier nicht erlaubt (und machen auch wenig Sinn).

Umrechnungspriorität

Das Dictionary des Bildobjekts kann einen Eintrag /Intent haben, der auf die bekannten Intents gesetzt werden kann. Als Daumenregel kann man sich merken:

Bei Formaten, die typischerweise Fotos enthalten (insbesondere JPEG), eignet sich /Perceptual als Standard.

Bei allen anderen Formaten eignet sich /RelativeColorimetric als Standard.

Daten aus Bildern ermitteln

Die Umrechnungspriorität müssen wir von Hand einstellen. Farbkorrekturinformationen hingegen sind zuweilen in den Bilddateien abgelegt. Wir müssen sie nur rausfischen.

JPEG

Bei JPEG herrscht leider ein gewisses Chaos. Das JFIF Format enthält überhaupt keine Farbkorrekturinformationen. Das EXIF Format erlaubt zwar, solche abzulegen, jedoch sind diese unzuverlässig: Zum einen handelt es sich meist nur um Schätzwerte, und zum anderen oft um die Werte der Kamera. Wir brauchen aber die Werte des Bildschirms, an dem das Bild aufbereitet wurde.

ICC hat schliesslich eine eigene Erweiterung veröffentlicht, welche es ermöglicht, ein ICC-Profil in eine JPEG-Datei einzubetten. Um diese Information zu ermitteln, müssen wir alle Metadatenblöcke des JPEG anschauen. Zur Erinnerung: Sobald wir dabei auf einen Block mit dem Marker <FF DA> stossen, können wir die Verarbeitung abbrechen. Nach diesem Block folgend die komprimierten Bilddaten.

Das ICC-Profil ist in einem oder mehreren Metadatenblöcken abgelegt. Diese Blöcke haben den Marker <FF E2>. Der Aufbau ist wie folgt:

PositionGrösse Typ Wert
01 Byte uint8 immer 0xFF
11 Byte uint8 immer 0xE2
22 Byte uint16Blockgrösse
411 Byte char ASCII-String ICC_PROFILE
151 Byte uint8 immer 0
161 Byte uint8 Blocknummer
171 Byte uint8 Blockanzahl
18Blockgrösse - 16* ICC-Profildaten

Der String ICC_PROFILE am Anfang soll den Block zweifelsfrei als ICC-Block markieren. Es können auch andere Daten in einem Block mit dem <FF E2> Marker abgelegt sein, da es sich nicht um einen offiziellen Standard des JPEG Komitees handelt.

Ist die Blockanzahl 1, so können wir die Profildaten aus diesem Block 1:1 übernehmen. Ansonsten müssen wir die Profildaten aller ICC-Blocks aneinanderhängen, und zwar in der Reihenfolge, die mit der Blocknummer vorgegeben ist (aufsteigend ab 1).

Die (allenfalls zusammengesetzten) Profildaten sind die unveränderten Daten einer ICC-Datei. Das heisst, die Daten können in einen Stream gepackt werden, der dann mit /ICCBased referenziert werden kann (den /N Eintrag im Stream des ICC-Profils nicht vergessen).

PNG

PNG bietet drei Möglichkeiten, die Farbkorrektur anzugeben: sRGB-Deklaration, ICC-Profile und Chromatizitätsdeklaration.

sRGB-Deklaration

Das ist die einfachste Methode. Enthält das PNG einen Block mit Marker sRGB (auf Gross- und Kleinschreibung achten), so wird das sRGB-Farbmodell verwendet. Man kann dementsprechend /CalRGB bzw. /CalGray mit den zu sRGB passenden Zahlen verwenden. Falls dieser Block vorkommt, sollten ICC-Profile oder Chromatizitätsdeklarationen ignoriert werden.

PositionGrösseTyp Wert
04 Byteuint32Blockgrösse
44 Bytechar immer sRGB
81 Byteuint8 Umrechnungspriorität
94 Byteuint32Prüfsumme

Wie man sieht, kann in diesem Block (und nur in diesem Block) eine bevorzugte Umrechnungspriorität definiert werden. Folgende vier Werte sind möglich:

0/Perceptual
1/RelativeColorimetric
2/Saturation
3/AbsoluteColorimetric

ICC-Profil

Enthält das PNG einen Block mit Marker iCCP, so enthält dieser ein ICC-Profil. Der Blockaufbau ist wie folgt:

PositionGrösseTyp Wert
04 Byteuint32Blockgrösse
44 Bytechar immer iCCP
8* char Profilname
*1 Byteuint8 immer 0
*1 Byteuint8 Kompression
** * ICC-Profildaten
*4 Byteuint32Prüfsumme

Der Profilname ist in ISO Latin-1 kodiert, für uns aber nicht wichtig. Entscheidend ist, dass der Name selbst kein 0-Byte enthalten kann. Das erste 0-Byte innerhalb der Blockdaten ist folglich der oben erwähnte uint8 mit Wert 0. Das ist die einzige Möglichkeit, die Länge des Profilnamens zu ermitteln.

Die Kompressionsmethode ist immer 0, was Deflate bedeutet. Das heisst, wir können die Profildaten 1:1 in einen Stream packen, und den /FlateDecode Filter angeben. Den Stream können wir dann mit /ICCBased referenzieren.

Chromatizität

Die ist die letzte Möglichkeit, falls weder eine sRGB-Deklaration noch ein ICC-Profil vorhanden sind. In diesem Fall haben wir zwei Blöcke. Einen cHRM Block mit Weisspunkt und Matrix, und einen gAMA Block mit dem Gammawert. Falls nur einer der beiden vorhanden ist (was nicht der Fall sein sollte), so kann man die sRGB-Werte für den fehlenden Block verwenden.

gAMA
Wert Blockgrösse immer gAMAGammawert (invers) Prüfsumme
PositionGrösseTyp
04 Byteuint32
44 Bytechar
84 Byteuint32
124 Byteuint32

Der inverse Gammawert ist eine Ganzzahl auf einer Skala von 0 - 100'000. Den Wert für das PDF erhalten wir folglich, indem wir mit einer Fliesskommadivision 100'000 durch den Wert aus dem PNG teilen:

g_{pdf} = \frac{100000}{g_{png}}

cHRM

Der cHRM Block enthält die Chromatizität des Weisspunkts und der verwendeten Rot- Grün- und Blautöne. Dies ist eine weitere CIE-Norm, die mit zwei Koordinaten x und y ausgedrückt wird. Um Verwechslungen mit dem XYZ-Farbraum zu vermeiden, werden für Chromatizität immer Kleinbuchstaben, für XYZ immer Grossbuchstaben verwendet.

PositionGrösseTyp Wert
04 Byteuint32Blockgrösse
44 Bytechar immer cHRM
84 Byteuint32xw
124 Byteuint32yw
164 Byteuint32xr
204 Byteuint32yr
244 Byteuint32xg
284 Byteuint32yg
324 Byteuint32xb
364 Byteuint32yb
404 Byteuint32Prüfsumme

Ähnlich wie im gAMA Block handelt es sich um vorzeichenlose Ganzzahlen auf einer Skala von 0 - 100'000. Anders als bei gAMA sind sie aber nicht invers. Folglich müssen wir diesmal die Zahlen mit Fliesskommadivisionen durch 100'000 teilen.

x_{pdf} = \frac{x_{png}}{100000} y_{pdf} = \frac{y_{png}}{100000}

Wenn wir dies erledigt haben, können wir die Werte in den XYZ-Farbraum umrechnen.

Zunächst müssen wir die XYZ-Werte des Weisspunkts berechnen:

X_w = \frac{x_w}{y_w} Y_w = 1 Z_w = \frac{1 - x_w - y_w}{y_w}

Für die Grundfarben brauchen wir folgenden Formelsatz:

z_r = 1 - x_r - y_r z_g = 1 - x_g - y_g z_b = 1 - x_b - y_b \begin{bmatrix} S_r && S_g && S_b \end{bmatrix} = \begin{bmatrix} x_r && y_r && z_r \\ x_g && y_g && z_g \\ x_b && y_b && z_b \end{bmatrix}^{-1} \begin{bmatrix} X_w && Y_w && Z_w \end{bmatrix} \begin{bmatrix} X_r && Y_r && Z_r \end{bmatrix} = \begin{bmatrix} x_r && y_r && z_r \end{bmatrix} S_r \begin{bmatrix} X_g && Y_g && Z_g \end{bmatrix} = \begin{bmatrix} x_g && y_g && z_g \end{bmatrix} S_g \begin{bmatrix} X_b && Y_b && Z_b \end{bmatrix} = \begin{bmatrix} x_b && y_b && z_b \end{bmatrix} S_b

Wie man sieht, enthalten die Formeln leider eine invertierte Matrix. Das macht die arithmetische Variante recht kompliziert:

z_r = 1 - x_r - y_r z_g = 1 - x_g - y_g z_b = 1 - x_b - y_b m_{xr} = y_gz_b - z_gy_b m_{xg} = z_ry_b - y_rz_b m_{xb} = y_rz_g - z_ry_g d = m_{xr}x_r + m_{xg}x_g + m_{xb}x_b S_r = \frac{m_{xr}X_w + (y_bZ_w - z_b) x_g + (z_g - y_gZ_w) x_b}{d} S_g = \frac{m_{xg}X_w + (y_rZ_w - z_r) x_b + (z_b - y_bZ_w) x_r}{d} S_b = \frac{m_{xb}X_w + (y_gZ_w - z_g)x_r + (z_r - y_rZ_w) x_g}{d} X_r = x_rS_r Y_r = y_rS_r Z_r = z_rS_r X_g = x_gS_g Y_g = y_gS_g Z_g = z_gS_g X_b = x_bS_b Y_b = y_bS_b Z_b = z_bS_b

Damit können wir nun die Werte für /CalRGB zusammenstellen. Ins /WhitePoint Array, gehören nacheinander die Werte Xw, Yw und Zw. Ins /Matrix Array nacheinander die Werte Xr, Yr, Zr, Xg, Yg, Zg, Xb, Yb und Zb.