Odborný článok

Vykresľovanie výťažkových a DeviceN priamych farieb v Delphi

HotPDF vykresľuje Separation (výťažkové) a DeviceN priame farby (spot colors) na načítaných stránkach PDF tak, že vyhodnotí farebný priestor prostredníctvom HPDFResolveColorSpace, vypočíta transformačnú funkciu odtieňa (tint-transform) pomocou HPDFEvalTintTransform a skonvertuje výsledok cez alternatívny priestor na RGB pre obrazovku. Od verzie v2.375.0 toto spracovanie vyhodnocuje všetky štyri typy funkcií PDF, vrátane PostScript kalkulátorov Type 4, takže tlačovo pripravený súbor s farbami Pantone zobrazí svoje skutočné farby namiesto zástupného symbolu. Tento článok popisuje, ako toto spracovanie funguje a rovnako tak aj to, kde zlyhávalo počas jeho vývoja

Spúšťací scenár je vždy rovnaký. Zákazník dostane PDF z tlačiarne: nadpis je nastavený v pomenovanej priamej farbe (spot ink), obalový dizajn používa zmes dvoch farieb DeviceN a text tela je čisto čierny. V programe Acrobat to vyzerá perfektne. Vo vašej aplikácii v Delphi sa nadpis vykreslí čierny, alebo ešte horšie, nevykreslí sa vôbec, a zákazník nahlási chybu vo vašom softvéri a nie v súbore. Súbor je v poriadku. Vykresľovač len neovláda farebné priestory, ktoré súbor používa

Prečo sa priame farby v prehliadači PDF vykresľujú čierne?

Priame farby sa vykreslia čierne, alebo zmiznú, keď vykresľovač implementuje iba operátory farieb zariadenia (rg, g, k) a ignoruje tie generické. Norma ISO 32000-1 §8.6 definuje tri skupiny farebných priestorov: priestory zariadení (DeviceGray, DeviceRGB, DeviceCMYK), priestory na báze CIE (CalGray, CalRGB, Lab, ICCBased) a špeciálne priestory (Indexed, Separation, DeviceN, Pattern). Všetko mimo skupiny zariadení sa vyberá pomocou generických operátorov: cs a CS zvolia priestor podľa názvu zo slovníka prostriedkov stránky, potom sc, SC, scn, a SCN dodajú hodnoty komponentov. Vykresľovač, ktorý tieto operátory preskočí, si ponechá akúkoľvek naposledy nastavenú farbu, čo pri stránke začínajúcej nadpisom v priamej farbe znamená predvolenú čiernu DeviceGray

HotPDF pridal generickú sadu operátorov do svojho vykresľovača stránok vo verzii v2.333.0, spolu s unifikovanou cestou vyhodnocovania: každá položka prostriedkov /ColorSpace, či už ide o prostý názov, vložené pole alebo nepriamy odkaz, je analyzovaná do jedného záznamu THPDFColorSpace a každá požiadavka na farbu výplne alebo ťahu prechádza cez jediné volanie HPDFResolveColor. Výpočet rodín ukazuje pokrytie na prvý pohľad

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;

Jedno rozhodnutie v návrhu prináša svoje ovocie opakovane: csfUnsupported (nepodporovaný farebný priestor) je plnohodnotná rodina a nie chyba. Priestor, ktorý vykresľovač nedokáže interpretovať, prejde na definovanú záložnú možnosť namiesto zrušenia vykresľovania stránky, čo zodpovedá správaniu bežných prehliadačov a zabraňuje tomu, aby jedna exotická výplň zneplatnila inak bezchybný dokument

Ako transformácia odtieňa premení hodnotu farby na skutočnú farbu?

Priestor typu Separation (výťažkový) nesie tri informácie: názov farby (ink name), alternatívny farebný priestor a funkciu transformácie odtieňa (tint-transform). Pole [/Separation /PANTONE485 /DeviceCMYK f] hovorí: keď prúd obsahu zapíše 0.8 scn, odovzdaj hodnotu odtieňa 0.8 funkcii f a vykresli výslednú CMYK štvoricu. DeviceN toto zovšeobecňuje na N farieb s funkciou s N vstupmi. Samotný názov farby na obrazovke slúži len ako odporúčanie; transformácia odtieňa predstavuje celú sémantiku vykresľovania, takže vykresľovač, ktorý analyzuje priestor, ale preskočí funkciu, neurobil nič užitočné

Metóda HPDFEvalTintTransform je funkčným jadrom na pozadí tohto kroku. Zavedená vo verzii v2.334.0, vyhodnocuje exponenciálne funkcie Type 2 (C0 + x^N * (C1 - C0) s orezaním Domain a Range), spájacie funkcie Type 3 (rekurzia podfunkcie vybraná podľa Bounds s premapovaním Encode) a vzorkované funkcie Type 0 s 8-, 16- a 32-bitovými vzorkami. Typ 0 je rovnaký systém vyhľadávacích tabuliek, aký sme popísali z pohľadu generovania v článku o zostavovaní 3D farebných tabuliek LUT typu 0; vykresľovacia strana prechádza rovnakú štruktúru v opačnom poradí, od dekódovaných bajtov vzoriek späť k hodnotám komponentov

Funkcie PostScript kalkulátora Type 4 boli poslednou prekážkou. Do verzie v2.375.0 prechádzali na neutrálny zástupný symbol; od verzie v2.375.0 metóda HPDFEvalPostScriptCalculator vykonáva plnú sadu operátorov podľa normy ISO 32000-1 §7.10.5 na obmedzenom zásobníku operandov: aritmetické, porovnávacie, logické a bitové operátory, manipuláciu so zásobníkom vrátane roll a podmienky if/ifelse. Okrajové sémantické pravidlá sú prísnejšie, než sa zdá. PostScriptový operátor round zaokrúhľuje polovicu smerom k vyššej hodnote, takže nemožno použiť bankárske zaokrúhľovanie Round v Delphi; goniometrické operátory pracujú v stupňoch, pričom atan vracia hodnoty v [0, 360) a exp predstavuje mocninu s dvoma operandmi, nie prirodzený exponenciál. Rovnaký vyhodnocovač riadi aj gradientné výplne založené na funkciách, čo je dôvod, prečo vykresľovanie axiálneho a radiálneho tieňovania získalo v rovnakej verzii PostScriptom riadené prechody

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

Úprimne o presnosti: softvérový kalkulátor vyhodnocovaný v presnosti double nebude zodpovedať RIP enginu bit po bite a orezanie na hraniciach rozsahu Range sa môže o najmenej významný bit líšiť od inej implementácie. Pre zobrazenie na obrazovke a regresné testovanie je to irelevantné; ak staviate farebne riadený nátlačok (color-managed proofing), transformácia odtieňa je len prvou fázou a na ďalšom stupni stále potrebujete skutočný CMM engine

CalGray, CalRGB, Lab a ICCBased bez ICC enginu

Rodiny založené na CIE využívajú druhú vetvu rovnakého vyhodnocovača. HotPDF konvertuje hodnoty Lab prostredníctvom štandardného reťazca Lab do XYZ do sRGB, vrátane zlomovej kocky 6/29 v inverznej prenosovej funkcii, a spracováva CalRGB s jeho gama hodnotou na kanál plus 3x3 lineárnou maticou a CalGray s jeho jedinou gama hodnotou. Detailom dôležitým pre výkon je spracovanie bieleho bodu: konverzná matica z XYZ do sRGB je pomocou Bradfordovej metódy adaptovaná na biely bod deklarovaný vo farebnom priestore a uložená do pamäte (cache) v zázname THPDFColorSpace, takže operácia na pixel zostáva jednoduchým násobením 3x3, bez ohľadu na to, aký exotický je deklarovaný zdroj svetla

Priestory typu ICCBased dostávajú zámerne pragmatické spracovanie. Špecifikácia PDF vyžaduje, aby prúdy ICCBased deklarovali alternatívny priestor /Alternate alebo predpokladaný priestor na základe počtu komponentov /N, presne preto, aby prehliadače bez enginu správy farieb dokázali súbor zobraziť zmysluplne. HotPDF vyhodnocuje ICCBased cez túto alternatívu, alebo odhadne DeviceGray, DeviceRGB či DeviceCMYK z počtu /N rovnajúcemu sa 1, 3 alebo 4, ak táto položka chýba, a samotné bajty profilu nikdy neanalyzuje. To znamená nulovú závislosť na lcms a nulové náklady na vyhľadávanie profilu, avšak za cenu farebnej presnosti: priestor ICCBased, ktorého profil sa výrazne líši od jeho alternatívy, zobrazí alternatívne vykreslenie. Pre zobrazenie na obrazovke a miniatúry je to rovnaký kompromis, aký robí každý ľahký prehliadač, a toto obmedzenie by ste mali jasne uviesť vo svojej vlastnej dokumentácii

Chyba, ktorá spôsobila zlyhanie každého vyhľadania pomenovaného farebného priestoru

Obsluha operátorov vo verzii v2.333.0 bola vydaná s chybou, ktorá zostala neviditeľná počas štyridsiatich dvoch vydaní: spracovanie cs a CS vyhľadávalo svoj operand s ponechanou úvodnou lomkou (/CS0) voči kľúčom slovníka prostriedkov, ktoré boli uložené bez lomky (CS0). Každé vyhľadávanie pomenovaného farebného priestoru preto zlyhalo, a to v sto percentách prípadov, a kód potichu prešiel na predvolený priestor DeviceGray. Viditeľný príznak bol rafinovaný v tom najhoršom zmysle: výplň Separation s hodnotou 1 scn sa stala DeviceGray 1.0, čo vykresľuje bielu farbu, a biely atrament na bielej stránke nie je chybou vykresľovania, ktorú by niekto zachytil na snímke obrazovky. Nápravou vo verzii v2.375.0 bol zdieľaný pomocník pre normalizáciu názvov, aplikovaný pri každom vyhľadávaní operandu v prostriedkoch

Z rovnakého vyšetrovania vyplynuli dve príbuzné chyby. Po prvé, nepriame odkazy na objekty vo forme poľa sa vracali nevyriešené: prístup k dokumentu vo vykresľovači mal typové rozlišovače iba pre prúdy a slovníky, takže /CS0 5 0 R ukazujúci na samostatné pole [/Separation ...] sa vrátil ako čistý odkaz a priestor sa vyhodnotil ako nepodporovaný. Po druhé, funkcia HPDFReadNumericArray vynucovala prísnu sémantiku dĺžky, vyžadujúc, aby pole PDF bolo aspoň také dlhé ako dodaný buffer. Čítanie kľúčov /C0 a /C1 funkcie Type 2 do štvorprvkového buffra preto zlyhalo pre jedno- a trojkomponentné alternatívy, čo zanechalo obe polia vynulované a každý ne-CMYK exponenciálny odtieň sa vykresľoval ako čierny, kým verzia v2.376.0 nepriniesla benevolentnejší čítač HPDFReadNumericArrayUpTo. Ponaučenie: akýkoľvek kľúč PDF zdokumentovaný ako držiaci číselné pole s premenlivou dĺžkou sa musí čítať pomocou čítača typu „naplň čo existuje“, pretože pevne definovaný buffer s prísnym porovnávaním mení platné súbory na tiché nuly

Ako testovať vykresľovanie priamych farieb bez toho, aby ste klamali sami seba?

Nepríjemnou otázkou zostáva, prečo testovacia sada zostala pri tom všetkom zelená. Pôvodný regresný test, SeparationRendersDistinguishable, overoval len to, že výsledná bitmapa nebola úplne čierna. Spracovanie, ktoré každú priamu farbu zhodilo na DeviceGray, produkovalo sivý a biely výstup, čo nie je čierne, takže asertácia prešla, hoci celá funkcia bola mŕtva. Slabé asertácie typu „nie je prázdny“, „nie je úplne čierny“ alebo „hash je nenulový“ nedokážu rozlíšiť funkčný vykresľovač od nefunkčného, pretože takmer každý chybový stav stále vyprodukuje nejaké pixely

Asertácia, ktorá tieto zlyhania skutočne zachytí, fixuje očakávanú farbu: vykreslite ručne pripravené minimálne PDF, ktorého Separation farba sa vyhodnocuje na známy odtieň, a potom spočítajte pixely, v ktorých tento odtieň dominuje. Vykreslenie do bitmapy na kontrolu využíva rovnaký vstupný bod RenderLoadedPageToBitmap popísaný v príručke pre vykresľovanie stránok do bitmapy

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;

Testovací základ (fixture) je rovnako dôležitý ako samotná asertácia. Ručne zostavené PDF s dĺžkou niekoľko stoviek bajtov s jednou výplňou Separation a ničím iným neponecháva žiadne pochybnosti o tom, aký má byť očakávaný výstup; reálny súbor precvičí viac kódu, ale nedokáže vám povedať, ktorá fáza zlyhala. Počítanie pixelov s dominantnou očakávanou farbou teraz považujeme za minimálnu latku pre akýkoľvek smoke test vykresľovania, pretože je to jediný štýl asertácie, ktorý tieto chyby vynútil na svetlo

Vykresľovanie priamych farieb a farebných priestorov CIE je súčasťou spracovania načítaných dokumentov v komponente losLab PDF Component pre Delphi a C++Builder, spolu s funkčným jadrom, vykresľovaním tieňovania a exportom do bitmapy zobrazeným vyššie