Technischer Artikel

Ein PDF in Delphi in einen einheitlichen Farbraum umwandeln

Eine Überschrift, gefüllt mit 1 0 0 rg, reinem Rot, kommt aus einer Graustufen-Konvertierung als 0.299 g zurück. Reines Blau landet bei 0.114 g, nah genug an Schwarz, dass ein Marineblaues Logo und der Fließtext schwer auseinanderzuhalten sind. Diese Zahlen sind kein Rundungsunfall: Sie sind die BT.601-Luminanz-Gewichte, dieselben, die ein Viewer anwendet, wenn er eine RGB-Seite auf einem grauen Gerät zeigt, und sie entscheiden, wie viel Kontrast überlebt, wenn eine Farbbroschüre grau werden muss

Was RecolorDocument für jede Farbe berechnet

Die PDF Library for Delphi stellt die Konvertierung als RecolorDocument(TargetSpace) auf TPDFlib bereit, mit 1 für Graustufen, 2 für RGB und 3 für CMYK. Sie gibt zurück, wie viele Dinge sie umgewandelt hat, und Geometrie, Textpositionen und Bildplatzierung bleiben, wo sie waren. Nur die Farbwerte und die Farbraum-Namen um sie herum ändern sich

Jede Farbe geht auf dem Weg ins Ziel über RGB. Grau ist die Luminanz-Summe 0.299 R + 0.587 G + 0.114 B. CMYK erreicht RGB über die schlichte Geräte-Regel aus ISO 32000-1 §10.3, bei der jeder Kanal eins minus der Summe aus seiner Tinte und Schwarz ist, geklemmt auf null. RGB erreicht CMYK, indem die größte Komponente nach Schwarz gezogen wird: K = 1 - max(R, G, B), und die übrigen Inks werden aus dem Rest skaliert. Ein paar Ergebnisse, die man kennen sollte, bevor irgendetwas zu einer Druckerei geht:

  • Ein Grau von 0.4 g wird zu CMYK konvertiert zu 0 0 0 0.6 k, nur Schwarz-Tinte, was eine Druckmaschine für Text und Linien will
  • RGB 1 0.5 0 (Orange) wird 0 0.5 1 0 k, ganz ohne Schwarz
  • Ein Tiefschwarz wie 0.6 0.4 0.4 1 k wird auf dem Weg nach Grau zu 0 g, die zusätzlichen Inks verschwinden also einfach
  • Reines Cyan, 1 0 0 0 k, wird zu 0.701 g, heller, als die meisten von einer gesättigten Tinte erwarten

Konvertierte Operanden werden mit höchstens drei Dezimalstellen geschrieben und immer mit einem Punkt als Dezimaltrenner, ganz gleich, welche Locale die Maschine hat, auf der der Job läuft. ISO 32000-1 §7.3.3 erlaubt nichts anderes in einem Content-Stream, und ein Konverter, der Zahlen mit der System-Locale formatiert, schreibt auf einem deutschen Arbeitsplatz 0,5 g, was ein Reader als die Zahl 0 gefolgt von einem unbekannten Operator parst

var
  Lib: TPDFlib;
  Converted: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('brochure.pdf', '') <> 1 then
      raise Exception.Create('Could not open brochure.pdf');
    Converted := Lib.RecolorDocument(1);   // 1 = Grau, 2 = RGB, 3 = CMYK
    WriteLn(Format('%d colors, images and gradients converted', [Converted]));
    Lib.SaveToFile('brochure-gray.pdf');
  finally
    Lib.Free;
  end;
end;

Eine Seite, deren Farben bereits im Zielraum liegen, behält ihre ursprünglichen Content-Bytes. RecolorDocument zählt nur Konvertierungen, die tatsächlich von einem Raum in einen anderen überqueren, ein bereits graues Dokument meldet also 0, und nichts darin wird neu geschrieben. Bei einer großen Datei zählt das: Ein Diff von Eingabe und Ausgabe zeigt genau die Seiten, die sich geändert haben

Warum lässt RecolorPage das Cover-Foto in Farbe?

RecolorPage konvertiert die Farboperatoren auf der gewählten Seite und stoppt dort. Bilder und Verläufe sind Objekte auf Dokumentebene, oft von mehreren Seiten geteilt, und nur RecolorDocument wandelt sie um. Der naheliegende Weg, nur ein Deckblatt zu konvertieren – Seite 1 wählen und RecolorPage(3) aufrufen –, liefert also CMYK-Überschriften und Linien über einem RGB-Foto

Diese Aufspaltung ist mit Absicht. Ein Bild-XObject, das auf dem Deckblatt gezeichnet wird, kann auch auf Seite 12 gezeichnet werden, es für eine Seite umzuwandeln wandelt es für beide um. Ein seitenbezogener Aufruf, der stillschweigend in gemeinsame Ressourcen greifen würde, änderte Seiten, die der Aufrufer nie gewählt hat. Das ist die schlimmere der beiden Überraschungen, also fasst der Pro-Seite-Aufruf nur das an, was der Seite gehört

// Operatoren nur auf dem Deckblatt; Bilder und Verläufe werden hier nicht umgewandelt
Lib.SelectPage(1);
Changed := Lib.RecolorPage(3);
if Changed = 0 then
  WriteLn('Cover already uses CMYK operators (or the target was out of range)');
Lib.SaveToFile('cover-cmyk-operators.pdf');

// Für ein Dokument, das durchgehend ein Raum sein muss, alles umwandeln
Lib.RecolorDocument(3);
Lib.SaveToFile('all-cmyk.pdf');

Achten Sie in dem Snippet auf den Rückgabewert. Beide Aufrufe liefern 0 für ein Ziel außerhalb von 1 bis 3, und 0 ist auch die Antwort für eine Seite, die keine Änderung brauchte, ein Tippfehler im Ziel sieht also genau aus wie ein bereits korrektes Dokument. Prüfen Sie das Argument selbst vor dem Aufruf. RecolorDocument stellt die Seite, die vor seinem Lauf gewählt war, wieder her, man kann es also mitten in einem Seite-für-Seite-Job aufrufen, ohne danach neu zu wählen

Noch ein Umfangs-Detail. Der Operator-Durchlauf liest den eigenen Content-Stream jeder Seite, die Zeichnungsanweisungen, die auf der Seite selbst gelistet sind, und wandelt g, rg, k, ihre Strichformen und sc / scn um, wenn der aktuelle Raum ein Geräte-Raum ist. Wenn /DeviceRGB cs einem konvertierten sc vorangeht, wird der Raum-Name ebenfalls ins Ziel umgeschrieben. Lässt ein handgeschriebener Konverter diese Umbenennung aus, bleibt ein Reader damit zurück, drei RGB-Komponenten zu suchen, wo jetzt ein Grauwert sitzt

Bilder und Paletten: welche sich ändern, welche ihre Bytes behalten

Bilder sind so wichtig wie Operatoren. Eine zu Grau konvertierte Broschüre, bei der nur die Operatoren behandelt wurden, zeigt weiterhin Farbfotos auf grauen Seiten, was schlechter wirkt als das Original oder als eine vollständige Konvertierung. RecolorDocument wandelt ein Bild um, wenn es acht Bits pro Komponente trägt, einen Geräte-Farbraum hat und entweder keine Komprimierung oder schlichtes Flate ohne Decode-Parameter aufweist

Indizierte Bilder nehmen einen anderen Weg. Ihre Abtastungen sind Indizes in eine Farbtabelle, also wird die Tabelle umgewandelt und jede Abtastung behält den Index, den sie ohnehin hielt. Ein Screenshot oder Diagramm, als 256-Farb-Palettenbild gespeichert, konvertiert auf diese Weise unabhängig davon, wie seine Abtastungen gepackt oder komprimiert sind, denn die Abtastungen werden nie dekodiert. Die Basis der Palette muss ein Geräte-Raum sein; eine Palette über einem ICC-Profil behält ihre Tabelle

Diagramm, das zeigt, welche Teile einer PDF RecolorDocument in Delphi neu schreibt und welche Teile wie JPEG-Bilder und Softmasks byte-identisch durchgereicht werden
Ein RecolorDocument-Durchlauf schreibt Operatoren, geeignete Bilder, indizierte Paletten und Verlaufspaarungen um, während JPEG-Bytes, Masken und profilgebundene Farben unangetastet mitfahren

Ein JPEG- oder Fax-kodiertes Bild behält seine Bytes. Seine Farben zu ändern würde bedeuten, es zu dekodieren, umzuwandeln und neu zu komprimieren, was eine zweite Generation von JPEG-Artefakten hinzufügt und die Dateigröße in Weisen ändert, die niemand verlangt hat. Wenn ein Job wirklich jedes Foto in Grau braucht, ist Re-Kodieren eine separate Entscheidung mit eigener Qualitätseinstellung, und wir lassen lieber den Aufrufer sie explizit treffen, als dass eine Farbkonvertierung sie für ihn trifft

Soft-Masken und Schablonen-Masken bleiben aus einem anderen Grund, wie sie sind. Eine Soft-Maske ist meist ein DeviceGray-Bild, aber ihre Werte bedeuten Abdeckung. Wandelt man eine in dreikomponentiges RGB um, hört ein Viewer auf, sie als Transparenz zu behandeln; sie landet als graue Grafik gemalt über dem Inhalt, den sie formen sollte. RecolorDocument sammelt jedes Objekt, das über /SMask oder /Mask referenziert wird, bevor es irgendein Bild umwandelt, und ein Bild mit /ImageMask true hat gar keine Farbe, die sich umwandeln ließe

Farben, die auf Operatorebene über ICCBased-, Separation-, Indexed- oder Pattern-Räume gesetzt wurden, bleiben ebenfalls, wie sie geschrieben sind. Eine Spot-Farbe oder einen profilmarkierten Wert ohne das Lesen ihres Profils in Geräte-Grau zu überführen würde ändern, wie die Seite aussieht, und ein Durchlauf, dessen Job die Bewahrung ist, kann nicht anfangen, zu erraten, was ein Profil gemeint hat

Verläufe: Der Farbraum und die Stops wandern gemeinsam

Ein Shading deklariert seinen Farbraum einmal und übergibt jeden Stop an eine Funktion. Benennt man /ColorSpace von DeviceRGB in DeviceGray um, während die Funktion weiterhin drei Zahlen liefert, nimmt ein Reader die erste davon, den Rot-Kanal, als Graustufe. Ein Blau-Weiß-Verlauf rendert dann als ein Band, das bei Schwarz beginnt. RecolorDocument läuft zuerst den ganzen Funktionsbaum lesend durch und wandelt nichts um, es sei denn, jeder Knoten darin lässt sich umwandeln; erst dann schreibt es die Stops neu und benennt den Raum um

Die Funktionen, die es neu schreibt, sind der exponentielle Typ, dessen zwei Endfarben /C0 und /C1 schlichte Arrays sind, und Stitching-Funktionen, die daraus gebaut sind. Eine /Background-Farbe wird mit umgewandelt. Ein durch eine Sampled-Tabelle oder ein PostScript-Programm definiertes Shading behält seine Definition, ebenso die Mesh-Typen 4 bis 7, die ihre Farben in einem Stream statt in einer Funktion tragen. Beide neu zu schreiben ist Regeneration, ein anderer Job als eine Farbe zu konvertieren

PDF Library for Delphi-Diagramm der Gradientenkonvertierung, die einen Shading-Farbraum mit seiner Stop-Funktion über eigenständige Objekte, Patterns und Seiten-Shading-Ressourcen hinweg koppelt, während Sampled-, PostScript- und Mesh-Shadings ihre eigene Definition behalten
Einen Farbverlauf zu konvertieren heißt, seinen Farbraum und die dahinterliegenden Stops gemeinsam zu verschieben, wo auch immer die Schattierung lebt; Mesh- oder Sampled-Function-Schattierungen behalten stattdessen ihre eigene Definition

Die Verläufe zu finden ist eine eigene Falle. Design-Werkzeuge geben einem Shading selten ein eigenes Objekt; es sitzt meist inline im Pattern-Dictionary, das es malt, oder in den /Shading-Ressourcen einer Seite. Als wir den Verlaufs-Durchlauf bauten, fand ein Scan der Top-Level-Objekte fast keine. Die Suche folgt jetzt dem Objektgraphen, begrenzt auf zwölf Ebenen Tiefe und mit jeder Objektnummer nur einmal, sodass eine von jeder Seite geteilte Ressource einmal umgewandelt wird und eine Datei, die ein Objekt auf sich selbst zeigen lässt, nicht in eine Schleife geraten kann

Wo Umfärben vor einer Druck-Einreichung hingehört

Behandeln Sie RecolorDocument als Normalisierung. Es konvertiert Werte zwischen Geräte-Räumen; es wendet kein ICC-Profil an, berücksichtigt keinen Rendering Intent und simuliert keine Druckmaschinen-Bedingung. Für einen Job, der einen verbindlichen Proof treffen muss, ist ein profilgetriebener Workflow das richtige Werkzeug, und ein Umfärbungs-Durchlauf ist höchstens der Schritt, der vorher alles in einen Raum bringt

Seinen Platz verdient es beim gewöhnlichen Produktionsproblem: einer Einreichung, die grau sein muss und mit einem RGB-Logo ankommt, einem internen Bericht, der nicht in Farbe drucken darf, einem Batch, den ein Preflight-Schritt zurückweist, bis jede Datei einen Raum verwendet. Alles, was das Haus verlässt, sollte durch RecolorDocument, nie durch RecolorPage allein, denn eine halb konvertierte Datei besteht eine schnelle Sichtprüfung und fällt dann beim Drucker auf. Lassen Sie das Urteil im selben Job laufen: Die Hinweise zu PDF/A- und PDF/UA-Preflight und zur Compliance- und Signatur-Workbench zeigen, wie

// IsValidRecolorTarget ist ein Helfer, den Sie selbst ergänzen; die Bibliothek hat keinen solchen Aufruf
function IsValidRecolorTarget(Space: Integer): Boolean;
begin
  Result := (Space >= 1) and (Space <= 3);
end;

procedure NormalizeFile(const Src, Dst: string; Space: Integer);
var
  Lib: TPDFlib;
begin
  if not IsValidRecolorTarget(Space) then
    raise EArgumentException.CreateFmt('Bad recolor target %d', [Space]);
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile(Src, '') <> 1 then
      raise Exception.CreateFmt('Could not open %s', [Src]);
    if Lib.RecolorDocument(Space) = 0 then
      WriteLn(Src, ': already in the target space, copied unchanged');
    Lib.SaveToFile(Dst);
  finally
    Lib.Free;
  end;
end;

Eine schnelle Prüfung der Ausgabe: Öffnen Sie den Inhalt einer konvertierten Seite nach einer Graustufen-Konvertierung in einem beliebigen PDF-Inspektor. Der eigene Content der Seite sollte keine rg-, RG-, k- oder K-Operatoren mehr enthalten. Farbe, die den Durchlauf überlebt, kommt aus den Fällen oben: ein JPEG, eine profilmarkierte oder Spot-Farbe, ein Sampled- oder Mesh-Verlauf, oder Grafik, die in einem Form-XObject gezeichnet wurde, deren Anweisungen in einem eigenen Stream leben statt im Seiteninhalt

Wenn Sie die Vektor-Grafik auch selbst aufbauen statt nur zu konvertieren, zeigt der Walkthrough zu Vektor-Grafik, Shadern und Verläufen, wie Shadings und Patterns überhaupt erst erzeugt werden, was die Verlaufs-Regeln oben leichter vorhersehbar macht

Der Farb-Durchlauf läuft im Prozess: Die PDF Library for Delphi ist natives Pascal für Delphi, C++Builder und Lazarus, eine Batch eingehender Dateien zu normalisieren braucht also keinen externen Konverter oder Befehlszeilen-Werkzeug auf dem Server. Die PDF Library for Delphi-Produktseite listet den Rest der Dokument-Verarbeitungs-API