Tekninen artikkeli

Separation- ja DeviceN-lisävärien piirtäminen Delphissä

HotPDF piirtää Separation- ja DeviceN-lisävärit (spot colors) ladatuilla PDF-sivuilla ratkaisemalla väriavaruuden HPDFResolveColorSpace-funktiolla, laskemalla sävymuunnosfunktion arvon HPDFEvalTintTransform-funktiolla ja muuntamalla tuloksen vaihtoehtoisen avaruuden kautta näytön RGB-väreiksi. Versiosta v2.375.0 alkaen tämä piirtoputki suorittaa kaikki neljä PDF-funktiotyyppiä, mukaan lukien Type 4 PostScript -laskimet, joten Pantone-musteita sisältävä painovalmis tiedosto näyttää todelliset värinsä väliaikaisen korvikkeen sijaan. Tässä artikkelissa käydään läpi, miten piirtoputki toimii ja, aivan yhtä hyödyllisesti, miten se epäonnistui kehityksen aikana

Käynnistävä tekijä on aina sama. Asiakas saa PDF-tiedoston painotalosta: otsikko on asetettu nimetyllä lisäväri-musteella (spot ink), pakkauskuvitus käyttää kahden musteen DeviceN-sekoitusta ja leipäteksti on tavallista mustaa. Acrobatissa se näyttää täydelliseltä. Delphi-sovelluksessasi otsikko piirtyy mustana tai pahempaa, ei piirry lainkaan, ja asiakas tekee virheilmoituksen ohjelmistostasi eikä tiedostosta. Tiedosto on kunnossa. Piirtomoottori ei vain ymmärrä tiedoston käyttämiä väriavaruuksia

Miksi lisävärit piirtyy mustina PDF-katseluohjelmassa?

Lisävärit piirtyvät mustina tai katoavat kokonaan, kun piirtomoottori toteuttaa vain laitekohtaiset värioperaattorit (rg, g, k) ja jättää yleiset operaattorit huomiotta. ISO 32000-1 §8.6 määrittelee kolme väriavaruusryhmää: laiteavaruudet (DeviceGray, DeviceRGB, DeviceCMYK), CIE-pohjaiset avaruudet (CalGray, CalRGB, Lab, ICCBased) ja erikoisavaruudet (Indexed, Separation, DeviceN, Pattern). Kaikki laiteavaruuksien ulkopuoliset kohteet valitaan yleisillä operaattoreilla: cs ja CS valitsevat avaruuden nimen perusteella sivun resurssisanakirjasta (resource dictionary), ja sen jälkeen sc, SC, scn ja SCN toimittavat komponenttien arvot. Piirtomoottori, joka ohittaa nämä operaattorit, säilyttää edellisen väriasetuksen, mikä lisäväriotsikolla alkavalla sivulla on alustava DeviceGrayn musta

HotPDF lisäsi yleiset operaattorit sivujen piirtomoottoriinsa versiossa v2.333.0 yhdessä yhtenäistetyn ratkaisupolun kanssa: jokainen /ColorSpace-resurssimerkintä, oli se sitten pelkkä nimi, sisäinen taulukko tai epäsuora viittaus, jäsennetään yhdeksi THPDFColorSpace-tietueeksi, ja jokainen täyttö- tai viivanväripyyntö ohjataan yhden HPDFResolveColor-kutsun kautta. Väriperheiden luettelo näyttää kattavuuden yhdellä silmäyksellä

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;

Yksi suunnittelupäätös osoittautuu toistuvasti hyödylliseksi: csfUnsupported on ensiluokkainen väriperhe, ei virhe. Väriavaruus, jota piirtomoottori ei kykene tulkitsemaan, palautuu määriteltyyn varavaihtoehtoon (fallback) sen sijaan, että sivun käsittely keskeytettäisiin. Tämä vastaa yleisimpien katseluohjelmien toimintaa ja estää yhtä erikoista täyttöä tyhjentämästä muutoin piirrettävissä olevaa asiakirjaa

Miten sävymuunnos muuttaa yhden mustearvon todelliseksi väriksi?

Separation-väriavaruus kantaa mukanaan kolmea tietoa: musteen nimeä, vaihtoehtoista väriavaruutta ja sävymuunnosfunktiota. Taulukko [/Separation /PANTONE485 /DeviceCMYK f] kertoo: kun sisältövirta kirjoittaa 0.8 scn, syötetään sävyarvo 0.8 funktioon f ja piirretään saatu CMYK-nelikko. DeviceN yleistää tämän N musteelle N-syötteisellä funktiolla. Itse musteen nimi on näytöllä vain suositus; sävymuunnos muodostaa koko piirtosemantiikan, joten piirtomoottori, joka jäsentää avaruuden mutta ohittaa funktion, ei ole vielä tehnyt mitään hyödyllistä

HPDFEvalTintTransform on tämän vaiheen taustalla oleva funktiomoottori. Se esiteltiin versiossa v2.334.0, ja se laskee arvoja Type 2 -eksponenttifunktioille (C0 + x^N * (C1 - C0) Domain- ja Range-rajoituksilla), Type 3 -liitosfunktioille (Bounds-valittu alifunktion rekursio Encode-uudelleenkartoituksella) ja Type 0 -näytefunktioille 8-, 16- ja 32-bittisillä näytteillä. Type 0 on sama hakutaulukkojärjestelmä, jota käsittelimme luontipuolelta artikkelissa Type 0 -värihakutaulukoiden rakentamisesta; piirtopuoli käy läpi saman rakenteen käänteisesti puretuista näytetavuista takaisin komponenttien arvoiksi

Type 4 PostScript -laskinlausekkeet olivat viimeinen puuttuva palanen. Versioon v2.375.0 asti ne palautuivat hallitusti neutraaliin korvikkeeseen; versiosta v2.375.0 lähtien HPDFEvalPostScriptCalculator suorittaa koko ISO 32000-1 §7.10.5 -operaattorisarjan rajatussa operandipinossa: aritmetiikan, vertailut, totuusarvo- ja bittioperaattorit, pinon hallinnan mukaan lukien roll-toiminnon sekä if/ifelse-ehdot. Yksityiskohdat ovat tiukempia miltä ne näyttävät. PostScriptin round pyöristää puolikkaat kohti suurempaa arvoa, joten Delphin pankkiiripyöristystä käyttävää Round-funktiota ei voida käyttää; trigonometriset operaattorit toimivat asteina, jolloin atan palauttaa arvot välillä [0, 360). Ja exp on kahden operandin potenssi, ei luonnollinen eksponentti. Sama ratkaisija ohjaa myös funktiotoimisia liukuväritäyttöjä, minkä vuoksi akselinsuuntainen ja säteittäinen varjostuspiirto sai laskinohjattavat liukuvärit samassa julkaisussa

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

Rehellisyys tarkkuudesta: kaksinkertaisella tarkkuudella laskettu ohjelmistolaskin ei vastaa rasterikuvaprosessoria (RIP) bitti bitiltä, ja Range-rajan rajoitus voi poiketa vähiten merkitsevän bitin verran toisesta toteutuksesta. Näyttöesityksen ja regressiopiirron kannalta tällä ei ole merkitystä; jos rakennat värihallittua vedostusta, sävymuunnos on vain ensimmäinen vaihe ja tarvitset edelleen todellisen värihallintajärjestelmän (CMM) jatkoprosesseihin

CalGray, CalRGB, Lab ja ICCBased ilman ICC-moottoria

HotPDF muuntaa Lab-arvot standardinmukaisen Lab -> XYZ -> sRGB -ketjun kautta, mukaan lukien käänteisen siirtofunktion 6/29-katkaisukohdan (breakpoint cube), ja käsittelee CalRGB:n kanavakohtaisen gamman ja 3x3 lineaarimatriisin avulla sekä CalGrayn yhdellä gammalla. Suorituskyvyn kannalta tärkeä yksityiskohta on valkopisteen käsittely: XYZ -> sRGB -muunnosmatriisi on Bradford-sovitettu väriavaruudessa ilmoitettuun valkopisteeseen ja tallennettu välimuistiin ratkaistuun THPDFColorSpace-tietueeseen, jolloin pikselikohtainen työ pysyy yhtenä 3x3-kertolaskuna riippumatta siitä, kuinka eksoottinen ilmoitettu valonlähde (illuminant) on

ICCBased-avaruudet saavat tietoisen pragmaattisen käsittelyn. PDF-spesifikaatio vaatii jokaista ICCBased-virtaa ilmoittamaan /Alternate-avaruuden tai oletetun avaruuden sen komponenttimäärän /N kautta, nimenomaan siksi, että katseluohjelmat ilman värihallintamoottoria voivat still piirtää sen järkevästi. HotPDF ratkaisee ICCBased-avaruuden tämän vaihtoehtoisen avaruuden kautta, tai arvaa sen olevan DeviceGray, DeviceRGB tai DeviceCMYK arvon /N (1, 3 tai 4) perusteella, kun merkintä puuttuu, eikä koskaan jäsenennä profiilitavuja. Tämä tarkoittaa, ettei lcms-riippuvuutta tai profiilinhakukustannuksia synny, värikkyyden tarkkuuden kustannuksella: ICCBased-avaruus, jonka profiili eroaa voimakkaasti sen vaihtoehtoisesta avaruudesta, näyttää vaihtoehtoisen piirron. Näyttökatselussa ja pikkukuvissa tämä on sama kompromissi, jonka jokainen kevyt katseluohjelma tekee, ja se on rajoitus, joka on syytä ilmoittaa selvästi omassa dokumentaatiossasi

Virhe, joka sai jokaisen nimetyn väriavaruushaun epäonnistumaan

Version v2.333.0 operaattoriputkisto toimitettiin virheellä, joka pysyi näkymättömänä 42 julkaisun ajan: cs- ja CS-käsittelijät etsivät operandiaan sen etuviiva säilytettynä (/CS0) resurssisanakirjan avaimista, jotka oli tallennettu ilman etuviivaa (CS0). Jokainen nimetyn väriavaruuden haku epäonnistui sataprosenttisesti, ja koodi palasi hiljaa oletusarvoiseen DeviceGray-tilaan. Näkyvä oire oli salakavala pahimmalla tavalla: Separation-täyttö arvolla 1 scn muuttui DeviceGray-arvoksi 1.0, joka piirtää valkoista. Valkoinen muste valkoisella sivulla ei ole sellainen piirtovirhe, josta kukaan ottaisi kuvakaappausta. Korjaus versiossa v2.375.0 oli jaettu nimen normalisoinnin apuohjelma, jota sovelletaan jokaisessa operandin ja resurssin välisessä haussa

Samasta tutkimuksesta paljastui kaksi muuta virhettä. Ensinnäkin epäsuorat viittaukset taulukkosisältöisiin olioihin palautettiin ratkaisemattomina: piirtomoottorin asiakirjakäsittelyssä oli tyypitetyt ratkaisijat vain virroille ja sanakirjoille, joten erilliseen [/Separation ...] -taulukkoon osoittava /CS0 5 0 R palasi raakana viittauksena ja avaruus tulkittiin ei-tuetuksi. Toiseksi HPDFReadNumericArray vaati tiukkaa pituussemantiikkaa edellyttäen, että PDF-taulukko on vähintään yhtä pitkä kuin toimitettu puskuri. Type 2 -funktion /C0- ja /C1-lukeminen nelielementtiseen puskuriin epäonnistui yksi- ja kolmikomponenttisissa vaihtoehdoissa jättäen molemmat taulukot nolliksi, jolloin jokainen ei-CMYK-eksponenttinen sävy piirtyi mustana, kunnes versio v2.376.0 esitteli sallivamman HPDFReadNumericArrayUpTo-lukijan. Opetus tulevaisuutta varten: kaikki PDF-avaimet, joiden on dokumentoitu sisältävän vaihtelevan pituisen numeerisen taulukon, on luettava "täytä-mitä-löytyy"-lukijalla, sillä kiinteän kokoinen puskuri tiukalla täsmäyksellä muuttaa kelvolliset tiedostot hiljaisiksi nolliksi

Miten testaat lisävärien piirtämistä huijaamatta itseäsi?

Epämiellyttävä kysymys on se, miksi testit säilyivät vihreinä tämän kaiken läpi. Alkuperäinen regressiotesti SeparationRendersDistinguishable varmisti vain, ettei piirretty bittikartta ollut täysin musta. Piirtoputki, joka pudotti jokaisen lisävärin DeviceGrayksi, tuotti harmaata ja valkoista jälkeä, mikä ei ole mustaa. Testivarmistus meni siis läpi, vaikka koko ominaisuus oli rikki. Heikot varmistukset muodoissa "ei tyhjä", "ei täysin musta" tai "tarkistussumma ei ole nolla" eivät kykene erottamaan toimivaa piirtomoottoria rikkinäisestä, sillä lähes mikä tahansa virhetila tuottaa silti jonkinlaisia pikseleitä

Varmistustapa, joka todella havaitsee nämä virheet, lukitsee odotetun värin: piirrä käsin rakennettu minimaalinen PDF, jonka Separation-muste ratkeaa tunnettuun sävyyn, ja laske sitten ne pikselit, joissa kyseinen sävy on hallitseva. Bittikartaksi piirtäminen tarkastelua varten käyttää samaa RenderLoadedPageToBitmap-aloituspistettä, jota kuvataan sivunpiirtoa bittikartaksi käsittelevässä oppaassa

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;

Testiedosto (fixture) on yhtä tärkeä kuin varmistus. Käsin rakennettu muutaman sadan tavun pituinen PDF-tiedosto yhdellä Separation-täytöllä ja ilman mitään muuta ei jätä epäselvyyttä siitä, mitä odotettu tulos on; todellinen tiedosto suorittaa enemmän koodia, muttei kykene kertomaan, mikä vaihe epäonnistui. Pidämme nykyään odotetun värin pikselilaskentaa vähimmäisvaatimuksena kaikissa piirron savutesteissä (smoke test), koska se on ainoa varmistustyyli, joka toi nämä viat esiin

Lisävärien ja CIE-väriavaruuksien piirtäminen on osa ladattujen asiakirjojen piirtoputkea HotPDF Component -tuotteessa Delphille ja C++Builderille yhdessä funktiomoottorin, varjostuspiirron ja yllä esitettyjen bittikarttavientipolkujen kanssa