Technical Article

LUT-uri de culoare 3D în PDF cu funcții eșantionate de tip 0

Funcțiile PDF sunt una dintre zonele mai puțin cunoscute ale specificației. Majoritatea dezvoltatorilor se întâlnesc cu ele o singură dată, ca elementul de care are nevoie o umbrire axială de tip 2 pentru a trece treptat între două culori, și nu le mai acordă atenție. Aceasta este o păcat, deoarece mecanismul de funcții este un mic evaluator de uz general pe care formatul îl reutilizează pentru umbriri, funcții de transfer, funcții punct de semiton, nuanțe de separare și curbe de transfer pentru mască soft. Dintre cele patru tipuri de funcții, Tipul 0 este cel mai puternic și cel mai puțin înțeles. Es o funcție eșantionată (sampled function): o grilă multidimensională de valori de ieșire între care cititorul interpolează. Deoarece grila poate conține orice numere introduceți în ea, o funcție de Tip 0 poate exprima o mapare neliniară arbitrară, care este forma exactă a unui tabel de căutare a culorilor (LUT).

Acest articol parcurge dicționarul de Tip 0 așa cum îl definește ISO 32000-1 în §7.10.2, apoi arată cele două cazuri care contează cel mai mult într-un flux de lucru al documentelor: un LUT de corecție a culorilor RGB-la-RGB cu trei intrări și o transformare a nuanței culorii spot cu o singură intrare. Același constructor de funcții eșantionate le servește pe ambele, iar diferența dintre ele este în întregime o chestiune de câte intrări are grila.

O funcție eșantionată este o grilă pe care cititorul o interpolează

O funcție eșantionată mapează un vector de intrare cu m componente la un vector de ieșire cu n componente stocând eșantioane pe o grilă regulată și interpolând între ele. ISO 32000-1 §7.10.2 listează cheile care descriu acea grilă. /Domain deține două numere per intrare, limita inferioară și superioară a fiecărei axe de intrare. /Range deține două numere per componentă de ieșire. /Size este un tablou de m întregi care indică numărul de eșantioane de-a lungul fiecărei axe de intrare, astfel încât o grilă cu douăsprezece eșantioane pe latură în trei dimensiuni are /Size [12 12 12] și stochează 1.728 de puncte de grilă. /BitsPerSample setează precizia fiecărei valori stocate; HotPDF acceptă 1, 2, 4, 8, 12, 16, 24 și 32 de biți, corespunzând valorilor permise de Tabelul 38.

Fluxul de eșantioane este citit într-o ordine fixă. Prima dimensiune de intrare variază cel mai rapid, apoi a doua și așa mai departe, iar la fiecare punct de grilă cele n componente de ieșire sunt stocate în ordine. Pentru un tabel RGB-la-RGB, acesta înseamnă trei octeți per punct de grilă la opt biți, aranjați în ordinea ieșire roșu, ieșire verde, ieșire albastru, parcurgând mai întâi intrarea roșie. Încă două chei mapează lumea continuă pe grila de întregi. /Encode mapează fiecare intrare din intervalul său /Domain la intervalul indicelui de eșantion 0 la Size[i] - 1, iar /Decode mapează întregii bruti stocați înapoi la intervalele /Range. Când le lăsați la valorile lor implicite, o intrare care acoperă [0 1] se plasează curat pe întreaga grilă, iar un octet stocat de 255 se decodifică la limita superioară a intervalului său de ieșire, ceea ce este exact ceea ce își dorește un LUT de culoare normalizat pe intervalul [0,1].

Ordinul 1 versus Ordinul 3

Între punctele grilei, cititorul trebuie să interpoleze, iar /Order alege cum. /Order 1 este interpolare multiliniară: liniară pe o axă, biliniară pe două, triliniară pe trei. Este rapidă, este exact ceea ce face hardware-ul din majoritatea vizualizatoarelor și, pentru o transformare lină a culorilor, este de obicei imposibil de distins de ceva mai sofisticat. /Order 3 solicită interpolare prin spline cubice, care potrivește o curbă mai lină prin eșantioane cu prețul unui volum mai mare de muncă și a unei regiuni de suport mai largi în jurul fiecărui punct evaluat.

Compromisul este densitatea grilei versus netezimea curbei. Ordinul cubic își justifică existența atunci când grila este rară și maparea are o curbură vizibilă, deoarece o linie dreaptă între două eșantioane îndepărtate poate aplatiza o curbă de ton într-un mod pe care ochiul îl sesizează la gradienți. Odată ce grila este densă, segmentele sunt suficient de scurte pentru ca interpolarea liniară să urmărească îndeaproape curba, iar ordinul cubic aduce puține beneficii. O regulă practică este să apelați la /Order 3 numai în cazul grilelor mici sau al transformărilor abrupte, iar în caz contrar să îl lăsați la valoarea implicită liniară. Rețineți că /Order se aplică numai funcțiilor de Tip 0, iar HotPDF respinge orice altă valoare în afară de 1 sau 3.

LUT-ul 3D: trei intrări, trei ieșiri

O corecție a culorilor RGB-la-RGB este cazul clasic pentru o grilă cu trei intrări, clasicul LUT 3D utilizat în calibrarea culorilor și potrivirea dispozitivelor. Fiecare axă a cubului este un canal de intrare, fiecare punct al grilei stochează tripletul RGB corectat pentru acea coordonată de intrare, iar cititorul interpolează triliniar eșantioanele din colțuri în jurul oricărei culori de intrare. Trei intrări sunt inevitabile aici deoarece roșul corectat poate depinde de verdele și albastrul de intrare, nu doar de roșul de intrare; o curbă per canal nu poate exprima diafonia canalelor (crosstalk), dar un cub poate.

HotPDF construiește fluxul de Tip 0 prin intermediul RegisterSampledFunction, care preia direct valorile /Domain, /Range, /Size, /BitsPerSample și octeții de eșantion și returnează obiectul funcție. Pentru un cub standard normalizat, transmiteți limitele [0,1] pentru toate cele trei axe de intrare și toate cele trei ieșiri, o dimensiune N x N x N și tabelul de eșantioane aplatizat. Constructorul validează că numărul de octeți corespunde grilei: pentru o adâncime aliniată la octet, se așteaptă la OutputCount x (BitsPerSample div 8) x produsul dimensiunilor și generează o eroare dacă tabloul are o lungime greșită, astfel încât un pas (stride) calculat greșit eșuează zgomotos la înregistrare, în loc să fie redat ca date corupte mai târziu.

const
  N = 17;  // 17 x 17 x 17 cube, the common ICC LUT resolution
var
  LutFn: THPDFStreamObject;
  Samples: TBytes;
begin
  // Fill Samples with N*N*N grid points, 3 bytes each (R,G,B output),
  // red input varying fastest. Build the corrected triple for each
  // grid coordinate with your ICC-managed conversion, then store it.
  SetLength(Samples, N * N * N * 3);
  BuildCorrectedCube(Samples, N);   // your color-managed fill

  LutFn := Pdf.RegisterSampledFunction(
    [0,1, 0,1, 0,1],
    [0,1, 0,1, 0,1],
    [N, N, N],
    8,
    Samples,
    1);
end;

Corectitudinea colorimetrică a cubului rezidă în modul în care îl umpleți, nu în funcția PDF. Calea corectă este de a calcula fiecare punct al grilei printr-o conversie gestionată prin ICC, același motor care controlează un soft-proof, astfel încât numerele din grilă să însemne ceva în raport cu un profil sursă și destinație definit. Înregistrați profilurile care delimitează conversia cu RegisterICCProfile, care înregistrează un spațiu de culoare ICCBased (1, 3 sau 4 componente) și returnează un nume de resursă pe care îl puteți atașa conținutului pe care îl alimentează LUT-ul. Funcția de Tip 0 poartă tabelul de interpolare; profilul ICC poartă semnificația punctelor finale.

Cazul 1D: o transformare a nuanței culorii spot

Spațiile de culoare de tip Separation se bazează pe același mecanism pentru o sarcină complet diferită. Un spațiu Separation, definit în ISO 32000-1 §8.6.6.4, reprezintă un singur colorant, o cerneală spot cum ar fi un Pantone sau un lac, prin asocierea unui nume cu o transformare a nuanței (tint transform): o funcție care mapează valoarea unidimensională a nuanței, de la 0 pentru lipsă cerneală la 1 pentru cerneală completă, pe un spațiu de culoare alternativ pe care dispozitivul îl poate reda de fapt, de obicei CMYK. Acea transformare a nuanței este frecvent o funcție de Tip 0, iar acum grila are exact o axă de intrare.

Acesta este contrastul clar cu LUT-ul 3D. O cerneală spot reprezintă un singur grad de libertate, așa că transformarea sa de nuanță are nevoie de o singură intrare, iar grila este o linie de eșantioane, fiecare conținând valoarea CMYK (sau altă valoare alternativă) la acel nivel de nuanță. Cubul RGB are nevoie de trei intrări deoarece domeniul său este tridimensional, iar canalele interacționează. Același tip de funcție, aceleași reguli de interpolare, dimensionalitate diferită; specificația reutilizează un singur evaluator și lasă ca /Size să decidă dacă parcurgeți o linie, un plan sau un cub. HotPDF împachetează întreaga separare în RegisterSeparationLUT, care construiește intern transformarea nuanței de Tip 0 cu o singură intrare dintr-un tablou de octeți plat și returnează numele resursei spațiului de culoare.

var
  SpotCS: AnsiString;
begin
  // Four CMYK output bytes per tint grid point, tint domain [0..1].
  // Here 0% ink -> all zero, 100% ink -> a rich spot build,
  // with two interior steps; the tint transform interpolates between.
  SpotCS := Pdf.RegisterSeparationLUT(
    'PANTONE 286 C',         // colorant name
    'DeviceCMYK',            // alternate color space
    [  0,   0,   0,   0,     // tint 0.00 -> 0,0,0,0
      90,  60,   0,   0,     // tint 0.33
     100,  80,   0,  10,     // tint 0.66
     100,  72,   0,  18]);   // tint 1.00 -> full ink build
  // Use SpotCS with SetFillColorSpace / SetFillColor on a page.
end;

Numărul de eșantioane trebuie să fie un număr întreg de puncte de grilă: un multiplu pozitiv al numărului de componente al spațiului alternativ și cel puțin două puncte pentru a exista un segment de interpolat. Dacă transmiteți trei octeți per punct în raport cu o alternativă CMYK, apelul îl respinge, aceeași validare defensivă pe care o aplică constructorul 3D, ceea ce este exact ceea ce vă doriți de la o funcție care altfel ar eșua în mod silențios la tipărire.

Unde apare din nou același mecanism

Odată ce priviți Tipul 0 ca pe un tabel generic de interpolare, alte două caracteristici de control al dispozitivului nu mai par a fi cazuri speciale. O funcție de transfer ajustează valorile componentelor în drumul lor către dispozitivul de ieșire și este doar o funcție per canal; HotPDF o înregistrează ca un ExtGState prin intermediul RegisterTransferFunctionState, care acceptă fie o singură funcție combinată, fie un tablou de funcții per canal. Deoarece acele funcții sunt obiecte funcție obișnuite, îi puteți transmite chiar obiectul THPDFStreamObject pe care îl returnează RegisterSampledFunction și puteți controla o curbă de transfer dintr-un tabel eșantionat, în loc de o formulă.

var
  ToneFn: THPDFStreamObject;
  GsName: AnsiString;
begin
  // A single-input, single-output sampled tone curve on [0,1].
  ToneFn := Pdf.RegisterSampledFunction(
    [0,1], [0,1], [256], 8, ToneCurveBytes, 1);

  // Apply it to all channels as a combined /TR2 transfer function.
  GsName := Pdf.RegisterTransferFunctionState(ToneFn, []);
  // Select GsName on the page before drawing the affected content.
end;

Generarea negrului (black generation) și eliminarea culorilor de dedesubt (undercolor removal) fac parte din aceeași familie. Când un dispozitiv convertește RGB în CMYK, acesta decide cât de mult din componenta de gri să poarte ca cerneală neagră, iar specificația exprimă acea decizie ca o funcție, intrările /BG2 și /UCR2 ale unui dicționar de stare grafică, fiecare fiind o curbă cu o singură intrare de la griul calculat la o cantitate de negru. Acestea sunt, de asemenea, funcții de Tip 0 atunci când doriți o curbă măsurată în locul uneia analitice, construite în același mod prin RegisterSampledFunction și plasate în starea grafică. Lecția care merită reținută este că funcția PDF nu este niciodată locul unde are loc gestionarea culorilor; este tabelul de căutare care poartă o decizie pe care ați luat-o cu un motor de culoare real, iar Tipul 0 este singurul tip de funcție suficient de flexibil pentru a purta orice decizie.

Pentru o imagine mai largă a modului în care fonturile, imaginile și resursele de culoare sunt emise într-un document final, consultați ghidul nostru pentru realizarea rapoartelor cu fonturi și imagini. Când documentul final trebuie să supraviețuiască unei verificări preflight de arhivare sau tipărire, regulile de spațiu de culoare și de intenție de ieșire acoperite în ghidul de validare PDF/A, PDF/X și PDF/UA guvernează care dintre aceste funcții sunt permise și cum trebuie etichetată culoarea dispozitivului. Totul este livrat în HotPDF Component pentru Delphi și C++Builder, alături de API-urile de umbrire, ICC și separare care se bazează pe același nucleu de Tip 0.