Tekninen artikkeli

JBIG2-koodaustaustat ja Free Pascal -linkkeri

PDFlibPas voi koodata kaksitasokuvia JBIG2-muotoon kahden eri taustajärjestelmän kautta. Toinen on natiivi Object Pascal -MMR-koodaaja, joka on aina läsnä. Toinen on ulkoinen symbolisanakirjakoodaaja, joka tuottaa huomattavasti pienemmän tulosteen skannatulla tekstillä, ja se on valinnainen: projektin on linkitettävä taustayksikkö, jotta se on ylipäätään olemassa. Tuo ero on tavallisin yllätys tämän ominaisuuden kohdalla, joten se kannattaa sanoa ensin: DefaultJBIG2EncodeOptions pyytää ulkoista koodaajaa oletuksena, ja kun taustayksikköä ei ole linkitetty, pyyntö siirtyy hiljaisesti Pascal-MMR-polkuun

Delphillä ja C++Builderilla ulkoinen tausta on joukko esikäänettyjä staattisia objektitiedostoja. Free Pascalilla sen piti tulla DLL:ksi, ja tie siihen johtopäätökseen on linkkeritarina, joka on hyödyllinen kaikille, jotka ovat yrittäneet linkittää C++-objekteja Free Pascal -ohjelmaan

Rekisteröinti on sopimus

Taustayksikkö rekisteröi itsensä alustusosastaan kutsumalla funktiota RegisterJBIG2EncoderBackend. Kutsujat pyytävät sitä joko asetusbitin PDF_JBIG2_OPTION_EXTERNAL_ENCODER kautta, jonka arvo on 4, tai laajennettujen kuvasisääntulopisteiden UseExternalEncoder-parametrin kautta. Kirjaston sateenvarjoyksikkö ei tarkoituksella vedä taustayksikköä mukaan, koska suuren objektijoukon kantamisen pitää olla jokaisen projektin oma päätös; C++Builder-puussa se sisällytetään esimerkiksi nimenomaisesti niihin projekteihin, jotka haluavat sen

Kutsujien kannalta seuraus on, että ulkoisen koodaajan pyytäminen on toive, ei takee, ja käännös, joka unohtaa yksikön, tuottaa suurempia tiedostoja eikä virhettä. Jos tulosteen koko merkitsee tarpeeksi paremman koodaajan pyytämiseen, se merkitsee tarpeeksi sen tarkistamiseen, että sait sen

PDFlibPasin JBIG2-koodauspyynnön kulku, jossa ulkoisen koodaajan toive siirtyy hiljaisesti natiiville Pascal-MMR-polulle ilman taustayksikköä
Ulkoisen symbolisanakirjakoodaajan pyytäminen on toive: linkitettynä tuloste pienenee; linkittämättä Pascal-MMR-polku ajaa hiljaisesti suuremmilla tiedostoilla
uses
  PDFlibrary,
{$IFDEF FPC}
  PDFlibJBIG2EncDLL;    // dynaaminen tausta Free Pascalille
{$ELSE}
  PDFlibJBIG2EncC;      // staattinen objektijoukko Delphille / C++Builderille
{$ENDIF}

var
  Pdf: TPDFlib;
  ImageId: Integer;
begin
  Pdf := TPDFlib.Create(nil);
  try
    Pdf.NewDocument;
    Pdf.NewPage;
    // Interpolate, SymbolExtract, UseExternalEncoder, SkipBlackDots,
    // BlackDotSize, LossyLevel
    ImageId := Pdf.AddImageJBIG2FromFileEx('scan-page-1.tif',
      0, 1, 1, 0, 0, 0);
    if ImageId = 0 then
      raise Exception.Create('JBIG2 encoding failed');
    Pdf.SaveToFile('archive.pdf');
  finally
    Pdf.Free;
  end;
end;

Yksikön kääntäminen oli kaksi riviä. Symbolit olivat työ

Taustayksikön itsensä saaminen kääntymään Free Pascalilla vaati täsmälleen kaksi muutosta: konekielimurron asettamisen ja tietuepohjaisen muotoilusetukonstruktorin korvaamisen globaalilla oletusmuuttujalla. Se on rehellinen kuva siitä, kuinka siirrettävä suoraviivainen Pascal on näiden kahden kääntäjän välillä

Symbolipuoli oli varsinainen työ. Objektijoukko viittaa 176 C-symboliin. Niistä 128:lla oli jo Pascal-toteutukset yksikön sisällä ja ne tarvitsivat vain vientinimet kiinnitettäväkseen, koska Delphi käyttää funktion nimeä symbolin nimenä, kun taas Free Pascal vaatii nimenomaisen julkisen nimideklaraation. Kaksikymmentäseitsemän olivat yhteisiä JPEG 2000 -koodekin kanssa ja ne piti viedä täsmälleen yhdestä paikasta, sillä kahdesti määritteleminen rikkoo jokaisen ohjelman, joka linkittää molemmat. Loput 21 olivat alusta- ja C-ajonaikaisuusmerkintöjä, kuusitoista Win32-tiedostofunktiota plus kourallinen vakiotukikutsuja, ja ne menivät uuteen yhteensopivuusyksikköön

Mikään niistä ei ole käsitteellisesti vaikeaa, ja kaikki se on tarpeen ennen kuin linkkeri edes yrittää. Linkkeri on paikka, jossa se pysähtyi

Kolme linkitysreittiä, kolme umpikujaa

Free Pascalin sisäinen linkkeri ei pysty lukemaan objektitiedostoja, koska ne tuotti kääntäjä, joka emittoi assosiatiivisia COMDAT-osioita, ja sisäinen linkkeri ilmoittaa, ettei tue niitä. Se on tasainen kieltäytyminen, ei varoitus

Siirtyminen ulkoiseen linkkeriin näytti vastaukselta. Free Pascalin mukana toimitettava binutils-linkkeri kaatuu suoraan soveltaessaan osioroskienkeruuta tähän arkistoon, ja se lippu on osa kiinteää parametrijoukkoa, jonka Free Pascal välittää 64-bittiselle Windows-kohteelle, joten sitä ei voi poistaa komentoriviltä; sen tukahduttamiseksi dokumentoidut kytkimet ohitetaan tällä polulla. Paljon uudemman binutilsin toimittaminen sen sijaan epäonnistuu eri tavalla: se ei pysty käsittelemään Free Pascal -linkitysskriptiä lainkaan, tuottaen tyhjän tulosteen ilman skriptiä ja seinällisen relokaatiovirheitä sen kanssa

Matkan varrella havaittu raja on syytä tuntea, vaikka et koskaan osuisi linkkeriongelmaan. Ulkoinen linkkeri ratkaisee objektitiedostopolut suoritettavan tulostushakemiston suhteen lähdepuun sijaan, joten suhteellinen include-object-direktiivi toimii vain silloin, kun tulostushakemisto sattuu olemaan sama kuin kääntöhetkinen työhakemisto. Kirjasto ei voi olettaa sellaista kuluttajan projektista, mikä itsessään on syy suosia linkitettyä kirjastoa irrallisten objektien sijaan

Kolme epäonnistunutta linkitysreittiä C++-JBIG2-koodaajan objekteille Free Pascalilla sekä DLL, joka paljastaa kaksi tasaista C-sisääntulopistettä ja ratkaisi ongelman
COMDAT-osiot kukistavat sisäisen linkkerin ja molemmat ulkoiset linkkerit epäonnistuvat, joten C++-koodaaja toimitetaan yhtenä DLL:änä, jonka taustayksikkö sitoo dynaamisesti

Miksi toinen C++-kääntäjä ei auta

Ilmeinen seuraava idea on kääntää C++-puoli uudelleen kääntäjällä, jonka objektit Free Pascal osaa lukea. Se ei toimiakaan, ja syy on perustavaa laatua eikä kytkimien asia. Minimaalinen C++-käännösyksikkö, joka sisältää templaatin ja joka on käännetty jokainen koodingenerointiominaisuus pois päältä, emittoi silti heikkoja ulkoisia symboleita, koska templaatti- ja inline-instanssointi tuottaa ne rakenteensa puolesta. Free Pascal hylkää tuon symboliluokan täysin. Käänteinen suunta epäonnistuu myös: päävirtauksen C++-linkkeri ei voi nielaista toisen kääntäjän objekteja saman COMDAT-osionkäsittelyn vuoksi

C++-koodia ei siis voi toimittaa objekteina Free Pascalille millään saatavilla olevalla reitillä. Sen voi toimittaa DLL:änä, ja niin kävi: koodaaja ja sen kuvankäsittelyriippuvuus rakennetaan yhdeksi kirjastoksi, joka paljastaa kaksi tasaista C-sisääntulopistettä, ja Free Pascal -taustayksikkö sitoo ne dynaamisesti ja rekisteröi itsensä täsmälleen kuten staattinen taustakin. Delphi- ja C++Builder-polkuun ei koskettu lainkaan, mikä on oikea lopputulos; portattavuusongelma yhdellä työkaluketjulla ei saa häiritä työkaluketjua, joka jo toimi

Polariteetti on se yksi asia, joka puree sinua

Windowsin kaksitasoisen bittikartan ja JBIG2-koodaajan välillä on käytäntöero, jonka mikään tyyppijärjestelmä ei nappaa. Yksi bitti per pikseli -laiterippumattoman bittikartan scanline-rivi pitää asetettua bittiä valkoisena. Koodaaja pitää asetettua bittiä mustana. Anna scanline-rivit ennallaan, ja saat täysin pätevän JBIG2-virran sivusi valokuvanegatiivista

Yksibittisen DIB:n ja JBIG2:n polariteettikäytännöt, joissa asetettu bitti on valkoinen scanlinessa ja musta koodaajassa, korjattuna kääntämällä jokainen tavu
Samat tavut, vastakkainen merkitys: ilman jokaisen tavun kääntämistä koodaaja tuottaa pätevän JBIG2-virran valokuvanegatiivista
// Yksibittinen DIB: asetettu bitti tarkoittaa valkoista. JBIG2-koodaaja:
// asetettu bitti tarkoittaa mustaa. Käännä jokainen tavu matkalla sisään
for I := 0 to RowBytes - 1 do
  Row[I] := Row[I] xor $FF;

Tarkistusmenetelmä merkitsee yhtä paljon kuin korjaus. Pakattujen virtojen pituuksien vertailu ei kerro mitään, koska negatiivikuva pakattuu samankokoiseksi. Sivun katsominen todistaa vain, ettei se ole ilmeisesti käännetty. Luotettava tarkistus on renderöidä molempien koodauspolkujen, natiivin Pascalin ja ulkoisen, tuloste PNG:ksi ja vertailla niitä tavu tavulta: molemmat koodaajat ovat häviöttömiä samalla lähdekuvalla, joten mikä tahansa muu kuin täsmällinen vastaavuus on virhe toisessa niistä. Tuo vertailu on nyt pysyvä regressiotesti, ja se on sellainen assertio, joka kannattaa rakentaa aina, kun kaksi toteutusta on tarkoitus sopia yhteen täsmälleen

Kumpaa taustaa käyttää

Yleiseen kaksitasosisältöön, ditherattuihin rasterikuviin, viivapiirrokseen, sekagrafiikkaan, natiivi Pascal-MMR-koodaaja riittää, eikä sillä ole jakelukustannusta. Skannatulle tekstille, joka on se tapaus, jota varten JBIG2 suunniteltiin, ulkoinen symbolisanakirjakoodaaja on se, jossa koon pieneneminen asuu, koska se jakaa toistuvat gliftimuodot sanakirjaan uudelleenkoodaamatta jokaista esiintymää. Jos tuotat skannattujen dokumenttien arkistoja, ero on tarpeeksi suuri muuttamaan tallennussuunnittelua

Ylävirran kysymys, miten kaksitasokuva ensinnäkin tuotetaan, merkitsee yhtä paljon tulosteen koolle; aluepohjainen monokromirenderöinti käsitellään artikkelissa monokromialueen renderöinnistä, ja dokumentinlaajuinen kokonaistrategia artikkelissa PDF-tiedostokoon optimoinnista ja fonttien subsetoinnista. Skannausjoukoille, joissa sivut toistuvat, deduplikaatio usein lyö paremman pakkauksen, mistä kerrotaan artikkelissa perseptuaalinen kuvadeduplikaatio. Työkaluketjun ja taustojen saatavuus alustaa kohden on lueteltu losLab PDF Developer Library -tuotesivulla