Technischer Artikel

Separation- und DeviceN-Schmuckfarben in Delphi rendern

HotPDF rendert Separation- und DeviceN-Schmuckfarben auf geladenen PDF-Seiten, indem es den Farbraum über HPDFResolveColorSpace auflöst, die Tint-Transform-Funktion mit HPDFEvalTintTransform auswertet und das Ergebnis über den Alternativfarbraum für den Bildschirm nach RGB konvertiert. Seit v2.375.0 wertet diese Pipeline alle vier PDF-Funktionstypen aus, einschließlich Type-4-PostScript-Calculatoren, sodass eine druckfertige Datei mit Pantone-Farben ihre echten Farben statt eines Platzhalters anzeigt. Dieser Artikel erklärt, wie die Pipeline funktioniert, und, ebenso nützlich, wie sie während der Entwicklung versagt hat

Der Auslöser ist immer derselbe. Ein Kunde erhält ein PDF von einer Druckerei: Die Überschrift ist in einer benannten Schmuckfarbe gesetzt, das Verpackungs-Artwork verwendet eine DeviceN-Mischung aus zwei Druckfarben, und der Fließtext ist reines Schwarz. In Acrobat sieht es perfekt aus. In Ihrer Delphi-Anwendung wird die Überschrift schwarz gerendert, oder schlimmer, gar nicht, und der Kunde meldet einen Fehler gegen Ihre Software statt gegen die Datei. Die Datei ist in Ordnung. Der Renderer spricht nur die Farbräume nicht, die die Datei verwendet

Warum werden Schmuckfarben in einem PDF-Viewer schwarz gerendert?

Schmuckfarben werden schwarz gerendert oder verschwinden, wenn der Renderer nur die Geräte-Farboperatoren (rg, g, k) implementiert und die generischen ignoriert. ISO 32000-1 §8.6 definiert drei Gruppen von Farbräumen: Geräteräume (DeviceGray, DeviceRGB, DeviceCMYK), CIE-basierte Räume (CalGray, CalRGB, Lab, ICCBased) und Spezialräume (Indexed, Separation, DeviceN, Pattern). Alles außerhalb der Gerätegruppe wird mit den generischen Operatoren ausgewählt: cs und CS wählen einen Raum per Name aus dem Ressourcen-Dictionary der Seite, dann liefern sc, SC, scn und SCN die Komponentenwerte. Ein Renderer, der diese Operatoren überspringt, behält die zuletzt gesetzte Farbe, was für eine Seite, die mit ihrer Schmuckfarben-Überschrift beginnt, das anfängliche DeviceGray-Schwarz ist

HotPDF hat den generischen Operatorsatz in v2.333.0 zu seinem Seiten-Renderer hinzugefügt, zusammen mit einem einheitlichen Auflösungspfad: Jeder /ColorSpace-Ressourceneintrag, ob bloßer Name, Inline-Array oder indirekte Referenz, wird in einen einzigen THPDFColorSpace-Record geparst, und jede Füll- oder Konturfarbanforderung läuft durch einen einzigen HPDFResolveColor-Aufruf. Die Familien-Enumeration zeigt die Abdeckung auf einen Blick

HotPDF: Die drei PDF-Farbraumfamilien aus ISO 32000-1 Abschnitt 8.6, wobei Geräteräume mit rg g k gemalt werden und CIE-basierte sowie Spezialräume nur über die generischen Operatoren cs CS scn SCN erreichbar sind
Nur die Gerätefamilie reagiert auf die direkten Operatoren rg, g und k — jeder CIE-basierte und Spezialraum hängt vom generischen Satz cs, CS, scn und SCN ab, und ein Renderer, der sie überspringt, behält die vorherige Farbe
type
  THPDFColorSpaceFamily = (csfDeviceGray, csfDeviceRGB, csfDeviceCMYK,
                           csfIndexed, csfCalGray, csfCalRGB, csfLab,
                           csfICCBased, csfSeparation, csfDeviceN,
                           csfUnsupported);

function HPDFResolveColorSpace(Obj: THPDFObject): THPDFColorSpace;

function HPDFResolveColor(const CS: THPDFColorSpace;
  const Comps: THPDFColorComps; CompCount: Integer): THPDFRenderColor;

Eine Designentscheidung zahlt sich immer wieder aus: csfUnsupported ist eine vollwertige Familie, kein Fehler. Ein Raum, den der Renderer nicht interpretieren kann, fällt auf einen definierten Fallback zurück, statt die Seite abzubrechen, was dem Verhalten gängiger Viewer entspricht und verhindert, dass eine einzelne exotische Füllung ein ansonsten darstellbares Dokument leert

Wie verwandelt eine Tint-Transform einen Druckfarbenwert in eine echte Farbe?

Ein Separation-Farbraum trägt drei Informationen: den Namen der Druckfarbe, einen Alternativfarbraum und eine Tint-Transform-Funktion. Das Array [/Separation /PANTONE485 /DeviceCMYK f] sagt: Wenn der Content-Stream 0.8 scn schreibt, führe den Tint-Wert 0,8 in die Funktion f ein und male das resultierende CMYK-Quadrupel. DeviceN verallgemeinert dies auf N Druckfarben mit einer Funktion mit N Eingängen. Der Name der Druckfarbe selbst ist am Bildschirm nur ein Hinweis; die Tint-Transform ist die gesamte Rendering-Semantik, sodass ein Renderer, der den Raum parst, aber die Funktion überspringt, noch nichts Nützliches getan hat

HPDFEvalTintTransform ist die Funktions-Engine hinter diesem Schritt. Eingeführt in v2.334.0, wertet sie Type-2-Exponentialfunktionen (C0 + x^N * (C1 - C0) mit Domain- und Range-Begrenzung), Type-3-Stitching-Funktionen (über Bounds ausgewählte Unterfunktions-Rekursion mit Encode-Umschlüsselung) und Type-0-Sampled-Funktionen mit 8-, 16- und 32-Bit-Samples aus. Type 0 ist dieselbe Lookup-Tabellen-Mechanik, die wir von der Erzeugungsseite in dem Artikel über den Aufbau von Type-0-Farb-LUTs behandelt haben; die Render-Seite durchläuft die identische Struktur rückwärts, von decodierten Sample-Bytes zurück zu Komponentenwerten

Separation-Tint-Transform-Pipeline in HotPDF, in der ein scn-Tint-Wert über das Separation-Farbraum-Array aufgelöst wird und HPDFEvalTintTransform die Funktionstypen 0, 2, 3 und 4 zu alternativen CMYK-Komponenten auswertet, die nach sRGB konvertiert werden
Ein Tint-Wert aus scn tritt in den Separation-Raum ein, und die Tint-Transform-Engine liefert ein vollständiges Komponententupel des Alternativraums zurück, das dann einmal für den Bildschirm konvertiert wird

Type-4-PostScript-Calculator-Funktionen waren die letzte Bastion. Bis v2.375.0 fielen sie sanft auf einen neutralen Platzhalter zurück; seit v2.375.0 führt HPDFEvalPostScriptCalculator den vollständigen Operatorsatz aus ISO 32000-1 §7.10.5 auf einem begrenzten Operandenstapel aus: arithmetische, Vergleichs-, boolesche und bitweise Operatoren, Stapelmanipulation einschließlich roll sowie if/ifelse-Bedingungen. Die Randsemantik ist strenger, als sie aussieht. PostScript-round rundet bei der Hälfte zum größeren Wert, sodass Delphis kaufmännisch rundendes Round nicht verwendet werden kann; die trigonometrischen Operatoren arbeiten in Grad, wobei atan Werte in [0, 360) zurückgibt; und exp ist eine Potenz mit zwei Operanden, nicht die natürliche Exponentialfunktion. Derselbe Auswerter treibt auch funktionsbasierte Verlaufsfüllungen an, weshalb das Rendering axialer und radialer Shadings im selben Release calculatorgesteuerte Rampen erhielt

// Eine Separation-/DeviceN-Tint-Transform auswerten (Type 0/2/3/4).
function HPDFEvalTintTransform(FuncObj: THPDFObject;
  const Inputs: THPDFColorComps; InputCount: Integer;
  out AltComps: THPDFColorComps): Boolean;

// Type-4-PostScript-Calculator, Operatorsatz aus ISO 32000-1 7.10.5.
function HPDFEvalPostScriptCalculator(const Prog: TBytes;
  const Inputs: THPDFColorComps; InputCount: Integer;
  var Outputs: THPDFColorComps; OutCount: Integer): Boolean;

Ehrlichkeit bei der Präzision: Ein in doppelter Genauigkeit ausgewerteter Software-Calculator wird einem RIP nicht Bit für Bit entsprechen, und das Begrenzen an der Range-Grenze kann sich um ein niedrigstwertiges Bit von einer anderen Implementierung unterscheiden. Für Bildschirmanzeige und Regressions-Rendering ist das irrelevant; wenn Sie farbverbindliches Proofing bauen, ist die Tint-Transform nur die erste Stufe, und Sie brauchen nachgelagert weiterhin ein echtes CMM

CalGray, CalRGB, Lab und ICCBased ohne ICC-Engine

Die CIE-basierten Familien nehmen den anderen Zweig desselben Resolvers. HotPDF konvertiert Lab-Werte über die Standardkette Lab nach XYZ nach sRGB, einschließlich des 6/29-Knickpunkt-Kubus in der inversen Übertragungsfunktion, und behandelt CalRGB mit seinem Gamma pro Kanal plus linearer 3x3-Matrix und CalGray mit seinem einzelnen Gamma. Das leistungsrelevante Detail ist die Weißpunktbehandlung: Die XYZ-nach-sRGB-Konvertierungsmatrix wird per Bradford an den im Farbraum deklarierten Weißpunkt adaptiert und im aufgelösten THPDFColorSpace-Record gecacht, sodass die Arbeit pro Pixel eine einzige 3x3-Multiplikation bleibt, egal wie exotisch die deklarierte Lichtart ist

ICCBased-Räume erhalten eine bewusst pragmatische Behandlung. Die PDF-Spezifikation verlangt, dass jeder ICCBased-Stream einen /Alternate-Raum deklariert oder einen impliziten über seine Komponentenanzahl /N, gerade damit Viewer ohne Farbmanagement-Engine trotzdem sinnvoll rendern können. HotPDF löst ICCBased über diesen Alternativraum auf oder rät DeviceGray, DeviceRGB oder DeviceCMYK daraus, dass /N 1, 3 oder 4 ist, wenn der Eintrag fehlt, und parst die Profilbytes nie. Das bedeutet keine lcms-Abhängigkeit und keine Kosten für Profil-Lookups, um den Preis kolorimetrischer Genauigkeit: Ein ICCBased-Raum, dessen Profil stark von seinem Alternativraum abweicht, zeigt die Darstellung des Alternativraums. Für die Bildschirmanzeige und Miniaturansichten ist das derselbe Kompromiss, den jeder leichtgewichtige Viewer eingeht, und es ist die Grenze, die Sie in Ihrer eigenen Dokumentation klar benennen sollten

Der Fehler, durch den jeder benannte Farbraum-Lookup danebenging

Die Operator-Verdrahtung aus v2.333.0 wurde mit einem Defekt ausgeliefert, der zweiundvierzig Releases lang unsichtbar blieb: Die cs- und CS-Handler suchten ihren Operanden mit intaktem führendem Schrägstrich (/CS0) in Ressourcen-Dictionary-Schlüsseln, die ohne Schrägstrich gespeichert waren (CS0). Jeder benannte Farbraum-Lookup ging daneben, in hundert Prozent der Fälle, und der Code fiel still auf das Standard-DeviceGray zurück. Das sichtbare Symptom war auf die schlimmste Weise subtil: Eine Separation-Füllung mit 1 scn wurde zu DeviceGray 1.0, was Weiß malt, und weiße Druckfarbe auf einer weißen Seite ist kein Rendering-Fehler, von dem jemand einen Screenshot macht. Die Korrektur in v2.375.0 war ein gemeinsamer Namensnormalisierungs-Helfer, der bei jedem Operand-zu-Ressource-Lookup angewendet wird

Zwei Geschwisterdefekte fielen bei derselben Untersuchung ab. Erstens wurden indirekte Referenzen auf Array-Objekte unaufgelöst zurückgegeben: Der Dokumentzugriff des Renderers hatte typisierte Resolver nur für Streams und Dictionaries, sodass /CS0 5 0 R, das auf ein eigenständiges [/Separation ...]-Array zeigte, als rohe Referenz zurückkam und der Raum als nicht unterstützt geparst wurde. Zweitens erzwang HPDFReadNumericArray strikte Längensemantik und verlangte, dass das PDF-Array mindestens so lang ist wie der übergebene Puffer. Das Lesen von /C0 und /C1 einer Type-2-Funktion in einen Vier-Elemente-Puffer schlug daher bei Alternativräumen mit einer und drei Komponenten fehl, ließ beide Arrays auf null und jede Nicht-CMYK-Exponential-Tint schwarz rendern, bis v2.376.0 den toleranten Reader HPDFReadNumericArrayUpTo einführte. Die übertragbare Lehre: Jeder PDF-Schlüssel, der laut Dokumentation ein numerisches Array variabler Länge enthält, muss mit einem Reader gelesen werden, der füllt, was vorhanden ist, denn ein Puffer fester Größe mit strikter Übereinstimmung verwandelt gültige Dateien in stille Nullen

Wie testet man Schmuckfarben-Rendering, ohne sich selbst zu täuschen?

Die unangenehme Frage lautet, warum die Testsuite während all dessen grün blieb. Die ursprüngliche Regression, SeparationRendersDistinguishable, prüfte nur, dass die gerenderte Bitmap nicht vollständig schwarz war. Eine Pipeline, die jede Schmuckfarbe auf DeviceGray herunterstufte, erzeugte graue und weiße Ausgabe, was nicht schwarz ist, sodass die Assertion bestand, während die gesamte Funktion tot war. Schwache Assertions der Form „nicht leer“, „nicht ganz schwarz“ oder „Digest ist ungleich null“ können einen funktionierenden Renderer nicht von einem defekten unterscheiden, denn fast jeder Fehlermodus erzeugt immer noch irgendwelche Pixel

Der Assertion-Stil, der diese Fehler tatsächlich erwischt, fixiert die erwartete Farbe: ein handgebautes minimales PDF rendern, dessen Separation-Druckfarbe zu einem bekannten Farbton aufgelöst wird, und dann die Pixel zählen, in denen dieser Farbton dominiert. Das Rendern in eine Bitmap zur Prüfung verwendet denselben Einstiegspunkt RenderLoadedPageToBitmap, der in dem Leitfaden zum Rendern von Seiten in Bitmaps beschrieben wird

HotPDF: Schwache versus starke Assertions für Schmuckfarben-Rendering, bei denen ein Nicht-ganz-schwarz-Prädikat auf grauer defekter Ausgabe besteht, während das Zählen rot-dominanter Pixel mit einem RedHits-Schwellenwert über 500 die tote Pipeline erwischt
Ein Nicht-ganz-schwarz-Prädikat besteht auf grauer Ausgabe, während die gesamte Schmuckfarben-Pipeline tot ist, und das Zählen von Pixeln des erwarteten Farbtons ist es, was den Defekt ans Licht zwingt
var
  Pdf: THotPDF;
  Bmp: TBitmap;
  X, Y, RedHits: Integer;
  Px: TColor;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('spot-red-fixture.pdf', '') > 0 then
    begin
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 96);
      try
        RedHits := 0;
        for Y := 0 to Bmp.Height - 1 do
          for X := 0 to Bmp.Width - 1 do
          begin
            Px := Bmp.Canvas.Pixels[X, Y];
            if (GetRValue(Px) > 180) and (GetGValue(Px) < 100) and
               (GetBValue(Px) < 100) then
              Inc(RedHits);
          end;
        // Die erwartete Druckfarbe fixieren: eine echte Fläche rot-dominanter
        // Pixel verlangen, sich nie mit "nicht ganz schwarz" zufriedengeben.
        Assert(RedHits > 500);
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Die Testdatei ist ebenso wichtig wie die Assertion. Ein handgebautes PDF von wenigen hundert Bytes, mit einer Separation-Füllung und sonst nichts, lässt keine Zweifel daran, was die erwartete Ausgabe ist; eine reale Datei übt mehr Code aus, kann Ihnen aber nicht sagen, welche Stufe versagt hat. Wir betrachten das Zählen von Pixeln der erwarteten Farbe inzwischen als Mindestanforderung für jeden Rendering-Smoke-Test, denn es ist der einzige Assertion-Stil, der diese Defekte ans Licht gezwungen hat

Das Rendering von Schmuckfarben und CIE-Farbräumen ist Teil der Pipeline für geladene Dokumente in der HotPDF Delphi Component für Delphi und C++Builder, neben der Funktions-Engine, dem Shading-Rendering und den oben gezeigten Bitmap-Exportpfaden