Tekninen artikkeli

PDF-liukuvärien renderöinti Delphissä: Aksiaaliset ja radiaaliset

PDF-tiedosto, jonka ylätunnisteessa on Word- tai Excel-tyylinen liukuväritausta, palautui ennen kuvaksi renderöitäessä tyhjänä valkoisena suorakulmiona kohdassa, jossa värien sekoituksen pitäisi olla. HotPDF:n sivurenderöijä noudattaa nyt PDF:n sh (shading) -operaattoria tyypin 2 aksiaalisille (axial) ja tyypin 3 radiaalisille (radial) varjostuksille: se laskee varjostuksen funktion liukuväriakselia pitkin ja piirtää sekoituksen, kun taas aiemmat versiot ohittivat operaattorin hiljaisesti ja jättivät alueen paperinvalkoiseksi. Nämä kaksi varjostustyyppiä kattavat lähes jokaisen toimistoasiakirjan liukuvärin. Mesh-tyypit puolestaan on jätetty pois, ja tämä artikkeli kertoo rehellisesti, mihin tuo raja asettuu

Miksi PDF-liukuväri renderöityy valkoisena laatikkona?

PDF-liukuväri on vektorirakenne, ei bittikartta, jonka renderöijä voi siirtää (blit). Varjostussanakirja (shading dictionary) (ISO 32000-1 §8.7.4.5) kuvaa geometrian — akselin tai ympyräparin Coords-taulukossa — sekä värifunktion, ja sh-operaattori maalaa tämän varjostuksen nykyisen rajausalueen (clip region) yli. Kyseessä on sama grafiikkamalli, jonka HotPDF tarjoaa, kun piirrät polkuja ja täyttöjä sivun kankaalle. HotPDF:n sivurenderöijä käy sisältövirran läpi operaattori kerrallaan, ja kaikki operaattorit, joille ei ole koodipolkua, ohitetaan, jotta muu osa sivusta renderöityy silti. Ennen versiota 2.335.0 sh kuului tähän ohitettujen joukkoon: ylätunniste tai koko sivun liukuväri ei vaikuttanut tulosteeseen, joten tausta näkyi läpi valkoisena. Tiedosto ei koskaan ollut korruptoitunut. Renderöijällä ei vain ollut käsittelijää tälle yhdelle operaattorille, ja valkoinen laatikko on juuri sitä, miltä puuttuva käsittelijä näyttää

Varjostuksen takana oleva funktion laskentaketju

Varjostusta ei voida maalata ennen kuin sen funktio on laskettu, joten tuon koneiston oli saavuttava ensin. Sekä tyypin 2 aksiaaliset että tyypin 3 radiaaliset varjostukset sisältävät /Function-merkinnän, joka mäppää parametrisen sijainnin liukuväriä pitkin väriksi (ISO 32000-1 §7.10), ja HotPDF:n funktionlaskija selvittää tyypin FunctionType ja suorittaa sen mukaisesti. Tyypin 2 eksponentiaalinen interpolointi (§7.10.3) laskee kaavan C0 + t^N * (C1 - C0) Domain- ja Range-rajoituksilla. Tyypin 0 näytteistetty funktio (§7.10.2) lukee 8-, 16- tai 32-bittisten näytteiden ruudukon ja valitsee lähimmän Encode-kartan kautta, mitä käsitellään syvällisesti artikkelissa läpikatsauksemme tyypin 0 näytteistetyistä värihakuista. Tyypin 3 tikkausfunktiot (stitching functions) (§7.10.4) valitsevat alifunktion Bounds-taulukon perusteella ja rekursioivat siihen. Tyypin 4 PostScript-laskinfunktioita (§7.10.5) ei lasketa — ne heikkenevät hallitusti epäonnistuneeksi laskennaksi väärän värin sijaan, noudattaen HotPDF:n konservatiivista politiikkaa läpi renderöintipolun. Tämä laskija toimitettiin versiossa 2.334.0, yhtä julkaisua ennen itse operaattoria, koska varjostusmaalari on vain niin hyvä kuin sen alla oleva funktio

Miten HotPDF muuttaa jatkuvan liukuvärin pikseleiksi?

HotPDF arvioi jatkuvaa sekoitusta kiinteällä määrällä yhtenäisen värisiä kaistoja (color bands). Varjostusmaalari käy läpi liukuväriakselin ja jakaa sen jopa 64 kaistaan — vähempään, jos akseli on lyhyt laitepikseleinä —, laskee funktion kunkin kaistan parametrisessa sijainnissa ja täyttää akseliin nähden kohtisuoran suorakulmion yhtenäisellä siveltimellä (brush). Kompromissi on kaistojen määrä suhteessa tasaisuuteen: useammat kaistat seuraavat sekoitusta tarkemmin, mutta maksavat enemmän täyttöoperaatioita, ja 64 on tietoinen enimmäisraja. Koska määrä skaalautuu myös akselin pituuden mukaan laitepikseleinä, resoluutiolla on merkitystä. Saman sivun renderöiminen suuremmalla DPI-arvolla pidentää akselia, joten lyhyt liukuväri saa lisää kaistoja ja sen raidat pehmenevät, kunnes enimmäisraja saavutetaan

// Liukuvärin diskretoija käyttää jopa 64 värikaistaa liukuväriakselia
// pitkin, mutta ei koskaan enempää kuin akseli on pitkä laitepikseleinä. Saman
// sivun renderöiminen suuremmalla DPI-arvolla pidentää akselia, joten lyhyt
// liukuväri saa lisää kaistoja ja raidoitus pehmenee, kunnes 64 kaistan katto saavutetaan.
Thumb := Pdf.RenderLoadedPageToBitmap(PageIndex, 96);   // karkeammat kaistat
Proof := Pdf.RenderLoadedPageToBitmap(PageIndex, 300);  // jopa 64 kaistaa

Varjostetun sivun renderöinti bittikartaksi

Julkinen aloituspiste on ennallaan: RenderLoadedPageToBitmap ottaa vastaan nollaperusteisen sivun indeksin ja DPI-arvon, palauttaa kutsujan omistaman 24-bittisen bittikartan ja palauttaa nil-arvon virheen sattuessa poikkeuksen nostamisen sijaan, joten varjostustuki on läpinäkyvää jo olemassa olevalle koodillesi. Sivu, joka renderöi aiemmin liukuväriylätunnisteensa tyhjänä, palauttaa nyt saman bittikartan, johon värien sekoitus on maalattu, eikä API-muutoksia tarvita sen saamiseksi. Yksi muistamisen arvoinen asia on omistajuus: kutsuja vapauttaa palautetun bittikartan. Koko renderöintiputki — sisältövirran tulkinta, upotetut kirjoitusmerkit ja sivun välimuisti — kuvataan artikkelissa opas PDF-sivujen renderöimiseen bittikartaksi Delphissä

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('quarterly-report.pdf') > 0 then
    begin
      // Sivu 1 (nollaperusteinen) 150 DPI:n tarkkuudella. Liukuvärillä täytetty ylätunniste,
      // joka palautui ennen tyhjänä, sisältää nyt värien sekoituksen.
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 150);
      if Assigned(Bmp) then
      try
        Bmp.SaveToFile('page1.bmp');
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Helppo regressionestotesti saadaan samasta kutsusta. Renderöi sivu, ota pikselinäyte kohdasta, johon liukuvärikaista kuuluu, ja varmista, ettei se ole enää paperinvalkoinen, johon renderöijä aiemmin turvautui — yhden rivin tarkistus, joka estää hiljaisen paluun vanhaan toimettomaan käyttäytymiseen

Bmp := Pdf.RenderLoadedPageToBitmap(0, 150);
if Assigned(Bmp) then
try
  // True, kun sh-operaattori maalasi; False, jos alue palautui valkoiseksi.
  HeaderPainted := Bmp.Canvas.Pixels[Bmp.Width div 2, 8] <> clWhite;
finally
  Bmp.Free;
end;

Mitä HotPDF ei piirrä: mesh-varjostukset ja rajausreunat

Neljä mesh-varjostustyyppiä ovat soveltamisalan ulkopuolella, ja asiakirjan laatijan on hyvä tietää se etukäteen. Vapaamuotoiset Gouraud-kolmiomeshit (tyyppi 4), hilarakenteiset Gouraud-meshit (tyyppi 5), Coons-patch-meshit (tyyppi 6) ja tensoritulo-patch-meshit (tyyppi 7), jotka määritellään standardin ISO 32000-1 osioissa §8.7.4.5.5–§8.7.4.5.8, eivät renderöidy. Sivut, jotka käyttävät niitä, renderöityvät silti, mutta kyseinen alue jätetään sellaiseksi kuin se on. Kaksi muuta rajausta on syytä sanoa suoraan. Kaistasuorakulmiot peittävät varjostuksen raidan sen sijaan, että ne rajautuisivat täsmälleen nykyiseen rajauspolkuun, joten pyöreään tai epäsäännölliseen muotoon peitetty liukuväri piirtyy sen rajaavana raitana, kunnes tarkka alueen rajaus saapuu myöhemmässä versiossa. HotPDF myös olettaa, että funktion tuloste on RGB- tai harmaasävyinen, joten CMYK-muodossa tai erikoisessa väriavaruudessa luotu varjostus arvioidaan värihallinnan sijaan. Useimmille asiakirjoille, joita Delphi- ja C++Builder-tiimit todella käsittelevät — raportit, laskut, tiliotteet ja PDF-muotoon viedyt esitykset —, aksiaaliset ja radiaaliset kattavat käytännössä jokaisen kohtaamasi liukuvärin, ja mesh-painotteiset vedokset on syytä käsitellä esikatseluina pikemmin kuin lopullisina vedoksina

HotPDF:n sivurenderöijä, tässä kuvattu sh-operaattorin tuki ja sen alla oleva tyypin 0/2/3 funktionlaskija toimitetaan osana Delphille ja C++Builderille tarkoitettua HotPDF-komponenttia — natiivia VCL-kirjastoa ilman ulkoisia DLL-riippuvuuksia, joka kattaa PDF-luonnin, muokkauksen, tekstinpoiston ja sivujen renderöinnin samassa paketissa