Tehnični članek

Izrisovanje Separation in DeviceN spot barv v Delphiju s HotPDF

HotPDF izrisuje spot barve Separation in DeviceN na naloženih straneh PDF tako, da razreši barvni prostor prek HPDFResolveColorSpace, ovrednoti funkcijo transformacije tona z HPDFEvalTintTransform in pretvori rezultat prek nadomestnega prostora v RGB za zaslon. Od različice v2.375.0 ta cevovod vrednoti vse štiri vrste funkcij PDF, vključno s PostScript kalkulatorji Type 4, tako da datoteka, pripravljena za tisk s Pantone črnili, prikaže svoje prave barve namesto nadomestnih. Ta članek opisuje delovanje cevovoda in, kar je prav tako koristno, kako je odpovedal, ko smo ga gradili

Sprožilni primer je vedno enak. Stranka prejme PDF iz tiskarne: naslov je nastavljen v imenovanem spot črnilu, embalaža uporablja mešanico dveh črnil DeviceN, besedilo pa je navadno črno. V programu Acrobat je videti popolno. V vaši aplikaciji v Delphiju pa se naslov izriše črno ali, še huje, se sploh ne izriše, stranka pa prijavi hrošča proti vaši programski opremi in ne proti datoteki. Datoteka je v redu. Izrisovalnik preprosto ne govori barvnih prostorov, ki jih datoteka uporablja

Zakaj se spot barve v pregledovalniku PDF izrišejo črno?

Spot barve se izrišejo črno ali pa izginejo, ko izrisovalnik implementira le operaterje barv naprav (rg, g, k) in prezre generične. Standard ISO 32000-1 §8.6 opredeljuje tri skupine barvnih prostorov: prostori naprav (DeviceGray, DeviceRGB, DeviceCMYK), prostori na osnovi CIE (CalGray, CalRGB, Lab, ICCBased) in posebni prostori (Indexed, Separation, DeviceN, Pattern). Vse zunaj skupine naprav se izbere z generičnimi operaterji: cs in CS izbereta prostor po imenu iz slovarja virov strani, nato pa sc, SC, scn in SCN zagotovijo vrednosti komponent. Izrisovalnik, ki te operaterje preskoči, ohrani barvo, ki je bila nazadnje nastavljena, kajti za stran, ki se začne z naslovom v spot barvi, to pomeni prvotno črno barvo DeviceGray

HotPDF je v različici v2.333.0 dodal nabor generičnih operaterjev v svoj izrisovalnik strani, skupaj z združeno potjo razreševanja: vsak vnos vira /ColorSpace, naj bo to golo ime, matrika v vrstici ali posredna referenca, se razčleni v en zapis THPDFColorSpace, vsaka zahteva za barvo polnjenja ali črte pa se usmeri skozi en sam klic HPDFResolveColor. Enumeracija družin prikazuje pokritost na prvi pogled

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;

Ena oblikovalska odločitev se vedno znova obrestuje: csfUnsupported je polnopravna družina in ne napaka. Prostor, ki ga izrisovalnik ne more interpretirati, se spusti na določen nadomestni prostor, namesto da bi prekinil izrisovanje strani, kar se ujema z obnašanjem večine pregledovalnikov in preprečuje, da bi en sam eksotičen polnilni element izpraznil sicer izrisljiv dokument

Kako transformacija tona pretvori vrednost črnila v pravo barvo?

Prostor Separation nosi tri podatke: ime črnila, nadomestni barvni prostor in funkcijo transformacije tona. Matrika [/Separation /PANTONE485 /DeviceCMYK f] pravi: ko tok vsebine zapiše 0.8 scn, vstavite vrednost tona 0.8 v funkcijo f in izrišite dobljeno četverico CMYK. DeviceN to posploši na N črnil s funkcijo z N vhodi. Samo ime črnila je na zaslonu le informativnega značaja; transformacija tona predstavlja celotno semantiko izrisovanja, zato izrisovalnik, ki razčleni prostor, a preskoči funkcijo, še ni naredil nič koristnega

Metoda HPDFEvalTintTransform je funkcijski pogon za tem korakom. Uveden v različici v2.334.0, vrednoti eksponentne funkcije Type 2 (C0 + x^N * (C1 - C0) z omejevanjem Domain in Range), šivalne (stitching) funkcije Type 3 (rekurzija podfunkcij glede na Bounds s prekodiranjem Encode) in vzorčene funkcije Type 0 z 8-, 16- in 32-bitnimi vzorci. Type 0 je enak mehanizem iskalnih tabel, kot smo ga obravnavali na strani ustvarjanja v članku o gradnji barvnih LUT-ov Type 0; stran za izrisovanje pa se sprehodi skozi enako strukturo v obratni smeri, od dekodiranih bajtov vzorcev nazaj do vrednosti komponent

Funkcije PostScript kalkulatorja Type 4 so bile zadnji manjkajoči del. Do različice v2.375.0 so se elegantno spustile na nevtralen nadomestek; od različice v2.375.0 naprej pa HPDFEvalPostScriptCalculator izvaja celoten nabor operaterjev po standardu ISO 32000-1 §7.10.5 na omejenem skladu operandov: aritmetiko, primerjave, logične in bitne operaterje, manipulacijo sklada, vključno z roll, ter pogoje if/ifelse. Semantika robnih primerov je strožja, kot je videti. PostScript round zaokroži polovico navzgor (proti večji vrednosti), zato Delphijevega bančnega zaokroževanja Round ni mogoče uporabiti; trigonometrični operaterji delujejo v stopinjah, pri čemer atan vrača vrednosti v [0, 360), exp pa je potenca z dvema operandoma in ne naravna eksponentna funkcija. Isti ocenjevalnik poganja tudi gradientna polnjenja na osnovi funkcij, zato je izrisovanje osnega in radialna senčenja v isti izdaji prevzelo rampe, ki jih poganja kalkulator

// Evaluate a Separation/DeviceN tint transform (Type 0/2/3/4).
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 natančnosti: programski kalkulator, ovrednoten v dvojni natančnosti, se ne bo ujemal s tiskarskim RIP-om bit za bit, omejevanje na meji Range pa se lahko za najmanj pomemben bit razlikuje od druge implementacije. Za prikaz na zaslonu in regresijsko izrisovanje is to nepomembno; če pa gradite barvno upravljano preizkušanje (proofing), je transformacija tona le prva stopnja in v nadaljevanju še vedno potrebujete pravi CMM

CalGray, CalRGB, Lab in ICCBased brez pogona ICC

Družine na osnovi CIE izberejo drugo vejo istega razreševalnika. HotPDF pretvori vrednosti Lab prek standardne verige Lab v XYZ v sRGB, vključno s prelomno kocko 6/29 v inverzni prenosni funkciji, ter obravnava CalRGB z njegovo gamo na kanal plus 3x3 linearno matriko in CalGray z njegovo enojno gamo. Podrobnost, pomembna za zmogljivost, je obravnava bele točke: pretvorbena matrika XYZ v sRGB je Bradford-prilagojena beli točki, deklarirani v barvnem prostoru, in predpomnjena v razrešenem zapisu THPDFColorSpace, tako da delo na slikovno piko ostane eno samo množenje 3x3, ne glede na to, kako eksotično je deklarirano svetilo

Prostori ICCBased so deležni namerno pragmatične obravnave. Specifikacija PDF zahteva, da vsak tok ICCBased deklarira nadomestni prostor /Alternate ali impliciranega prek števila komponent /N, prav zato, da lahko pregledovalniki brez pogona za upravljanje barv še vedno smiselno izrisujejo. HotPDF razreši ICCBased prek tega nadomestnega prostora ali pa ugane DeviceGray, DeviceRGB ali DeviceCMYK iz vrednosti /N (1, 3 ali 4), ko vnos ni prisoten, in nikoli ne razčlenjuje bajtov profila. To pomeni brez odvisnosti od lcms in brez stroškov iskanja profilov, za ceno barvne natančnosti: prostor ICCBased, katerega profil močno odstopa od njegovega nadomestnega, bo prikazal nadomestni izris. Za ogled na zaslonu in sličice je to enak kompromis, kot ga sprejme vsak lahek pregledovalnik, in to je omejitev, ki jo morate jasno navesti v lastni dokumentaciji

Hrošč, zaradi katerega je vsako iskanje imenovanega barvnega prostora zgrešilo

Cevovod operaterjev iz različice v2.333.0 se je prinašal z napako, ki je ostala nevidna dvainštirideset izdaj: upravljalnika cs in CS sta iskala svoj operand z vodilno poševnico (/CS0) proti ključem slovarja virov, shranjenim brez poševnice (CS0). Vsako iskanje imenovanega barvnega prostora je zgrešilo v stotih odstotkih primerov, koda pa se je tiho vrnila na privzeto vrednost DeviceGray. Vidni simptom je bil subtilen na najslabši možen način: polnjenje Separation z vrednostjo 1 scn je postalo DeviceGray 1.0, kajti bela barva na beli strani pa ni napaka pri izrisu, ki bi jo kdo posnel kot posnetek zaslona. Popravek v različici v2.375.0 je bil skupni pomočnik za normalizacijo imen, ki se uporabi pri vsakem iskanju operanda v virih

Iz iste preiskave sta izšli dve sorodni napaki. Prvič, posredne reference na objekte v obliki matrik so se vračale nerazrešene: dostop do dokumenta v izrisovalniku je imel tipizirane razreševalnike le za tokovne objekte in slovarje, zato se je /CS0 5 0 R, ki kaže na samostojno matriko [/Separation ...], vrnil kot surova referenca, prostor pa se je razčlenil kot nepodprt. Drugič, metoda HPDFReadNumericArray je uveljavljala strogo semantiko dolžine, ki je zahtevala, da je matrika PDF dolga vsaj toliko kot posredovani odpomnilnik (buffer). Branje ključev /C0 in /C1 funkcije Type 2 v štiri-elementni odpomnilnik je zato spodletelo za nadomestne prostore z eno in tremi komponentami, kar je pustilo obe matriki na ničlah, vsak ne-CMYK eksponentni ton pa se je izrisal črno, dokler različica v2.376.0 ni uvedla prizanesljivega bralnika HPDFReadNumericArrayUpTo. Pouk za naprej: vsak ključ PDF, dokumentiran kot tak, ki vsebuje numerično matriko spremenljive dolžine, je treba brati z bralnikom, ki napolni le to, kar obstaja, saj fiksni odpomnilnik s strogim ujemanjem pretvori veljavne datoteke v tihe ničle

Kako preizkusiti izrisovanje spot barv brez zavajanja?

Neprijetno vprašanje je, zakaj je testna zbirka ves ta čas ostajala zelena. Prvotni regresijski preizkus, SeparationRendersDistinguishable, je trdil le, da izrisana bitna slika ni popolnoma črna. Cevovod, ki je vsako spot barvo spustil na DeviceGray, je ustvaril siv in bel izhod, kar ni črno, zato je trditev prešla, čeprav je bila celotna funkcija mrtva. Šibke trditve (assertions) oblike "ni prazno", "ni popolnoma črno" ali "izvleček ni ničeln" ne morejo razlikovati delujočega izrisovalnika od pokvarjenega, saj skoraj vsak neuspešen način še vedno ustvari nekaj slikovnih pik

Slog trditev, ki dejansko ujeme te napake, zaklene pričakovano barvo: izrišite ročno izdelan minimalen PDF, katerega Separation črnilo se razreši v znan odtenek, nato pa preštejte slikovne pike, v katerih prevladuje ta odtenek. Izrisovanje v bitno sliko za pregled uporablja isto vstopno točko RenderLoadedPageToBitmap, opisano v priročniku za izrisovanje strani v bitno sliko

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 objekt (fixture) je pomemben prav toliko kot trditev. Ročno izdelan PDF, dolg nekaj sto bajtov, z enim polnjenjem Separation in ničemer drugim, ne pušča dvomov o tem, kakšen je pričakovani izhod; datoteka iz resničnega sveta sicer izvaja več kode, vendar vam ne more povedati, katera stopnja je spodletela. Štetje slikovnih pik pričakovane barve zdaj obravnavamo kot minimalni standard za vsak preizkus delovanja (smoke test) izrisovanja, saj je to edini slog trditve, ki je te napake prisilil na plan

Izrisovanje spot barv in barvnih prostorov CIE je del cevovoda za naložene dokumente v komponenti HotPDF Component za Delphi in C++Builder, skupaj s funkcijskim pogonom, izrisovanjem senčenja in zgoraj prikazanimi potmi za izvoz bitnih slik