Artikel Teknis

Berbagi Symbol Dictionary JBIG2 Lintas Halaman di Delphi

Sebuah kontrak hasil scan lima puluh halaman mengulang alfabet yang sama di setiap halaman, tetapi sebuah encoder JBIG2 yang membangun satu symbol dictionary per gambar akan melatih ulang alfabet itu lima puluh kali terpisah. HotPDF, komponen PDF native Delphi dan C++Builder, sebagai gantinya bisa mengakumulasi satu symbol dictionary bersama di seluruh dokumen dan mempromosikannya menjadi satu stream /JBIG2Globals tingkat-dokumen tunggal, sehingga stream JBIG2 milik setiap halaman hanya merujuk ke ID simbol alih-alih menyimpan salinannya sendiri dari alfabet tersebut

Artikel ini sengaja dibuat sempit dan hanya membahas bagaimana HotPDF membangun cross-page sharing itu secara internal — dasar-dasar JBIG2, perbandingan dengan CCITT, dan trade-off Lossless versus LossyLevel sudah dibahas di artikel pendamping tentang kompresi bilevel JBIG2 native di Delphi, yang diasumsikan sudah dibaca lebih dulu

Mengapa kompresi JBIG2 per-halaman masih mengulang biaya yang sama?

Jawabannya adalah tidak ada apa pun yang membawa state di antara pemanggilan. Setiap kali encoder milik HotPDF membangun sebuah symbol dictionary untuk satu gambar, dictionary itu terlingkup pada satu pemanggilan AddImage tersebut: langkah pencocokan bentuk dimulai dari nol, setiap glyph pada halaman diklasifikasikan sebagai baru, dan bitmap hasilnya di-encode secara aritmetika dan disimpan baru. Beri makan encoder yang sama dengan lima puluh halaman dalam typeface yang sama dan ia dengan senang hati mengulang seluruh langkah pelatihan itu lima puluh kali, karena dari sudut pandangnya setiap halaman adalah gambar yang tidak berkaitan yang kebetulan terlihat mirip. UseSymbolDictionary per-halaman sudah mengalahkan encode generic-region datar dengan margin lebar pada satu halaman, tetapi mentok jauh sebelum batas atas yang ditinggalkan sebuah scan multi-halaman sungguhan di atas meja

Bagaimana HotPDF berbagi satu symbol dictionary lintas halaman?

Aktifkan AccumulateGlobalsAcrossPages pada THPDFJBIG2Options dan HotPDF menjaga satu symbol dictionary tetap hidup di memori sepanjang umur dokumen alih-alih membuangnya setelah setiap gambar. Glyph setiap halaman berikutnya diperiksa terhadap dictionary yang sedang berjalan itu sebelum apa pun di-encode ulang: sebuah bentuk yang sudah ada digunakan kembali lewat ID simbolnya, dan hanya bentuk yang belum pernah dilihat siapa pun yang ditambahkan dan di-encode ke dalam dictionary. Perbandingan ini menggunakan kembali logika toleransi yang sama yang diterapkan LossyLevel pada satu halaman — sebuah scan huruf yang sama dengan sedikit noise tetap dihitung sebagai kecocokan — sehingga akumulator tidak diam-diam membengkak menjadi satu entri dictionary per variasi tingkat-piksel dari glyph yang sama. Ekstraksi terjadi lebih dulu dan memberi makan perbandingan itu: HotPDF menelusuri bitmap setiap halaman dan menarik keluar bentuk-bentuk yang terhubung lewat flood fill terhadap piksel hitam, ide yang sama seperti menelusuri gumpalan tinta dengan tangan, dan bentuk-bentuk yang diekstrak itulah, bukan blok piksel mentah, yang dibandingkan terhadap dictionary yang sedang berjalan

Bagaimana dictionary bersama itu duduk di dalam sebuah stream /JBIG2Globals

Dictionary yang terakumulasi ditulis sebagai satu segmen symbol-dictionary di dalam stream /JBIG2Globals, dipegang pada sebuah nomor segmen tetap sehingga setiap halaman bisa menunjuk ke target yang sama. Di dalam organisasi JBIG2 yang disematkan yang didefinisikan ISO 32000-1 §7.4.7, sebuah segmen text-region bisa menyebutkan segmen lain sebagai sumber simbolnya lewat field referred-to-segment dalam header segmen, dan itulah persis mekanisme yang diandalkan HotPDF: stream globals membawa satu symbol dictionary besar, dan stream JBIG2 milik setiap halaman menyusut menjadi sebuah segmen page-info plus sebuah segmen text-region yang daftar referred-to-nya menunjuk kembali ke segmen globals. Apa yang dulunya sebuah bitstream mandiri per halaman menjadi daftar pendek posisi dan ID simbol, dan setiap halaman yang dibangun dengan cara ini merujuk ke objek indirect /JBIG2Globals yang identik alih-alih salinannya. Cakupan regresi milik HotPDF sendiri memeriksa persis itu: encode sebuah dokumen pendek di mana setiap halaman memiliki tata letak glyph yang berbeda, muat ulang, dan hitung berapa banyak referensi objek /JBIG2Globals berbeda yang muncul dalam file — satu dokumen, satu referensi objek, tidak peduli berapa banyak halaman yang menyumbangkan simbol padanya

Mengaktifkan akumulasi symbol dictionary lintas halaman

Sakelarnya berada pada record opsi yang sama yang dibahas artikel pendamping, dan membutuhkan empat pengaturan yang sepakat satu sama lain sebelum akumulasi benar-benar aktif

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;

Pemasangan itu bukan hiasan opsional. Seam encoder eksternal yang dijelaskan di artikel kompresi-bilevel — yang Anda daftarkan lewat RegisterJBIG2EncoderBackend untuk rasio kelas produksi — dibangun di sekitar encoding per-gambar, dan demo akumulasi serta test regresi milik HotPDF sendiri selalu memasangkan AccumulateGlobalsAcrossPages dengan UseExternalEncoder := False. Perlakukan itu sebagai persyaratan keras alih-alih sekadar saran: cross-page sharing adalah fitur encoder native, dan sebuah backend eksternal yang terdaftar sekadar bukan bagian dari jalur yang membangun dictionary bersama itu

Seberapa jauh sebenarnya sebuah scan multi-halaman mengecil?

Jawaban jujurnya dimulai dari apa yang ternyata tidak banyak berpengaruh lebih dulu. Sebuah rilis sebelumnya menambahkan cache content-addressed untuk stream /JBIG2Globals — sebuah lookup yang dikunci oleh hash FNV-1a 64-bit dari byte stream, sehingga dua gambar yang kebetulan menghasilkan data globals identik-byte bisa berbagi satu objek PDF. Diukur terhadap output sungguhan, cache itu nyaris tidak membantu, karena deteksi duplikat seluruh-gambar yang sudah ada di HotPDF sudah menciutkan gambar identik-byte sebelum cache itu sempat mendapat kesempatan berjalan. Pelajarannya adalah deduplikasi tingkat-stream hanya membuahkan hasil begitu dua gambar halaman yang benar-benar berbeda masih bisa berbagi satu dictionary yang sedang tumbuh, dan itulah yang diberikan akumulasi lintas-halaman sungguhan

Untuk kasus yang lebih sulit itu, estimasi rekayasa milik HotPDF sendiri menempatkan penghematan tambahan di sekitar 30 hingga 60 persen lebih kecil dibanding yang dicapai deduplikasi tingkat-stream saja, untuk sebuah scan multi-halaman tipikal yang dibangun dari satu font yang berulang — rentang itu bergerak sesuai seberapa banyak kosakata visual dokumen yang benar-benar berulang, karena sebuah halaman penuh diagram unik tidak memberikan apa pun untuk digunakan ulang dictionary. Perlakukan itu sebagai target desain alih-alih jaminan untuk input spesifik mana pun, dan ukur dokumen Anda sendiri alih-alih memercayai satu angka. Demo JBIG2Benchmark yang disertakan HotPDF ada persis untuk tujuan itu: ia meng-encode scan multi-halaman yang sama dengan empat cara berbeda dan mencetak ukuran file hasilnya untuk setiap konfigurasi, sehingga perbandingan berjalan terhadap campuran scan Anda sendiri alih-alih yang sintetis

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.

Di mana akumulasi lintas-halaman mencapai batasnya

Dictionary yang terakumulasi dibatasi pada 4096 simbol, batas atas yang sama yang sudah diterapkan encoder native per-gambar pada satu halaman. Lewati batas itu di tengah dokumen dan HotPDF tidak memunculkan exception atau membatalkan proses: akumulator menolak glyph baru itu, dan halaman yang memperkenalkannya jatuh kembali ke encoding per-gambar independen secara otomatis, sehingga dokumen tetap keluar dengan benar — Anda hanya berhenti mendapatkan penghematan lintas-halaman untuk halaman mana pun yang melewati batas tersebut. Sebuah pengaman kedua mengawasi ukuran total alih-alih jumlah simbol: begitu lebar simbol gabungan dictionary yang terakumulasi melewati 131071 piksel, HotPDF menumpahkan batch saat ini ke disk dan memulai grup globals baru secara otomatis, alih-alih membiarkan satu struktur di-memori tumbuh tanpa batas. Tak satu pun batas ini membutuhkan kode apa pun di sisi Anda, karena keduanya adalah fallback otomatis alih-alih exception yang perlu Anda tangkap

Kesesuaian PDF/A adalah satu-satunya pengaturan yang mematikan seluruh mekanisme ini alih-alih sekadar membatasinya. HotPDF diam-diam mengganti CCITT Group 4 untuk JBIG2 begitu PDFACompliance tidak kosong, pada setiap halaman, terlepas dari AccumulateGlobalsAcrossPages atau apa pun lainnya pada JBIG2Options — sebuah pilihan kesesuaian yang disengaja, bukan bug, tetapi ini berarti sebuah profil arsip dan symbol sharing lintas-halaman saling eksklusif untuk saat ini. Konfigurasi apa pun yang Anda pilih, decode apa yang Anda tulis sebelum memercayainya: muat kembali file itu dengan LoadFromFile dan tarik setiap halaman lewat ExtractLoadedImage, yang menyelesaikan globals bersama untuk Anda dengan cara yang sama seperti reader mana pun yang sesuai standar akan lakukan, dan bandingkan hasilnya terhadap bitmap sumber Anda

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;

Cross-page dictionary sharing hanya menyentuh sisi gambar bilevel sebuah dokumen. Jika pipeline yang sama juga menghasilkan halaman teks yang dibuat berdampingan dengan scan — sampul, halaman indeks, sebuah lapisan teks OCR — object stream dan xref stream menyerang separuh lain dari anggaran ukuran file dengan mengompresi struktur dokumen yang ditambahkan halaman-halaman tersebut. JBIG2 globals lintas-halaman disertakan sebagai bagian dari HotPDF Component untuk Delphi dan C++Builder, berdampingan dengan opsi JBIG2 per-gambar dan sisa pipeline kompresinya