Skenirani ugovor od pedeset stranica ponavlja isti alfabet na svakoj stranici, no JBIG2 enkoder koji izgradi po jedan rječnik simbola za svaku sliku taj alfabet ponovno „trenira" pedeset zasebnih puta. HotPDF, izvorna Delphi i C++Builder PDF komponenta, umjesto toga može akumulirati jedan zajednički rječnik simbola kroz cijeli dokument i podići ga na razinu jednog toka /JBIG2Globals na razini dokumenta, tako da vlastiti JBIG2 tok svake stranice samo upućuje na ID-ove simbola umjesto da pohranjuje vlastitu kopiju alfabeta
Ovaj tekst namjerno ostaje uzak i pokriva samo kako HotPDF interno gradi to dijeljenje između stranica — osnove JBIG2 formata, usporedba s CCITT-om i kompromisi između Lossless i LossyLevel već su opisani u pratećem članku o izvornoj JBIG2 bilevel kompresiji u Delphiju, koji ovaj tekst pretpostavlja da ste pročitali
Zašto JBIG2 kompresija po stranicama i dalje ponavlja isti trošak?
Odgovor je da ništa ne prenosi stanje između poziva. Svaki put kad HotPDF-ov enkoder izgradi rječnik simbola za jednu sliku, taj je rječnik ograničen na taj jedan poziv AddImage: prolaz podudaranja oblika kreće od nule, svaki znak na stranici klasificira se kao nov, a rezultirajuće bitmape se aritmetički kodiraju i pohranjuju iznova. Predate li istom enkoderu pedeset stranica složenih istim pismom, on rado ponavlja cijeli taj prolaz treniranja pedeset puta, jer je s njegovog gledišta svaka stranica nepovezana slika koja se slučajno slično izgleda. UseSymbolDictionary po stranici već znatno nadmašuje ravno kodiranje generičkog područja na jednoj stranici, no zaustavlja se daleko ispod granice koju pravi višestranični sken ostavlja na stolu
Kako HotPDF dijeli jedan rječnik simbola preko stranica?
Omogućite li AccumulateGlobalsAcrossPages na THPDFJBIG2Options, HotPDF drži jedan rječnik simbola živim u memoriji kroz cijeli vijek trajanja dokumenta, umjesto da ga odbaci nakon svake slike. Znakovi svake sljedeće stranice provjeravaju se prema tom tekućem rječniku prije nego što se bilo što ponovno kodira: oblik koji već postoji ponovno se koristi preko svog ID-a simbola, a samo se oblik koji nitko dosad nije vidio dodaje i kodira u rječnik. Usporedba ponovno koristi istu logiku tolerancije koju LossyLevel primjenjuje na jednoj stranici — malo zašumljen sken istog slova i dalje se broji kao podudaranje — pa akumulator tiho ne naraste u po jedan unos rječnika za svaku varijaciju istog znaka na razini piksela. Izdvajanje se odvija prvo i hrani tu usporedbu: HotPDF prolazi kroz bitmapu svake stranice i izvlači povezane oblike putem flood fill algoritma nad crnim pikselima, iste ideje kao ručno obrisivanje mrlja tinte, i upravo se ti izdvojeni oblici, a ne sirovi blokovi piksela, uspoređuju s tekućim rječnikom
Kako se zajednički rječnik smješta unutar toka /JBIG2Globals
Akumulirani rječnik zapisuje se kao jedan segment rječnika simbola unutar toka /JBIG2Globals, smješten na fiksnom broju segmenta kako bi svaka stranica mogla upućivati na istu metu. Unutar organizacije ugrađenog JBIG2 formata koju definira ISO 32000-1 §7.4.7, segment tekstualnog područja može imenovati drugi segment kao svoj izvor simbola kroz polje upućenog segmenta (referred-to-segment) u zaglavlju segmenta, i to je upravo mehanizam na koji se HotPDF oslanja: tok globala nosi jedan veliki rječnik simbola, a vlastiti JBIG2 tok svake stranice svodi se na segment podataka o stranici plus segment tekstualnog područja čiji popis upućivanja pokazuje natrag na segment globala. Ono što je nekad bio samostalan bitni tok po stranici postaje kratak popis pozicija i ID-ova simbola, a svaka stranica izgrađena na ovaj način upućuje na identičan neizravan objekt /JBIG2Globals, a ne na njegovu kopiju. HotPDF-ovo vlastito regresijsko pokrivanje provjerava upravo to: kodira kratak dokument u kojem svaka stranica ima drugačiji raspored znakova, ponovno ga učitava i broji koliko se različitih referenci na objekt /JBIG2Globals pojavljuje u datoteci — jedan dokument, jedna referenca na objekt, bez obzira koliko je stranica pridonijelo simbolima
Uključivanje akumulacije rječnika simbola preko stranica
Prekidač se nalazi na istom zapisu opcija opisanom u pratećem članku, a potrebna su četiri postavljanja koja se moraju međusobno slagati prije nego što se akumulacija zaista pokrene
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;
To sparivanje nije neobavezan detalj. Sučelje za vanjski enkoder opisano u članku o bilevel kompresiji — ono koje registrirate preko RegisterJBIG2EncoderBackend za omjere produkcijske razine — izgrađeno je oko kodiranja po slici, a HotPDF-ovi vlastiti demonstracijski primjeri akumulacije i regresijski testovi uvijek sparuju AccumulateGlobalsAcrossPages s UseExternalEncoder := False. Tretirajte to kao tvrd zahtjev, a ne prijedlog: dijeljenje preko stranica značajka je izvornog enkodera, a registrirani vanjski backend jednostavno nije dio putanje koja gradi zajednički rječnik
Koliko se zapravo smanji višestranični sken?
Iskren odgovor počinje s onim što isprva nije pomaklo iglu. Ranije izdanje dodalo je predmemoriju adresiranu prema sadržaju za tokove /JBIG2Globals — pretragu ključanu 64-bitnim FNV-1a hashom bajtova toka, tako da su dvije slike koje su slučajno proizvele bajtno identične podatke globala mogle dijeliti jedan PDF objekt. Mjereno na stvarnom izlazu, ta je predmemorija jedva pomogla, jer je HotPDF-ovo postojeće otkrivanje duplikata cijelih slika već stapalo bajtno identične slike prije nego što je predmemorija uopće dobila priliku raditi. Pouka je bila da se deduplikacija na razini toka isplati tek kad dvije stvarno različite slike stranica i dalje mogu dijeliti jedan rječnik koji raste, a to je upravo ono što isporučuje istinska akumulacija preko stranica
Za taj teži slučaj, HotPDF-ova vlastita inženjerska procjena stavlja dodatnu uštedu na otprilike 30 do 60 posto manju veličinu od one koju sama deduplikacija na razini toka postiže, za tipičan višestranični sken izgrađen od jednog fonta koji se ponavlja — raspon se mijenja ovisno o tome koliko se vizualnog rječnika dokumenta zaista ponavlja, jer stranica puna jedinstvenih dijagrama rječniku ne daje ništa za ponovnu upotrebu. Tretirajte to kao ciljnu vrijednost dizajna, a ne jamstvo za bilo koji konkretan ulaz, i mjerite vlastite dokumente umjesto da vjerujete jednom broju. Demonstracijski primjer JBIG2Benchmark koji se isporučuje uz HotPDF postoji upravo za tu svrhu: kodira isti višestranični sken na četiri različita načina i ispisuje rezultirajuću veličinu datoteke za svaku konfiguraciju, tako da se usporedba provodi nad vašom vlastitom mješavinom skenova umjesto sintetičkom
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.
Gdje akumulacija preko stranica dosegne svoje granice
Akumulirani rječnik ograničen je na 4096 simbola, istu granicu koju izvorni enkoder po slici već nameće na jednoj stranici. Premašite li tu granicu usred dokumenta, HotPDF ne izbacuje iznimku niti prekida izvođenje: akumulator odbija novi znak, a stranica koja ga je uvela automatski se vraća na neovisno kodiranje po slici, pa dokument i dalje ispada ispravan — samo prestajete dobivati uštedu preko stranica za one stranice koje su premašile granicu. Druga zaštita prati ukupnu veličinu, a ne broj simbola: čim kombinirana širina simbola akumuliranog rječnika prijeđe 131071 piksela, HotPDF automatski izlijeva trenutnu skupinu na disk i pokreće svježu skupinu globala, umjesto da dopusti jednoj strukturi u memoriji da raste bez ograničenja. Nijedna od ovih granica ne zahtijeva kod s vaše strane, jer su obje automatski rezervni mehanizmi, a ne iznimke koje trebate hvatati
PDF/A sukladnost jedina je postavka koja cijeli mehanizam isključuje umjesto da ga samo ograniči. HotPDF tiho zamjenjuje JBIG2 CCITT Group 4 formatom čim PDFACompliance nije prazan, na svakoj stranici, neovisno o AccumulateGlobalsAcrossPages ili bilo čemu drugom u JBIG2Options — namjeran izbor sukladnosti, a ne greška, no to znači da su arhivski profil i dijeljenje simbola preko stranica danas međusobno isključivi. Bez obzira na konfiguraciju na kojoj se odlučite, dekodirajte ono što ste zapisali prije nego što tome povjerujete: učitajte datoteku natrag pomoću LoadFromFile i izvucite svaku stranicu kroz ExtractLoadedImage, koja umjesto vas razrješava zajedničke globale na isti način kao i svaki usklađen čitač, te usporedite rezultat sa svojim 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;
Dijeljenje rječnika preko stranica dotiče se samo bilevel slikovnog dijela dokumenta. Ako isti cjevovod uz skenove generira i tekstualne stranice — naslovnice, indeksne stranice, sloj teksta iz OCR-a — tokovi objekata i xref tokovi napadaju drugu polovicu proračuna veličine datoteke komprimirajući strukturu dokumenta koju te stranice dodaju. Dijeljenje JBIG2 globala preko stranica isporučuje se kao dio HotPDF komponente za Delphi i C++Builder, uz opcije JBIG2 po slici i ostatak cjevovoda za kompresiju