HotPDF esegue il rendering dei colori spot Separation e DeviceN sulle pagine PDF caricate risolvendo lo spazio colore tramite HPDFResolveColorSpace, valutando la funzione di trasformazione della tinta con HPDFEvalTintTransform e convertendo il risultato tramite lo spazio alternativo in RGB per lo schermo. A partir della versione v2.375.0, questa pipeline valuta tutti e quattro i tipi di funzione PDF, inclusi i calcolatori PostScript Type 4, per cui un file pronto per la stampa con inchiostri Pantone mostra i suoi colori reali invece di un segnaposto. Questo articolo illustra il funzionamento della pipeline e, in modo altrettanto utile, come ha fallito durante la sua implementazione
Il caso scatenante è sempre lo stesso. Un cliente riceve un PDF da una tipografia: l'intestazione è impostata con un inchiostro spot denominato, la grafica della confezione utilizza una miscela DeviceN a due inchiostri e il testo del corpo è nero semplice. In Acrobat appare perfetto. Nella tua applicazione Delphi, l'intestazione viene renderizzata in nero o, peggio, non viene renderizzata affatto, e il cliente segnala un errore nel software piuttosto che nel file. Il file è corretto. È il renderer che non supporta gli spazi colore utilizzati dal file
Perché i colori spot vengono renderizzati in nero in un visualizzatore PDF?
I colori spot vengono renderizzati in nero, o scompaiono, quando il renderer implementa solo gli operatori di colore del dispositivo (rg, g, k) e ignora quelli generici. Lo standard ISO 32000-1 §8.6 definisce tre gruppi di spazi colore: spazi del dispositivo (DeviceGray, DeviceRGB, DeviceCMYK), spazi basati su CIE (CalGray, CalRGB, Lab, ICCBased) e spazi speciali (Indexed, Separation, DeviceN, Pattern). Tutto ciò che si trova al di fuori del gruppo di dispositivi viene selezionato con gli operatori generici: cs e CS scelgono uno spazio per nome dal dizionario delle risorse della pagina, quindi sc, SC, scn e SCN forniscono i valori dei componenti. Un renderer che salta questi operatori mantiene l'ultimo colore impostato, che per una pagina che inizia con l'intestazione in colore spot coincide con il nero iniziale di DeviceGray
In che modo una trasformazione della tinta converte un valore di inchiostro in un colore reale?
Uno spazio Separation contiene tre elementi informativi: il nome dell'inchiostro, uno spazio colore alternativo e una funzione di trasformazione della tinta. L'array [/Separation /PANTONE485 /DeviceCMYK f] indica che: quando il flusso di contenuto scrive 0.8 scn, il valore della tinta 0.8 viene passato alla funzione f e viene disegnata la quadrupla CMYK risultante. DeviceN estende questo concetto a N inchiostri con una funzione a N input. Il nome dell'inchiostro stesso ha solo valore indicativo sullo schermo; la trasformazione della tinta costituisce l'intera semantica di rendering, quindi un renderer che analizza lo spazio colore ma salta la funzione non esegue alcuna operazione utile
HPDFEvalTintTransform è il motore di funzioni alla base di questo passaggio. Introdotto nella versione v2.334.0, valuta le funzioni esponenziali Type 2 (C0 + x^N * (C1 - C0) con limitazione di Domain e Range), le funzioni di unione (stitching) Type 3 (ricorsione di sottofunzioni selezionata in base ai limiti con rimappatura di Encode) e le funzioni campionate Type 0 con campioni a 8, 16 e 32 bit. Type 0 rappresenta lo stesso meccanismo di tabella di ricerca (lookup table) che abbiamo analizzato dal lato della creazione nell'articolo sulla creazione di LUT colore Type 0; il lato di rendering percorre la stessa identica struttura al contrario, dai byte di campioni decodificati fino ai valori dei componenti
Le funzioni del calcolatore PostScript Type 4 sono state l'ultimo elemento implementato. Fino alla versione v2.375.0 si riducevano a un segnaposto neutro; a partire dalla v2.375.0, HPDFEvalPostScriptCalculator esegue l'insieme completo di operatori dello standard ISO 32000-1 §7.10.5 su uno stack di operandi limitato: aritmetica, confronto, operatori booleani e bit a bit, manipolazione dello stack incluso roll, e condizionali if/ifelse. La semantica dei casi limite è più rigorosa di quanto sembri. La funzione PostScript round arrotonda la metà verso il valore maggiore, quindi la funzione di arrotondamento bancario Round di Delphi non può essere utilizzata; gli operatori trigonometrici lavorano in gradi, con atan che restituisce valori in [0, 360); ed exp è una potenza a due operandi, non l'esponenziale naturale. Lo stesso valutatore gestisce anche i riempimenti sfumati basati su funzioni, motivo per cui il rendering delle sfumature assiali e radiali ha introdotto rampe guidate da calcolatore nella stessa release
// Evaluate a Separation/DeviceN tint transform (Type 0/2/3/4).
function HPDFEvalTintTransform(FuncObj: THPDFObject;
const Inputs: THPDFColorComps; InputCount: Integer;
out AltComps: THPDFColorComps): Boolean;
// Type 4 PostScript calculator, ISO 32000-1 7.10.5 operator set.
function HPDFEvalPostScriptCalculator(const Prog: TBytes;
const Inputs: THPDFColorComps; InputCount: Integer;
var Outputs: THPDFColorComps; OutCount: Integer): Boolean;
Precisione e approssimazione: un calcolatore software valutato in doppia precisione non corrisponderà a un RIP bit per bit, e la limitazione al confine del Range può differire per un bit meno significativo rispetto a un'altra implementazione. Per la visualizzazione a schermo e il rendering di regressione questo è irrilevante; se stai creando un sistema di prove colore gestite, la trasformazione della tinta è solo la prima fase e avrai comunque bisogno di un vero CMM a valle
CalGray, CalRGB, Lab e ICCBased senza un motore ICC
Le famiglie basate su CIE seguono l'altro ramo del risolutore. HotPDF converte i valori Lab tramite la catena standard da Lab a XYZ a sRGB, inclusa la gestione del punto di discontinuità 6/29 nella funzione di trasferimento inversa, e gestisce CalRGB con il suo gamma per canale più una matrice lineare 3x3 e CalGray con il suo singolo gamma. Il dettaglio rilevante per le prestazioni riguarda la gestione del punto di bianco: la matrice di conversione da XYZ a sRGB viene adattata secondo il metodo Bradford al punto di bianco dichiarato nello spazio colore e memorizzata nella cache nel record THPDFColorSpace risolto, in modo che l'elaborazione per pixel rimanga una singola moltiplicazione 3x3, a prescindere dall'illuminante dichiarato
Gli spazi ICCBased ricevono un trattamento volutamente pragmatico. La specifica PDF richiede che ogni flusso ICCBased dichiari uno spazio /Alternate o uno implicito tramite il conteggio dei componenti /N, proprio per consentire ai visualizzatore senza un motore di gestione del colore di eseguire comunque il rendering in modo sensato. HotPDF risolve gli spazi ICCBased tramite tale alternativa, o ipotizza DeviceGray, DeviceRGB o DeviceCMYK in base al fatto che /N sia 1, 3 o 4 quando la voce è assente, senza mai analizzare i byte del profilo. Ciò elimina la dipendenza da lcms e i tempi di ricerca del profilo, a scapito della precisione colorimetrica: uno spazio ICCBased il cui profilo differisce notevolmente dalla sua alternativa mostrerà il rendering di quest'ultima. Per la visualizzazione a schermo e le miniature, si tratta del medesimo compromesso adottato da ogni visualizzatore leggero, ed è un limite da esporre chiaramente nella documentazione
Il bug che impediva il corretto funzionamento di ogni ricerca di spazi colore con nome
La gestione degli operatori della versione v2.333.0 presentava un difetto rimasto invisibile per quarantadue release: i gestori di cs e CS cercavano il loro operando mantenendo la barra iniziale (/CS0) rispetto alle chiavi del dizionario delle risorse memorizzate senza la barra (CS0). Ogni ricerca di spazi colore con nome falliva nel cento percento dei casi, e il codice tornava silenziosamente al valore predefinito DeviceGray. Il sintomo visibile era subdolo: un riempimento Separation di 1 scn diventava DeviceGray 1.0, che disegna in bianco, e l'inchiostro bianco su una pagina bianca non è un errore di rendering che spinge a inviare schermate di errore. La correzione, nella versione v2.375.0, consiste in un helper di normalizzazione del nome condiviso applicato ad ogni ricerca da operando a risorsa
Due difetti correlati sono emersi dalla stessa indagine. In primo luogo, i riferimenti indiretti agli oggetti di tipo array venivano restituiti non risolti: l'accesso al documento del renderer disponeva di risolutori tipizzati solo per flussi e dizionari, quindi /CS0 5 0 R che puntava a un array [/Separation ...] indipendente veniva restituito come riferimento grezzo e lo spazio veniva interpretato come non supportato. In secondo luogo, HPDFReadNumericArray applicava una semantica di lunghezza rigorosa, richiedendo che l'array PDF fosse lungo almeno quanto il buffer fornito. La lettura di /C0 e /C1 di una funzione Type 2 in un buffer a quattro elementi falliva quindi per le alternative a uno e tre componenti, lasciando entrambi gli array azzerati, e ogni tinta esponenziale non CMYK veniva renderizzata in nero finché la versione v2.376.0 non ha introdotto il lettore non restrittivo HPDFReadNumericArrayUpTo. La lezione utile: qualsiasi chiave PDF documentata come contenente un array numerico a lunghezza variabile deve essere letta con un lettore che compila ciò che esiste, poiché un buffer a dimensione fissa con corrispondenza rigida trasforma file validi in zeri silenziosi
Come si verifica il rendering dei colori spot senza trarre conclusioni errate?
La domanda scomoda è perché la suite di test sia rimasta verde nonostante tutto questo. La regressione originale, SeparationRendersDistinguishable, verificava solo che la bitmap renderizzata non fosse completamente nera. Una pipeline che riduceva ogni colore spot a DeviceGray produceva un output grigio e bianco, che non è nero, quindi la verifica passava mentre l'intera funzionalità era inattiva. Verifiche deboli del tipo "non vuoto", "non tutto nero" o "digest diverso da zero" non possono distinguere un renderer funzionante da uno rotto, poiché quasi tutti i tipi di errore producono comunque alcuni pixel
L'approccio di verifica che rileva effettivamente questi fallimenti consiste nel bloccare il colore atteso: eseguire il rendering di un PDF minimo creato manualmente il cui inchiostro Separation si risolva in una tonalità nota, quindi contare i pixel in cui domina tale tonalità. Il rendering su una bitmap per l'ispezione utilizza lo stesso punto di accesso RenderLoadedPageToBitmap descritto nella guida al rendering da pagina a bitmap
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;
// Lock the expected ink: demand a real area of red-dominant
// pixels, never settle for "not all black".
Assert(RedHits > 500);
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
L'ambiente di test (fixture) è importante quanto la verifica. Un PDF creato a mano lungo poche centinaia di byte, con un unico riempimento Separation e nient'altro, non lascia dubbi sull'output atteso; un file reale esercita più codice ma non può dirti quale fase ha fallito. Ora consideriamo il conteggio dei pixel del colore atteso come il requisito minimo per qualsiasi smoke test di rendering, poiché è l'unico approccio di verifica che ha costretto questi difetti a emergere
Il rendering dei colori spot e degli spazi colore CIE fa parte della pipeline dei documenti caricati nel Componente HotPDF per Delphi e C++Builder, insieme al motore delle funzioni, al rendering delle sfumature e ai percorsi di esportazione delle bitmap mostrati sopra