Tehnički članak

Deljenje JBIG2 rečnika simbola između stranica u Delphiju

Ugovor skeniran na pedeset stranica ponavlja isti alfabet na svakoj stranici, ali JBIG2 koder koji pravi po jedan rečnik simbola za svaku sliku ponavlja obuku tog alfabeta pedeset puta. HotPDF, izvorna PDF komponenta za Delphi i C++Builder, umesto toga može da akumulira jedan deljeni rečnik simbola kroz ceo dokument i da ga pretvori u jedinstveni tok /JBIG2Globals na nivou dokumenta, tako da sopstveni JBIG2 tok svake stranice samo upućuje na ID-jeve simbola umesto da čuva sopstvenu kopiju alfabeta

Ovaj tekst je namerno usko usmeren i objašnjava samo kako HotPDF interno gradi deljenje između stranica — osnove JBIG2 formata, poređenje sa CCITT-om i razmatranje odnosa između Lossless i LossyLevel već su obrađeni u pratećem članku o izvornoj JBIG2 binarnoj kompresiji u Delphiju, koji podrazumeva da ste pročitali

Zašto kompresija JBIG2 po stranici i dalje ponavlja isti trošak?

Razlog je to što se stanje ne prenosi između poziva. Svaki put kada HotPDF-ov koder napravi rečnik simbola za jednu sliku, taj rečnik je ograničen na jedan poziv AddImage: prolaz za poređenje oblika počinje od nule, svaki glif na stranici klasifikuje se kao nov, a dobijene bitmape se iznova aritmetički kodiraju i čuvaju. Prosledite istom koderu pedeset stranica u istom pismu i on će bez oklevanja ponoviti celu obuku pedeset puta, jer je iz njegove perspektive svaka stranica nepovezana slika koja samo slučajno izgleda slično. UseSymbolDictionary po stranici već znatno nadmašuje ravno kodiranje generičkih regiona na jednoj stranici, ali se zaustavlja mnogo pre granice koju pravo skeniranje sa više stranica ostavlja neiskorišćenom

Kako HotPDF deli jedan rečnik simbola između stranica?

Uključite AccumulateGlobalsAcrossPages u opcijama THPDFJBIG2Options i HotPDF će držati jedan rečnik simbola u memoriji tokom celog životnog veka dokumenta, umesto da ga odbacuje posle svake slike. Glifovi svake naredne stranice proveravaju se u odnosu na taj tekući rečnik pre novog kodiranja: oblik koji već postoji ponovo se koristi preko svog ID-ja simbola, dok se samo oblik koji još nije viđen dodaje i kodira u rečnik. Poređenje koristi istu logiku tolerancije koju LossyLevel primenjuje na jednoj stranici — blago šumovit sken istog slova i dalje se računa kao podudaranje — pa akumulator ne narasta neprimetno do jednog unosa rečnika za svaku piksel-po-piksel varijaciju istog glifa. Izdvajanje se obavlja prvo i daje podatke tom poređenju: HotPDF prolazi kroz bitmapu svake stranice i izdvaja povezane oblike popunjavanjem po crnim pikselima, po istoj ideji kao ručno praćenje mrlja mastila, a sa tekućim rečnikom porede se upravo ti izdvojeni oblici, ne sirovi blokovi piksela

Kako se deljeni rečnik smešta u tok /JBIG2Globals?

Akumulirani rečnik zapisuje se kao jedan segment rečnika simbola unutar toka /JBIG2Globals i čuva se pod fiksnim brojem segmenta, tako da svaka stranica može da pokaže na isti cilj. U ugrađenoj JBIG2 organizaciji koju definiše ISO 32000-1 §7.4.7, segment tekstualnog regiona može da navede drugi segment kao izvor simbola kroz polje referred-to-segment u zaglavlju segmenta, a to je tačan mehanizam na koji se HotPDF oslanja: tok globalnih podataka nosi veliki rečnik simbola, dok se sopstveni JBIG2 tok svake stranice smanjuje na segment informacija o stranici i segment tekstualnog regiona čija lista referred-to pokazuje nazad na globalni segment. Ono što je ranije bio samostalni bitni tok po stranici postaje kratka lista položaja i ID-jeva simbola, a svaka stranica napravljena na ovaj način upućuje na isti indirektni objekat /JBIG2Globals, umesto na njegovu kopiju. HotPDF-ovi regresioni testovi proveravaju upravo to: kodiraju kratak dokument u kojem svaka stranica ima drugačiji raspored glifova, ponovo ga učitavaju i broje koliko se različitih referenci na objekte /JBIG2Globals pojavljuje u datoteci — jedan dokument, jedna referenca na objekat, bez obzira na broj stranica koje su doprinele simbole

Uključivanje akumulacije rečnika simbola između stranica

Prekidač se nalazi u istom zapisu opcija koji je obrađen u pratećem članku, a akumulacija se zaista uključuje tek kada se četiri podešavanja međusobno slažu

var
  Pdf: THotPDF;
  Bmp: TBitmap;
  PageIdx, ImgIdx: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.JBIG2Options.Lossless := True;
    Pdf.JBIG2Options.UseSymbolDictionary := True;
    Pdf.JBIG2Options.UseGlobalSegments := True;
    Pdf.JBIG2Options.AccumulateGlobalsAcrossPages := True;  // opt-in, default False
    Pdf.JBIG2Options.UseExternalEncoder := False;            // accumulation needs the native path
    Pdf.JBIG2Options.UseNativeArithmeticFallback := True;
    Pdf.BeginDoc;
    for PageIdx := 0 to ScannedPages.Count - 1 do
    begin
      if PageIdx > 0 then
        Pdf.AddPage;
      Bmp := ScannedPages[PageIdx];             // 1-bit TBitmap for this page
      ImgIdx := Pdf.AddImage(Bmp, icJBIG2);
      Pdf.CurrentPage.ShowImage(ImgIdx, 0, 0, Bmp.Width, Bmp.Height, 0);
    end;
    Pdf.EndDoc;                                  // the shared /JBIG2Globals stream is finalized here
  finally
    Pdf.Free;
  end;
end;

Ova kombinacija nije ukrasna opcija. Spoj sa spoljnim koderom opisan u članku o binarnoj kompresiji — onaj koji registrujete preko RegisterJBIG2EncoderBackend za produkcione odnose kompresije — zasnovan je na kodiranju po slici, a HotPDF-ovi primeri akumulacije i regresioni testovi uvek uparuju AccumulateGlobalsAcrossPages sa UseExternalEncoder := False. Tretirajte to kao čvrst zahtev, a ne kao preporuku: deljenje između stranica je funkcija izvornog kodera, a registrovani spoljni backend jednostavno nije deo putanje koja gradi deljeni rečnik

Koliko se sken sa više stranica zaista smanjuje?

Pošten odgovor počinje onim što prvo nije donelo značajnu razliku. Ranije izdanje dodalo je keš za tokove /JBIG2Globals zasnovan na sadržaju — pretragu po 64-bitnom FNV-1a hešu bajtova toka, tako da dve slike koje slučajno daju bajt-po-bajt identične globalne podatke mogu da dele jedan PDF objekat. Mereno na stvarnom izlazu, taj keš je jedva pomogao, jer je HotPDF-ovo postojeće otkrivanje duplikata celih slika već spajalo bajt-po-bajt identične slike pre nego što je keš uopšte stigao da se pokrene. Pouka je da deduplikacija na nivou toka počinje da se isplati tek kada dve zaista različite slike stranica i dalje mogu da dele jedan rastući rečnik, a upravo to omogućava prava akumulacija između stranica

U tom zahtevnijem slučaju HotPDF-ova inženjerska procena navodi dodatnu uštedu od približno 30 do 60 procenata u odnosu na ono što sama deduplikacija na nivou toka postiže, za tipičan sken sa više stranica izgrađen oko jednog ponavljanog fonta — raspon zavisi od toga koliko se vizuelni rečnik dokumenta zaista ponavlja, jer stranica puna jedinstvenih dijagrama ne daje rečniku ništa za ponovnu upotrebu. Shvatite to kao cilj dizajna, a ne kao garanciju za određeni ulaz, i merite sopstvene dokumente umesto da verujete jednom broju. Demo JBIG2Benchmark koji dolazi uz HotPDF postoji upravo zbog toga: kodira isti sken sa više stranica na četiri različita načina i ispisuje dobijenu veličinu datoteke za svaku konfiguraciju, pa se poređenje zasniva na vašoj kombinaciji skenova, a ne na sintetičkom primeru

procedure RunScenario(const Title: string; AccumulateGlobals: Boolean);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.JBIG2Options.Lossless := True;
    Pdf.JBIG2Options.UseSymbolDictionary := True;
    Pdf.JBIG2Options.UseGlobalSegments := True;
    Pdf.JBIG2Options.AccumulateGlobalsAcrossPages := AccumulateGlobals;
    Pdf.JBIG2Options.UseExternalEncoder := not AccumulateGlobals;
    // ... encode the same three-page scan here, then compare file sizes.
  finally
    Pdf.Free;
  end;
end;

begin
  RunScenario('Per-image lossless baseline', False);
  RunScenario('Cross-page accumulated globals', True);
end.

Gde akumulacija između stranica dostiže svoje granice

Akumulirani rečnik ograničen je na 4096 simbola, što je ista granica koju izvorni koder po slici već primenjuje na jednoj stranici. Ako se ta granica pređe usred dokumenta, HotPDF ne podiže izuzetak niti prekida obradu: akumulator odbija novi glif, a stranica koja ga je uvela automatski prelazi na nezavisno kodiranje po slici, pa dokument i dalje ostaje ispravan — samo prestajete da dobijate uštedu deljenja između stranica za one stranice koje su prešle granicu. Druga zaštita prati ukupnu veličinu, a ne broj simbola: kada zbirna širina simbola u akumuliranom rečniku pređe 131071 piksel, HotPDF automatski upisuje trenutnu grupu na disk i započinje novu grupu globalnih podataka, umesto da dozvoli da jedna struktura u memoriji raste bez ograničenja. Nijedno ograničenje ne zahteva kod sa vaše strane, jer su oba slučaja automatski rezervni putevi, a ne izuzeci koje morate da hvatate

Usklađenost sa PDF/A je jedino podešavanje koje isključuje ceo mehanizam, umesto da ga samo ograniči. HotPDF tiho zamenjuje JBIG2 formatom CCITT Group 4 čim je PDFACompliance neprazan, na svakoj stranici, nezavisno od AccumulateGlobalsAcrossPages ili bilo čega drugog u JBIG2Options — to je namerna odluka radi usklađenosti, a ne greška, ali znači da se arhivski profil i deljenje simbola između stranica danas međusobno isključuju. Bez obzira na izabranu konfiguraciju, dekodirajte ono što ste zapisali pre nego što mu poverujete: ponovo učitajte datoteku pomoću LoadFromFile, provucite svaku stranicu kroz ExtractLoadedImage, koji za vas razrešava deljene globalne podatke na isti način kao usklađeni čitač, i uporedite rezultat sa izvornim bitmapama

var
  Loaded: THotPDF;
  PageBmp: TBitmap;
  PageIdx: Integer;
begin
  Loaded := THotPDF.Create(nil);
  try
    Loaded.LoadFromFile('scanned-contract.pdf');
    for PageIdx := 0 to Loaded.PagesCount - 1 do
    begin
      PageBmp := Loaded.ExtractLoadedImage(PageIdx);   // resolves the shared globals for you
      try
        // Compare PageBmp against the source bitmap for this page.
      finally
        PageBmp.Free;
      end;
    end;
  finally
    Loaded.Free;
  end;
end;

Deljenje rečnika između stranica dotiče samo stranu dokumenta koja se odnosi na binarne slike. Ako isti tok rada uz skenove generiše i stranice sa tekstom — naslovne listove, stranice sa indeksom ili OCR sloj teksta — tokovi objekata i xref tokovi smanjuju drugu polovinu budžeta veličine datoteke kompresovanjem strukture dokumenta koju te stranice dodaju. Globalni JBIG2 podaci između stranica deo su HotPDF Component za Delphi i C++Builder, zajedno sa JBIG2 opcijama po slici i ostatkom toka kompresije