Tehnički članak

Renderovanje Separation i DeviceN spot boja u Delphi-ju

HotPDF renderuje Separation i DeviceN spot boje na učitanim PDF stranicama tako što razrešava prostor boja preko funkcije HPDFResolveColorSpace, evaluira funkciju transformacije nijanse pomoću HPDFEvalTintTransform i konvertuje rezultat kroz alternativni prostor u RGB za prikaz na ekranu. Od verzije v2.375.0 taj cevovod evaluira sva četiri tipa PDF funkcija, uključujući Type 4 PostScript kalkulatore, tako da fajl spreman za štampu sa Pantone bojama prikazuje svoje stvarne boje umesto zamenskih. Ovaj članak objašnjava kako ovaj proces funkcioniše i, podjednako korisno, kako je otkazivao dok smo ga gradili

Okidač za problem je uvek isti. Korisnik dobija PDF iz štamparije: naslov je definisan u imenovanoj spot boji (spot ink), dizajn pakovanja koristi mešavinu dve boje DeviceN, a tekst tela je čisto crn. U Acrobat-u to izgleda savršeno. U vašoj Delphi aplikaciji naslov se renderuje crno, ili još gore, uopšte se ne prikazuje, a korisnik prijavljuje grešku u vašem softveru umesto u samom fajlu. Fajl je u redu. Samo što program za renderovanje ne govori jezike prostora boja koje fajl koristi

Zašto se spot boje renderuju crno u pregledaču PDF-a?

Spot boje se renderuju crno, ili nestaju, kada program za renderovanje implementira samo operatore boja uređaja (rg, g, k) a ignoriše generičke. ISO 32000-1 §8.6 definiše tri grupe prostora boja: prostori uređaja (DeviceGray, DeviceRGB, DeviceCMYK), CIE-zasnovani prostori (CalGray, CalRGB, Lab, ICCBased) i specijalni prostori (Indexed, Separation, DeviceN, Pattern). Sve izvan grupe uređaja bira se generičkim operatorima: cs i CS biraju prostor po imenu iz rečnika resursa stranice, a zatim sc, SC, scn i SCN isporučuju vrednosti komponenti. Program za renderovanje koji preskače ove operatore zadržava onu boju koja je poslednja bila podešena, što je za stranicu koja počinje naslovom u spot boji početna DeviceGray crna boja

HotPDF je dodao skup generičkih operatora u svoj program za renderovanje stranica u verziji v2.333.0, zajedno sa objedinjenom putanjom razrešavanja: svaki unos resursa /ColorSpace, bilo da je u pitanju čisto ime, ugrađeni niz ili indirektna referenca, analizira se u jedan THPDFColorSpace zapis, a svaki zahtev za boju popune ili linije usmerava se kroz jedinstveni poziv HPDFResolveColor. Nabrajanje porodica 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 odluka u dizajnu se višestruko isplatila: csfUnsupported je porodica prvog reda, a ne greška. Prostor koji program za renderovanje ne može da interpretira degradira se na definisanu rezervnu opciju (fallback) umesto da se prekine prikazivanje stranice, što odgovara ponašanju vodećih pregledača i sprečava da jedna egzotična popuna isprazni čitav dokument koji bi inače mogao da se renderuje

Kako transformacija nijanse pretvara jednu vrednost boje u stvarnu boju?

Prostor Separation nosi tri informacije: ime boje (ink name), alternativni prostor boja i funkciju transformacije nijanse (tint-transform). Niz [/Separation /PANTONE485 /DeviceCMYK f] kaže: kada tok sadržaja zapiše 0.8 scn, prosledi vrednost nijanse 0.8 u funkciju f i oboji rezultujući CMYK četvorostruki skup. DeviceN ovo uopštava na N boja sa funkcijom od N ulaza. Samo ime boje je samo savetodavnog karaktera na ekranu; transformacija nijanse predstavlja celokupnu semantiku renderovanja, tako da program za renderovanje koji analizira prostor a preskače funkciju zapravo nije uradio ništa korisno

Funkcija HPDFEvalTintTransform je pokretački mehanizam iza tog koraka. Uvedena u verziji v2.334.0, ona evaluira eksponencijalne funkcije Type 2 (C0 + x^N * (C1 - C0) sa ograničavanjem Domain i Range), funkcije spajanja Type 3 (rekurzija podfunkcija izabrana preko Bounds sa re-mapiranjem Encode) i uzorkovane funkcije Type 0 sa 8-, 16- i 32-bitnim uzorcima. Tip 0 je isti mehanizam tabele pretraživanja (lookup table) koji smo pokrili sa strane kreiranja u članku o izgradnji LUT tabela boja Type 0; strana za renderovanje prolazi kroz istu strukturu unazad, od dekodiranih bajtova uzoraka nazad do vrednosti komponenti

Kalkulatorske funkcije Type 4 PostScript bile su poslednji izazov. Do verzije v2.375.0 one su se elegantno degradirale na neutralni zamenski znak; od verzije v2.375.0 funkcija HPDFEvalPostScriptCalculator izvršava kompletan skup operatora specifikacije ISO 32000-1 §7.10.5 na ograničenom steku operanada: aritmetičke, operatore poređenja, logičke i bitovne operatore, manipulacije stekom uključujući roll, kao i uslove if/ifelse. Granična semantika je stroža nego što izgleda. PostScript operator round zaokružuje polovinu ka većoj vrednosti, pa se Delphi-jevo bankarsko zaokruživanje Round ne može koristiti; trigonometrijski operatori rade u stepenima, pri čemu atan vraća vrednosti u opsegu [0, 360); a exp je stepen sa dva operanda, a ne prirodni eksponent. Isti evaluator takođe pokreće gradijentne popune zasnovane na funkcijama, zbog čega je renderovanje aksijalnog i radijalnog senčenja dobilo gradijente 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 koji radi u dvostrukoj preciznosti (double precision) neće se poklapati sa RIP-om bit po bit, a odsecanje na granici opsega (Range) može se razlikovati za najmanje značajan bit (LSB) u odnosu na drugu implementaciju. Za prikaz na ekranu i renderovanje regresionih testova to je nevažno; ako gradite profesionalni sistem za proveru sa upravljanjem bojama, transformacija nijanse je samo prva faza, a u nastavku vam je potreban pravi CMM

CalGray, CalRGB, Lab i ICCBased bez ICC mehanizma

Porodice zasnovane na CIE standardu idu drugom granom istog razrešivača. HotPDF konvertuje Lab vrednosti kroz standardni lanac Lab u XYZ u sRGB, uključujući i prelomnu kocku 6/29 u inverznoj funkciji prenosa, i rukuje CalRGB-om sa njegovom gama vrednošću po kanalu plus 3x3 linearnom matricom, kao i CalGray-om sa njegovom jedinstvenom gama vrednošću. Detalj važan za performanse je rukovanje belom tačkom (white point): matrica konverzije XYZ u sRGB je Bredford-adaptirana (Bradford-adapted) na belu tačku deklarisanu u prostoru boja i keširana u razrešenom zapisu THPDFColorSpace, tako da rad po pikselu ostaje jedno množenje matrice 3x3 bez obzira na to koliko je egzotičan izvor svetlosti

Prostori ICCBased dobijaju namerno pragmatičan tretman. PDF specifikacija zahteva da svaki tok ICCBased deklariše alternativni prostor /Alternate or podrazumevani preko svog broja komponenti /N, upravo zato da bi čitači bez mehanizma za upravljanje bojama i dalje mogli razumno da vrše renderovanje. HotPDF razrešava ICCBased kroz tu alternativu, ili pogađa DeviceGray, DeviceRGB ili DeviceCMYK na osnovu toga što je /N 1, 3 ili 4 kada unos nedostaje, i nikada ne analizira bajtove profila. To znači da nema zavisnosti od lcms-a niti troškova pretraživanja profila, po cenu kolorimetrijske preciznosti: ICCBased prostor čiji profil značajno odstupa od svoje alternative prikazaće alternativni render. Za prikaz na ekranu i sličice (thumbnails) to je isti kompromis koji pravi svaki lagani čitač, i to je ograničenje koje treba jasno navesti u sopstvenoj dokumentaciji

Bag koji je činio da svaka pretraga imenovanih prostora boja ne uspe

Instalacija operatora u verziji v2.333.0 isporučena je sa defektom koji je ostao nevidljiv tokom četrdeset dva izdanja: rukovaoci cs i CS tražili su svoj operand sa netaknutom vodećom kosom crtom (/CS0) u odnosu na ključeve rečnika resursa koji su sačuvani bez kose crte (CS0). Svaka pretraga imenovanog prostora boja je promašila, sto posto vremena, a kod se tiho vraćao na podrazumevani DeviceGray. Vidljivi simptom je bio suptilan na najgori mogući način: Separation popuna od 1 scn postajala je DeviceGray 1.0, što boji u belo, a bela boja na beloj stranici nije greška u renderovanju za koju bi iko napravio snimak ekrana. Rešenje, u verziji v2.375.0, bio je zajednički pomoćnik za normalizaciju imena primenjen pri svakoj pretrazi operanda u resursima

Dva srodna defekta uočena su tokom iste istrage. Prvo, indirektne reference na objekte u vidu niza vraćane su nerazrešene: pristup dokumentu od strane programa za renderovanje imao je tipizirane razrešivače samo za tokove i rečnike, pa se /CS0 5 0 R koji ukazuje na samostalni niz [/Separation ...] vraćao kao sirova referenca i prostor se tumačio kao nepodržan. Drugo, funkcija HPDFReadNumericArray je sprovodila strogu semantiku dužine, zahtevajući da PDF niz bude dugačak najmanje onoliko koliko i isporučeni bafer. Čitanje nizova /C0 i /C1 funkcije Type 2 u bafer od četiri elementa stoga nije uspevalo za jednokomponentne i trokomponentne alternative, ostavljajući oba niza na nuli, pa se svaka eksponencijalna nijansa koja nije CMYK renderovala kao crna sve dok verzija v2.376.0 nije uvela popustljivi čitač HPDFReadNumericArrayUpTo. Pouka koja se može preneti: svaki PDF ključ za koji je dokumentovano da drži numerički niz promenljive dužine mora se čitati sa čitačem koji popunjava ono što postoji, jer bafer fiksne veličine sa strogim podudaranjem pretvara ispravne fajlove u tihe nule

Kako testirati renderovanje spot boja bez samozavaravanja?

Neprijatno pitanje je zašto je test paket ostajao zelen kroz sve ovo. Prvobitni regresioni test, SeparationRendersDistinguishable, proveravao je samo da li renderovana bitmapa nije potpuno crna. Cevovod koji je svaku spot boju degradirao na DeviceGray davao je sivi i beli izlaz, što nije crno, pa je tvrdnja (assertion) prolazila iako je čitava funkcionalnost bila mrtva. Slabe tvrdnje tipa "nije prazno", "nije skroz crno" ili "heš vrednost je različita od nule" ne mogu razlikovati funkcionalan program za renderovanje od pokvarenog, jer skoro svaki režim otkazivanja i dalje proizvodi neke piksele

Stil tvrdnji koji zapravo hvata ove greške zaključava očekivanu boju: renderujte ručno napravljen minimalni PDF čija se Separation boja razrešava u poznatu nijansu, a zatim brojite piksele u kojima dominira ta nijansa. Renderovanje u bitmapu radi inspekcije koristi istu ulaznu tačku RenderLoadedPageToBitmap opisanu u vodiču za renderovanje 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;

Test fixture (pripremljeni podaci za test) je važan podjednako kao i tvrdnja. Ručno napravljen PDF od nekoliko stotina bajtova, sa jednom Separation popunom i ničim drugim, ne ostavlja nikakvu dvosmislenost o tome šta je očekivani izlaz; fajl iz stvarnog sveta pokreće više koda ali vam ne može reći koja je faza otkazala. Sada tretiramo brojanje piksela očekivane boje kao minimalni prag za svaki smoke test renderovanja, jer je to jedini stil proveravanja koji je primorao ove defekte da izađu na videlo

Renderovanje spot boja i CIE prostora boja deo je cevovoda za učitane dokumente u HotPDF komponenti za Delphi i C++Builder, zajedno sa mehanizmom funkcija, renderovanjem senčenja i putanjama za izvoz bitmapa prikazanim iznad