Techninis straipsnis

„Separation“ ir „DeviceN“ taškinių spalvų atvaizdavimas Delphi aplinkoje

„HotPDF“ atvaizduoja „Separation“ ir „DeviceN“ taškines spalvas (spot colors) įkeltuose PDF puslapiuose, išspręsdama spalvų erdvę per „HPDFResolveColorSpace“, įvertindama atspalvių transformavimo (tint-transform) funkciją per „HPDFEvalTintTransform“ ir konvertuodama rezultatą per alternatyvią erdvę į RGB ekrano rodymui. Nuo v2.375.0 versijos šis konvejeris įvertina visus keturis PDF funkcijų tipus, įskaitant Type 4 „PostScript“ skaičiuotuvus, todėl spaudai paruoštas failas su „Pantone“ dažais rodo savo tikrąsias spalvas, o ne laikiną užpildą. Šiame straipsnyje apžvelgiama, kaip veikia šis konvejeris ir kaip jis klydo jį kuriant

Taškinės spalvos atvaizduojamos juodai arba išvis pradingsta, kai atvaizdavimo variklis realizuoja tik įrenginio spalvų operatorius („rg“, „g“, „k“) ir ignoruoja bendruosius. ISO 32000-1 §8.6 apibrėžia tris spalvų erdvių grupes: įrenginio erdves („DeviceGray“, „DeviceRGB“, „DeviceCMYK“), CIE pagrįstas erdves („CalGray“, „CalRGB“, „Lab“, „ICCBased“) ir specialiąsias erdves („Indexed“, „Separation“, „DeviceN“, „Pattern“). Viskas, kas yra už įrenginio grupės ribų, pasirenkamas bendraisiais operatoriais: „cs“ ir „CS“ pasirenka erdvę pagal pavadinimą iš puslapio resursų žodyno, o tada „sc“, „SC“, „scn“ ir „SCN“ pateikia komponentų reikšmes. Atvaizdavimo variklis, kuris praleidžia šiuos operatorius, išlaiko paskutinę nustatytą spalvą, kas puslapyje su taškinės spalvos antrašte iš pradžių yra pradinė „DeviceGray“ juoda spalva

„HotPDF“ pridėjo bendrųjų operatorių rinkinį prie savo puslapių atvaizdavimo variklio v2.333.0 versijoje kartu su bendru išsprendimo keliu: kiekvienas resursų įrašas „/ColorSpace“ (nesvarbu, ar tai grynas pavadinimas, ar įterptas masyvas, ar netiesioginė nuoroda) yra išanalizuojamas į vieną „THPDFColorSpace“ įrašą, o kiekvienas užpildymo ar brėžimo spalvos prašymas nukreipiamas per vieną iškvietimą „HPDFResolveColor“. Šeimos išvardijimas iškart parodo palaikymą

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;

Vienas dizaino sprendimas atsiperka nuolat: „csfUnsupported“ yra lygiavertė šeima, o ne klaida. Erdvė, kurios atvaizdavimo variklis negali interpretuoti, yra pradinė atsarginė erdvė (fallback), užuot nutraukus puslapio apdorojimą — tai atitinka įprastų peržiūros programų elgseną ir neleidžia vienam egzotiškam užpildymui sugadinti viso puslapio rodymo

Kaip atspalvių transformacija paverčia vieną dažų reikšmę tikra spalva?

„Separation“ erdvė perneša tris informacijos dalis: dažų pavadinimą, alternatyvią spalvų erdvę ir atspalvių transformavimo funkciją. Masyvas „[/Separation /PANTONE485 /DeviceCMYK f]“ nurodo: kai turinio srautas rašo „0.8 scn“, perduokite atspalvio reikšmę 0.8 į funkciją „f“ ir nupieškite gautą CMYK ketvertą. „DeviceN“ apibendrina tai iki N dažų su N įvesčių funkcija. Pats dažų pavadinimas ekrane yra tik informacinio pobūdžio; atspalvių transformacija yra visa atvaizdavimo semantika, todėl atvaizdavimo variklis, kuris išanalizuoja erdvę, bet praleidžia funkciją, neatlieka nieko naudingo

„HPDFEvalTintTransform“ yra šio veiksmo variklis. Pridėtas v2.334.0 versijoje, jis įvertina Type 2 eksponentines funkcijas („C0 + x^N * (C1 - C0)“ su srities ir diapazono apribojimais), Type 3 jungiančiąsias (stitching) funkcijas (subfunkcijų rekursija pagal ribas su reikalavimu perskaičiuoti) ir Type 0 diskretizuotas (sampled) funkcijas su 8, 16 ir 32 bitų pavyzdžiais. Type 0 naudoja tą patį lentelių mechanizmą, kurį nagrinėjome iš kūrimo pusės straipsnyje apie Type 0 spalvų lentelių (LUT) kūrimą; atvaizdavimo pusė eina ta pačia struktūra atgal — nuo dekoduotų pavyzdžių baitų prie komponentų reikšmių

Type 4 „PostScript“ skaičiuotuvų funkcijos buvo paskutinė nepalaikoma dalis. Iki v2.375.0 versijos jos buvo pakeičiamos neutraliu užpildymu; nuo v2.375.0 „HPDFEvalPostScriptCalculator“ vykdo visą ISO 32000-1 §7.10.5 operatorių rinkinį ribotame steke: aritmetinius, palyginimo, loginius bei bitų operatorius, steko operacijas, įskaitant „roll“, ir sąlygas „if“/„ifelse“. Ši semantika yra griežtesnė nei atrodo: „PostScript“ „round“ apvalina pusę reikšmės į didesnės reikšmės pusę, todėl Delphi bankininkų apvalinimas „Round“ netinka; trigonometriniai operatoriai veikia laipsniais, „atan“ grąžina reikšmes iš diapazono [0, 360), o „exp“ yra dviejų operandų kėlimas laipsniu, o ne natūralusis eksponentas. Tas pats skaičiuotuvas valdo ir funkcijomis grįstus gradientinius užpildymus, todėl ašinio ir radialinio šešėliavimo atvaizdavimas gavo skaičiuotuvu valdomus perėjimus tame pačiame leidime

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

Pastaba apie tikslumą: programinis skaičiuotuvas, atliekantis skaičiavimus dvigubo tikslumo slankiojo kablelio skaičiais, nesutaps su RIP įrenginiu bitas į bitą, o apribojimai ties „Range“ riba gali skirtis vienu mažiausiai reikšmingu bitu nuo kitos realizacijos. Ekrano rodymui ir regresijos testavimui tai yra nesvarbu; jei kuriate profesionalų spalvų valdymą, atspalvių transformacija yra tik pirmasis etapas, o toliau jums vis tiek reikės tikro spalvų valdymo modulio (CMM)

CalGray, CalRGB, Lab ir ICCBased be ICC variklio

CIE pagrįstos spalvų šeimos naudoja kitą to paties išsprendimo variklio šaką. „HotPDF“ konvertuoja „Lab“ reikšmes per standartinę „Lab -> XYZ -> sRGB“ grandinę, įskaitant 6/29 ribinį kubą atvirkštinėje perdavimo funkcijoje, bei apdoroja „CalRGB“ su jo atskirų kanalų gama ir 3x3 linijine matrica bei „CalGray“ su jo viena gama. Našumui svarbi detalė yra balto taško (white point) apdorojimas: „XYZ -> sRGB“ konvertavimo matrica yra pritaikyta pagal Bradfordo metodą balto taško atžvilgiu, deklaruotam spalvų erdvėje, ir išsaugoma „THPDFColorSpace“ įraše, todėl taško lygio darbas lieka tik viena 3x3 matricos daugyba, nesvarbu, koks egzotiškas šviesos šaltinis būtų nurodytas

„ICCBased“ erdvės apdorojamos pragmatiškai. PDF specifikacija reikalauja, kad kiekvienas „ICCBased“ srautas deklaruotų alternatyvią erdvę „/Alternate“ arba numatomąją pagal komponentų skaičių „/N“, kad peržiūros programos be pilno spalvų valdymo variklio vis tiek galėtų nupiešti vaizdą protingai. „HotPDF“ išsprendžia „ICCBased“ per šią alternatyvą, o kai įrašo nėra — nustato „DeviceGray“, „DeviceRGB“ arba „DeviceCMYK“ pagal tai, ar „/N“ yra 1, 3 ar 4, ir niekada neanalizuoja pačių profilio baitų. Tai reiškia, kad nėra „lcms“ bibliotekos priklausomybės ir jokių profilio paieškos išlaidų, tačiau nukenčia spalvinis tikslumas: „ICCBased“ erdvė, kurios profilis stipriai skiriasi nuo alternatyvaus, bus atvaizduojama pagal alternatyvų atvaizdavimą. Ekrano rodymui ir miniatiūroms tai yra tas pats kompromisas, kurį daro kiekviena lengva peržiūros programa

Klaida, dėl kurios nepasisekdavo nė viena vardinių spalvų erdvių paieška

v2.333.0 operatorių apdorojimo kodas turėjo klaidą, kuri išliko nematoma net keturiasdešimt du leidimus: „cs“ ir „CS“ apdorojimo programos ieškojo savo operando su pasvirusiu brūkšniu priekyje („/CS0“) resursų žodyno raktuose, kurie buvo saugomi be brūkšnio („CS0“). Kiekviena vardinė spalvų erdvės paieška buvo nesėkminga 100 procentų atvejų, o kodas tyliai sugrįždavo prie numatytosios „DeviceGray“ erdvės. Matomas simptomas buvo klaidinantis: „Separation“ užpildymas „1 scn“ tapdavo „DeviceGray 1.0“ (balta spalva), o balti dažai baltame puslapyje nėra ta klaida, kurią kas nors pastebėtų ekrane. Pataisymas v2.375.0 versijoje pritaikė bendrą pavadinimų normalizavimo funkciją kiekvienoje resursų paieškoje

To paties tyrimo metu buvo aptiktos dar dvi susijusios klaidos. Pirma, netiesioginės nuorodos į masyvo objektus buvo grąžinamos neišspręstos: atvaizdavimo variklis turėjo tipinius išsprendimus tik srautams ir žodynams, todėl „/CS0 5 0 R“, rodanti į savarankišką „[/Separation ...]“ masyvą, grįždavo kaip neapdorota nuoroda ir spalvų erdvė buvo laikoma nepalaikoma. Antra, „HPDFReadNumericArray“ reikalavo griežto ilgio atitikimo, kad PDF masyvas būtų bent jau tokio ilgio, kaip pateiktas buferis. Todėl skaitant Type 2 funkcijos „/C0“ ir „/C1“ į keturių elementų buferį, operacija nepavykdavo vieno ir trijų komponentų alternatyvoms, palikdama abu masyvus užpildytus nuliais, o kiekvienas ne CMYK eksponentinis atspalvis buvo atvaizduojamas juodai, kol v2.376.0 versijoje buvo įvesta atlaidi „HPDFReadNumericArrayUpTo“ skaitymo funkcija. Pamoka ateičiai: bet koks PDF raktas, turintis kintamo ilgio skaitinį masyvą, turi būti skaitomas su funkcija „užpildyk tiek, kiek yra“, nes fiksuoto dydžio buferis su griežtu ilgio patikrinimu paverčia teisingus failus nuliais

Kaip testuoti taškinių spalvų atvaizdavimą neapgaudinėjant savęs?

Nepatogus klausimas yra tas, kodėl testų rinkinys išlikdavo žalias per visą šį laiką. Pradinis testas „SeparationRendersDistinguishable“ tikrino tik tai, ar atvaizduotas bitų žemėlapis nėra visiškai juodas. Konvejeris, kuris kiekvieną taškinę spalvą pakeisdavo „DeviceGray“ erdvės pilka ir balta spalva, davė ne visiškai juodą rezultatą, todėl testas praeidavo, nors pati funkcija neveikė. Silpni testų tikrinimai (pvz., „ne tuščias“, „ne visiškai juodas“) negali atskirti veikiančio atvaizdavimo variklio nuo neveikiančio, nes beišeitina klaidų būsena vis tiek sukuria tam tikrus taškelius ekrane

Tikrinimo būdas, kuris iš tikrųjų padeda aptikti tokias klaidas, reikalauja konkrečios tikėtinos spalvos: atvaizduokite rankiniu būdu sukurtą minimalų PDF, kurio „Separation“ dažai išsprendžiami į žinomą atspalvį, o tada suskaičiuokite taškelius, kuriuose vyrauja šis atspalvis. Atvaizdavimui bitų žemėlapyje naudojamas tas pats „RenderLoadedPageToBitmap“ įėjimo taškas, aprašytas puslapių atvaizdavimo bitų žemėlapyje vadove

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;

Testavimo failas yra toks pat svarbus, kaip ir patikrinimas. Rankiniu būdu sukurtas kelių šimtų baitų PDF failas su vienu „Separation“ užpildymu ir niekuo kitu nepalieka jokių abejonių dėl to, koks turi būti rezultatas; realus failas naudoja daugiau kodo, bet negali pasakyti, kuriame etape įvyko klaida. Dabar taškelių su vyraujančia spalva skaičiavimą laikome minimaliu standartu bet kokiam atvaizdavimo testui, nes tai buvo vienintelis patikrinimo stilius, kuris padėjo aptikti šiuos defektus

Taškinių spalvų ir CIE spalvų erdvių atvaizdavimas yra įkeltų dokumentų konvejerio dalis „HotPDF Component“ komponente, skirtame Delphi ir C++Builder, kartu su funkcijų varikliu, šešėliavimo atvaizdavimu ir aukščiau parodytais bitų žemėlapių eksportavimo keliais