HotPDF randează culori spot Separation și DeviceN pe pagini PDF încărcate prin rezolvarea spațiului de culoare prin intermediul HPDFResolveColorSpace, evaluarea funcției de transformare a nuanței cu HPDFEvalTintTransform și convertirea rezultatului prin spațiul alternativ în RGB pentru ecran. Încărcând cu versiunea v2.375.0, acea conductă evaluează toate cele patru tipuri de funcții PDF, inclusiv calculatoarele PostScript Type 4, astfel încât un fișier gata de tipar cu cerneluri Pantone își afișează culorile reale în loc de un înlocuitor. Acest articol explică modul în care funcționează conducta și, la fel de util, modul în care a eșuat în timp ce o construiam
Cazul declanșator este întotdeauna același. Un client primește un PDF de la o tipografie: titlul este setat într-o cerneală spot numită, ambalajul folosește un amestec DeviceN cu două cerneluri, iar textul din corp este negru simplu. În Acrobat arată perfect. În aplicația dvs. Delphi, titlul se randează ca negru sau, mai rău, nu se randează deloc, iar clientul raportează o eroare împotriva software-ului dvs. și nu a fișierului. Fișierul este în regulă. Motorul de randare doar nu cunoaște spațiile de culoare folosite de fișier
De ce se randează culorile spot ca negru într-un vizualizator PDF?
Culorile spot se randează ca negru sau dispar complet atunci când motorul de randare implementează doar operatorii de culoare de dispozitiv (rg, g, k) și îi ignoră pe cei generici. ISO 32000-1 §8.6 definește trei grupuri de spații de culoare: spații de dispozitiv (DeviceGray, DeviceRGB, DeviceCMYK), spații bazate pe CIE (CalGray, CalRGB, Lab, ICCBased) și spații speciale (Indexed, Separation, DeviceN, Pattern). Tot ceea ce se află în afara grupului de dispozitiv este selectat cu operatorii generici: cs și CS aleg un spațiu după nume din dicționarul de resurse al paginii, apoi sc, SC, scn și SCN furnizează valorile componentelor. Un motor de randare care omite acești operatori păstrează orice culoare a fost setată ultima dată, ceea ce, pentru o pagină care începe cu titlul în culoare spot, este negrul inițial DeviceGray
HotPDF a adăugat setul de operatori generici la motorul său de randare a paginii în versiunea v2.333.0, alături de o cale de rezolvare unificată: fiecare intrare de resursă /ColorSpace, fie că este un nume simplu, un tablou inline sau o referință indirectă, este analizată într-o înregistrare THPDFColorSpace, iar fiecare solicitare de culoare de umplere sau de contur trece printr-un singur apel HPDFResolveColor. Enumerarea familiilor arată acoperirea la o primă vedere
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;
O decizie de proiectare își dovedește valoarea în mod repetat: csfUnsupported este o familie de prim rang, nu o eroare. Un spațiu pe care motorul de randare nu îl poate interpreta retrogradează la o alternativă definită în loc să anuleze redarea paginii, ceea ce corespunde comportamentului vizualizatoarelor principale și împiedică o singură umplere exotică să blocheze un document altfel randabil
Cum transformă o transformare de nuanță o valoare de cerneală într-o culoare reală?
Un spațiu de culoare Separation conține trei informații: numele cernelii, un spațiu de culoare alternativ și o funcție de transformare a nuanței (tint-transform). Tabloul [/Separation /PANTONE485 /DeviceCMYK f] spune: când fluxul de conținut scrie 0.8 scn, introduceți valoarea de nuanță 0.8 în funcția f și desenați cvadrupla CMYK rezultată. DeviceN generalizează acest concept la N cerneluri cu o funcție cu N intrări. Numele cernelii în sine este doar orientativ pe ecran; transformarea nuanței reprezintă întreaga semantică a randării, astfel încât un motor de randare care analizează spațiul dar omite funcția nu a realizat nimic util încă
HPDFEvalTintTransform este motorul de funcții din spatele acestui pas. Introdus în versiunea v2.334.0, acesta evaluează funcții exponențiale Type 2 (C0 + x^N * (C1 - C0) cu limitare Domain și Range), funcții de coasere Type 3 (recursivitate a subfuncțiilor selectate prin Bounds cu remaparea Encode) și funcții eșantionate Type 0 cu eșantioane de 8, 16 și 32 de biți. Type 0 este aceiași structură de tabel de căutare pe care am acoperit-o din perspectiva creării în articolul despre construirea tabelelor de căutare de culori Type 0; partea de randare parcurge exact aceeași structură în sens invers, de la octeții de eșantion decodificați înapoi la valorile componentelor
Funcțiile calculator PostScript Type 4 au fost ultimul element adăugat. Până la versiunea v2.375.0, acestea retrogradau controlat la un înlocuitor neutru; de la v2.375.0, HPDFEvalPostScriptCalculator execută setul complet de operatori ISO 32000-1 §7.10.5 pe o stivă delimitată de operanzi: operații aritmetice, de comparare, operatori booleeni și pe biți, manipularea stivei inclusiv roll și condiționalele if/ifelse. Semantica detaliilor este mai strictă decât pare. Operatorul PostScript round rotunjește jumătățile spre valoarea mai mare, deci rotunjirea bancară Round din Delphi nu poate fi utilizată; operatorii trigonometrici funcționează în grade, cu atan returning values in [0, 360); iar exp este o ridicare la putere cu doi operanzi, nu exponențiala naturală. Același evaluator conduce și umplerile cu gradienți bazate pe funcții, motiv pentru care randarea umbririlor axiale și radiale a preluat rampele bazate pe calculator în aceeași versiune
// 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;
Onestitate legată de precizie: un calculator software evaluat în precizie dublă nu se va potrivi cu un RIP bit cu bit, iar limitarea la granița Range poate diferi cu cel mai puțin semnificativ bit față de altă implementare. Pentru afișarea pe ecran și randarea de regresie acest lucru este irelevant; dacă construiți sisteme de proofing cu management de culoare, transformarea nuanței este doar prima etapă și veți avea în continuare nevoie de un motor CMM real în aval
CalGray, CalRGB, Lab și ICCBased fără un motor ICC
Familiile bazate pe CIE folosesc cealaltă ramură a aceluiași rezolvator. HotPDF convertește valorile Lab prin lanțul standard Lab la XYZ la sRGB, incluzând punctul de întrerupere cubic 6/29 în funcția de transfer inversă, și gestionează CalRGB cu gama sa pe canal plus o matrice liniară 3x3, iar CalGray cu o singură gamă. Detaliul relevant pentru performanță este gestionarea punctului de alb: matricea de conversie XYZ la sRGB este adaptată prin metoda Bradford la punctul de alb declarat în spațiul de culoare și stocată în cache în înregistrarea THPDFColorSpace rezolvată, astfel încât operațiunea per-pixel rămâne o singură înmulțire 3x3, indiferent de cât de exotic este iluminantul declarat
Spațiile ICCBased primesc un tratament deliberat pragmatic. Specificația PDF impune ca fiecare flux ICCBased să declare un spațiu /Alternate sau unul implicit prin intermediul numărului de componente /N, tocmai pentru ca vizualizatoarele fără un motor de gestionare a culorilor să poată reda totuși paginile într-un mod rezonabil. HotPDF rezolvă ICCBased prin intermediul acelei alternative sau deduce DeviceGray, DeviceRGB sau DeviceCMYK din valoarea lui /N (1, 3 sau 4) când intrarea lipsește, și nu analizează niciodată octeții profilului. Acest lucru înseamnă că nu există nicio dependență de lcms și niciun cost de căutare a profilului, cu prețul acurateții colorimetrice: un spațiu ICCBased al cărui profil diferă puternic de alternativa sa va afișa randarea alternativei. Pentru afișarea pe ecran și miniaturi, acesta este același compromis pe care îl face orice vizualizator simplu, și este limita pe care trebuie să o menționați clar în propria documentație
Eroarea care a făcut ca orice căutare a spațiului de culoare după nume să dea greș
Structura de operatori din v2.333.0 a fost livrată cu un defect care a rămas invizibil timp de patruzeci și două de versiuni: handlerele cs și CS își căutau operandul cu slash-ul inițial intact (/CS0) în cheile dicționarului de resurse stocate fără slash (CS0). Fiecare căutare de spațiu de culoare după nume a eșuat, în proporție de o sută la sută, iar codul a recurs silențios la valoarea implicită DeviceGray. Simptomul vizibil a fost subtil în cel mai rău mod: o umplere Separation de 1 scn devenea DeviceGray 1.0, care desenează cu alb, iar cerneala albă pe o pagină albă nu este o eroare de randare pe care să o observe cineva cu ușurință. Soluția, introdusă în v2.375.0, a fost un ajutor comun de normalizare a numelor aplicat la fiecare căutare de operand-la-resursă
Două defecte similare au reieșit din aceeași investigație. În primul rând, referințele indirecte către obiecte de tip tablou erau returnate nerezolvate: accesul la document al motorului de randare avea rezolvatoare tipizate doar pentru fluxuri și dicționare, astfel încât /CS0 5 0 R care indica spre un tablou independent [/Separation ...] era returnat ca referință brută, iar spațiul era analizat ca fiind neacceptat. În al doilea rând, HPDFReadNumericArray impunea o semantică strictă de lungime, cerând ca tabloul PDF să fie cel puțin la fel de lung ca bufferul furnizat. Citirea elementelor /C0 și /C1 ale unei funcții Type 2 într-un buffer de patru elemente a eșuat pentru alternativele cu una și trei componente, lăsând ambele tablouri cu valoarea zero, iar fiecare nuanță exponențială non-CMYK s-a randat ca negru până când versiunea v2.376.0 a introdus cititorul permisiv HPDFReadNumericArrayUpTo. Învățătura ce poate fi preluată: orice cheie PDF documentată ca deținând un tablou numeric de lungime variabilă trebuie citită cu un cititor de tipul „umple-ce-există”, deoarece un buffer de dimensiune fixă cu potrivire strictă transformă fișierele valide în valori zero silențioase
Cum se testează randarea culorilor spot fără a vă păcăli singur?
Întrebarea inconfortabilă este de ce suita de teste a rămas pe verde pe parcursul tuturor acestor evenimente. Testul de regresie original, SeparationRendersDistinguishable, aserta doar că imaginea bitmap randată nu era complet neagră. O conductă care reducea fiecare culoare spot la DeviceGray producea rezultate gri și albe, ceea ce nu este complet negru, astfel încât aserțiunea trecea în timp ce întreaga caracteristică era nefuncțională. Aserțiunile slabe de tipul „nu este gol”, „nu este complet negru” sau „rezumatul (digest) este diferit de zero” nu pot deosebi un motor de randare funcțional de unul defect, deoarece aproape orice mod de eșec produce totuși câțiva pixeli
Stilul de aserțiune care detectează efectiv aceste eșecuri blochează culoarea așteptată: randați un PDF minim construit manual a cărui cerneală Separation se rezolvă la o nuanță cunoscută, apoi numărați pixelii care sunt dominanți în acea nuanță. Randarea într-un bitmap pentru inspecție folosește același punct de intrare RenderLoadedPageToBitmap descris în ghidul de randare a paginii în 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;
Fișierul de test (fixture) contează la fel de mult ca aserțiunea. Un PDF construit manual de câteva sute de octeți, cu o singură umplere Separation și nimic altceva, nu lasă nicio ambiguitate cu pragul de rezultate așteptate; un fișier din lumea reală rulează mai mult cod, dar nu vă poate spune care etapă a eșuat. Acum considerăm numărarea pixelilor de culoare așteptată ca fiind pragul minim pentru orice test de randare de tip smoke test, deoarece este singurul stil de aserțiune care a scos la lumină aceste defecte
Randarea culorilor spot și a spațiilor de culoare CIE face parte din conducta pentru documente încărcate din HotPDF Component pentru Delphi și C++Builder, alături de motorul de funcții, randarea umbririlor și căile de export în bitmap prezentate mai sus