Technical Article

Iscrtavanje Separation i DeviceN spot boja u Delphiju

HotPDF iscrtava Separation i DeviceN spot boje na učitanim PDF stranicama tako što razrješava prostor boja kroz HPDFResolveColorSpace, evaluira funkciju transformacije tona (tint-transform) pomoću HPDFEvalTintTransform i pretvara rezultat kroz alternativni prostor u RGB za zaslon. Od verzije v2.375.0 taj cjevovod evaluira sva četiri tipa PDF funkcija, uključujući Type 4 PostScript kalkulatore, tako da datoteka spremna za tisak s Pantone tintama prikazuje svoje stvarne boje umjesto rezerviranog mjesta (placeholder). Ovaj članak objašnjava kako taj cjevovod radi i, jednako korisno, kako je zakazao dok smo ga gradili

Okidač je uvijek isti. Kupac prima PDF iz tiskare: naslov je postavljen u imenovanoj spot boji, dizajn pakiranja koristi mješavinu dviju DeviceN tinti, a tekst tijela je običan crn. U Acrobat-u to izgleda savršeno. U vašoj Delphi aplikaciji naslov se iscrtava u crnoj boji ili, još gore, uopće se ne iscrtava, a kupac prijavljuje pogrešku protiv vašeg softvera umjesto protiv datoteke. Datoteka je u redu. Iscrtavač samo ne 'govori' prostore boja koje datoteka koristi

Zašto se spot boje iscrtavaju crno u PDF pregledniku?

Spot boje se iscrtavaju crno, ili nestaju, kada iscrtavač implementira samo operatore boja uređaja (rg, g, k) i zanemaruje one generičke. ISO 32000-1 §8.6 definira tri skupine prostora boja: prostori uređaja (DeviceGray, DeviceRGB, DeviceCMYK), prostori temeljeni na CIE (CalGray, CalRGB, Lab, ICCBased) i posebni prostori (Indexed, Separation, DeviceN, Pattern). Sve izvan skupine uređaja odabire se generičkim operatorima: cs i CS biraju prostor prema nazivu iz rječnika resursa stranice, a zatim sc, SC, scn i SCN isporučuju vrijednosti komponenti. Iscrtavač koji preskače te operatore zadržava boju koja je zadnja postavljena, što je za stranicu koja počinje naslovom u spot boji početna crna boja DeviceGray

HotPDF je dodao skup generičkih operatora svom iscrtavaču stranica u verziji v2.333.0, zajedno s jedinstvenom stazom razrješavanja: svaki unos resursa /ColorSpace, bilo da se radi o čistom nazivu, ugrađenom nizu ili neizravnoj referenci, analizira se u jedan zapis THPDFColorSpace, a svaki zahtjev za ispunom ili bojom poteza prolazi kroz jedan jedini poziv HPDFResolveColor. Nabrajanje obitelji (family enumeration) na prvi pogled pokazuje pokrivenost

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;

Jedna dizajnerska odluka višestruko se isplati: csfUnsupported is a obitelj prvog reda (first-class), a ne pogreška. Prostor koji iscrtavač ne može interpretirati degradira se na definirano zamjensko rješenje (fallback) umjesto prekida iscrtavanja stranice, što odgovara ponašanju uobičajenih preglednika i sprječava da jedna egzotična ispuna isprazni inače iscrtiv dokument

Kako transformacija tona pretvara vrijednost tinti u stvarnu boju?

Separation prostor nosi tri informacije: naziv tinte, alternativni prostor boja i funkciju transformacije tona. Niz [/Separation /PANTONE485 /DeviceCMYK f] kaže: kada struja sadržaja napiše 0.8 scn, proslijedi vrijednost tona 0.8 u funkciju f i oboji rezultirajući CMYK četverac. DeviceN to generalizira na N tinti s funkcijom od N ulaza. Sam naziv tinte je samo savjetodavan na zaslonu; transformacija tona predstavlja cjelokupnu semantiku iscrtavanja, pa iscrtavač koji analizira prostor, ali preskače funkciju, još nije učinio ništa korisno

HPDFEvalTintTransform je funkcijski mehanizam iza tog koraka. Uveden u verziji v2.334.0, on evaluira eksponencijalne funkcije Type 2 (C0 + x^N * (C1 - C0) s ograničavanjem Domain i Range), funkcije spajanja (stitching) Type 3 (rekurzija podfunkcija odabranih granicama s Encode remapiranjem) i uzorkovane funkcije Type 0 s 8-, 16- i 32-bitnim uzorcima. Type 0 je isti mehanizam tablice pretraživanja (lookup table) koji smo pokrili sa strane stvaranja u članku o izgradnji Type 0 tablica pretraživanja boja; strana iscrtavanja prolazi kroz identičnu strukturu unatrag, od dekodiranih bajtova uzorka natrag do vrijednosti komponenti

Funkcije PostScript kalkulatora Type 4 bile su posljednje uporište. Do verzije v2.375.0 one su se elegantno degradirale u neutralno rezervirano mjesto; od verzije v2.375.0 HPDFEvalPostScriptCalculator izvršava cijeli skup operatora iz ISO 32000-1 §7.10.5 na ograničenom stogu operanda: aritmetičke operatore, usporedbe, logičke i bitovne operatore, manipulaciju stogom uključujući roll te uvjete if/ifelse. Granična semantika je stroža nego što izgleda. PostScript-ov round zaokružuje polovicu prema većoj vrijednosti, pa se Delphi-jevo bankarsko zaokruživanje Round ne može koristiti; trigonometrijski operatori rade u stupnjevima, pri čemu atan vraća vrijednosti u [0, 360), a exp je potencija s dva operanda, a ne prirodna eksponencijalna funkcija. Isti evaluator također upravlja ispunama gradijenta temeljenim na funkcijama, zbog čega je aksijalno i radijalno iscrtavanje sjenčanja dobilo rampe vođene kalkulatorom u istom izdanju

// 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;

Iskreno o preciznosti: softverski kalkulator evaluiran u dvostrukoj preciznosti neće odgovarati RIP-u bit po bit, a ograničavanje na granici raspona (Range) može se razlikovati za najmanje značajan bit od druge implementacije. Za prikaz na zaslonu i regresijsko iscrtavanje to je nevažno; ako gradite provjeru pod upravljanjem bojama (color-managed proofing), transformacija tona je samo prva faza i još uvijek trebate pravi CMM nakon toga

CalGray, CalRGB, Lab i ICCBased bez ICC mehanizma

Obitelji temeljene na CIE uzimaju drugu granu istog razrješavatelja. HotPDF pretvara Lab vrijednosti kroz standardni lanac Lab u XYZ pa u sRGB, uključujući kubični prijelom 6/29 u inverznoj prijenosnoj funkciji, te rukuje CalRGB-om s njegovom gamom po kanalu plus linearnom matricom 3x3 i CalGray-om s njegovom jednom gamom. Detalj važan za performanse je rukovanje bijelom točkom: matrica pretvorbe XYZ u sRGB je Bradford-prilagođena bijeloj točki deklariranoj u prostoru boja i predmemorirana (cached) u razriješenom zapisu THPDFColorSpace, tako da rad po pikselu ostaje jedno množenje 3x3 bez obzira na to koliko je deklarirani izvor svjetlosti (illuminant) egzotičan

ICCBased prostori dobivaju namjerno pragmatičan tretman. PDF specifikacija zahtijeva da svaka ICCBased struja deklarira prostor /Alternate ili implicirani prostor putem broja komponenti /N, upravo zato da bi preglednici bez mehanizma za upravljanje bojama i dalje mogli razumno iscrtavati. HotPDF razrješava ICCBased kroz tu alternativu, ili pogađa DeviceGray, DeviceRGB ili DeviceCMYK na temelju toga što je /N 1, 3 ili 4 kada unos nedostaje, i nikada ne analizira bajtove profila. To znači da nema ovisnosti o lcms-u i nema troška pretraživanja profila, po cijeni kolorimetrijske točnosti: ICCBased prostor čiji profil snažno odstupa od svoje alternative prikazat će alternativni prikaz. Za prikaz na zaslonu i sličice (thumbnails) to je isti kompromis koji radi svaki lagani preglednik, i to je granica koju trebate jasno navesti u vlastitoj dokumentaciji

Pogreška zbog koje je svako pretraživanje imenovanog prostora boja promašilo

Cjevovod operatora iz verzije v2.333.0 isporučen je s nedostatkom koji je ostao nevidljiv četrdeset i dva izdanja: rukovatelji cs i CS tražili su svoj operand, s netaknutom vodećom kosom crtom (/CS0), u ključevima rječnika resursa pohranjenim bez kose crte (CS0). Svako pretraživanje imenovanog prostora boja je promašilo, sto posto vremena, a kôd se tiho vratio na zadani DeviceGray. Vidljivi simptom bio je suptilan na najgori način: Separation ispuna od 1 scn postala je DeviceGray 1.0, što boja u bijelo, a bijela tinta na bijeloj stranici nije pogreška iscrtavanja koju bi netko primijetio na snimci zaslona. Rješenje u verziji v2.375.0 bio je zajednički pomoćnik za normalizaciju naziva koji se primjenjuje pri svakom pretraživanju operanda u resursima

Dva srodna nedostatka proizašla su iz iste istrage. Prvo, neizravne reference na objekte tipa niza vraćane su nerazriješene: pristup dokumentu iscrtavača imao je tipizirane razrješavatelje samo za struje i rječnike, pa se /CS0 5 0 R koji pokazuje na samostalni niz [/Separation ...] vraćao kao sirova referenca i prostor se analizirao kao nepodržan. Drugo, HPDFReadNumericArray je provodio strogu semantiku duljine, zahtijevajući da PDF niz bude barem onoliko dug koliko je isporučeni međuspremnik (buffer). Čitanje /C0 i /C1 funkcije Type 2 u četveročlani međuspremnik stoga nije uspjelo za alternative s jednom i tri komponente, ostavljajući oba niza na nuli, pa se svaki ne-CMYK eksponencijalni ton iscrtavao crno sve dok verzija v2.376.0 nije uvela popustljiv čitač HPDFReadNumericArrayUpTo. Lekcija koju možemo prenijeti: svaki PDF ključ za koji je dokumentirano da drži numerički niz varijabilne duljine mora se čitati pomoću čitača koji popunjava ono što postoji, jer međuspremnik fiksne veličine sa strogim podudaranjem pretvara valjane datoteke u tihe nule

Kako testirati iscrtavanje spot boja a da ne zavarate sami sebe?

Neugodno pitanje je zašto je skup testova ostao zelen kroz sve ovo. Izvorni regresijski test, SeparationRendersDistinguishable, tvrdio je samo da iscrtana bitmapa nije u potpunosti crna. Cjevovod koji je svaku spot boju odbacivao na DeviceGray proizvodio je sivi i bijeli izlaz, što nije crno, pa je tvrdnja (assertion) prolazila iako je cijela značajka bila mrtva. Slabe tvrdnje u obliku 'nije prazno', 'nije potpuno crno' ili 'sažetak (digest) nije nula' ne mogu razlikovati ispravan iscrtavač od pokvarenog, jer gotovo svaki neuspjeh i dalje proizvodi neke piksele

Stil tvrdnje koji zapravo hvata ove neuspjehe zaključava očekivanu boju: iscrtajte ručno izrađen minimalni PDF čija se Separation tinta razrješava u poznatu nijansu, a zatim prebrojite piksele u kojima dominira ta nijansa. Iscrtavanje u bitmapu za pregled koristi istu ulaznu točku RenderLoadedPageToBitmap opisanu u vodiču za iscrtavanje stranice u bitmapu

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;

Testni predložak (fixture) važan je koliko i tvrdnja. Ručno izrađen PDF dug nekoliko stotina bajtova, s jednom Separation ispunom i ničim drugim, ne ostavlja nikakvu dvosmislenost o tome kakav je očekivani izlaz; datoteka iz stvarnog svijeta pokreće više koda, ali vam ne može reći koji je korak zakazao. Sada broj piksela očekivane boje tretiramo kao minimalnu ljestvicu za bilo koji smoke test iscrtavanja, jer je to jedini stil tvrdnje koji je natjerao ove nedostatke na vidjelo

Iscrtavanje spot boja i CIE prostora boja dio je cjevovoda za učitane dokumente u HotPDF komponenti za Delphi i C++Builder, uz funkcijski mehanizam, iscrtavanje sjenčanja i staze za izvoz bitmape prikazane gore