Teknik Makale

Delphi'de Sayfalar Arası JBIG2 Sembol Sözlüklerini Paylaşma

Elli sayfalık taranmış bir sözleşme her sayfada aynı alfabeyi tekrarlar, ancak görüntü başına bir sembol sözlüğü inşa eden bir JBIG2 kodlayıcı o alfabeyi elli ayrı kez yeniden eğitir. Yerel Delphi ve C++Builder PDF bileşeni olan HotPDF, bunun yerine tüm belge boyunca paylaşılan tek bir sembol sözlüğü biriktirebilir ve onu tek bir belge düzeyi /JBIG2Globals akışına yükseltebilir; böylece her sayfanın kendi JBIG2 akışı, alfabenin kendi kopyasını saklamak yerine yalnızca sembol kimliklerine referans verir

Bu yazı kasıtlı olarak dar tutulmuştur ve yalnızca HotPDF'in bu sayfalar arası paylaşımı dahili olarak nasıl inşa ettiğini kapsar — JBIG2 temelleri, CCITT karşılaştırması ve Lossless ile LossyLevel takasları zaten Delphi'de yerel JBIG2 iki seviyeli sıkıştırma üzerine tamamlayıcı makalede yer alır; bu yazı onu okuduğunuzu varsayar

Sayfa başına JBIG2 sıkıştırma neden hâlâ aynı maliyeti tekrarlıyor?

Cevap, çağrılar arasında hiçbir şeyin durum taşımamasıdır. HotPDF'in kodlayıcısı bir görüntü için bir sembol sözlüğü inşa ettiği her seferinde, o sözlük o tek AddImage çağrısına kapsamlıdır: şekil eşleştirme geçişi sıfırdan başlar, sayfadaki her glif yeni olarak sınıflandırılır ve ortaya çıkan bit eşlemleri aritmetik olarak kodlanır ve yeniden saklanır. Aynı kodlayıcıya aynı yazı tipinde ayarlanmış elli sayfa besleyin ve bu tüm eğitim geçişini elli kez memnuniyetle tekrarlar, çünkü onun bakış açısından her sayfa tesadüfen benzer görünen ilgisiz bir görüntüdür. Sayfa başına UseSymbolDictionary, zaten tek bir sayfada düz genel bölge kodlamasını geniş bir farkla yener, ancak gerçek bir çok sayfalı taramanın masada bıraktığı tavana çok önce ulaşır

HotPDF tek bir sembol sözlüğünü sayfalar arasında nasıl paylaşır?

THPDFJBIG2Options'ta AccumulateGlobalsAcrossPages'i etkinleştirin ve HotPDF, her görüntüden sonra atmak yerine belgenin ömrü boyunca bellekte tek bir sembol sözlüğünü canlı tutar. Sonraki her sayfanın glifleri, herhangi bir şey yeniden kodlanmadan önce bu çalışan sözlüğe karşı kontrol edilir: zaten var olan bir şekil sembol kimliğiyle yeniden kullanılır ve yalnızca kimsenin daha önce görmediği bir şekil eklenir ve sözlüğe kodlanır. Karşılaştırma, LossyLevel'in tek bir sayfada uyguladığı aynı tolerans mantığını yeniden kullanır — aynı harfin biraz gürültülü bir taraması yine de bir eşleşme sayılır — bu yüzden biriktirici, aynı glifin piksel düzeyindeki her varyasyonu için bir sözlük girdisine sessizce şişmez. Çıkarma önce gerçekleşir ve o karşılaştırmayı besler: HotPDF her sayfanın bit eşlemesini gezer ve siyah piksellere karşı taşma doldurma yoluyla bağlı şekilleri çıkarır — mürekkep lekelerini elle izlemekle aynı fikir — ve çalışan sözlüğe karşı karşılaştırılan, ham piksel blokları değil, bu çıkarılan şekillerdir

Paylaşılan sözlük bir /JBIG2Globals akışının içinde nasıl oturur

Biriktirilen sözlük, her sayfanın aynı hedefe işaret edebilmesi için sabit bir segment numarasında tutulan, /JBIG2Globals akışı içinde tek bir sembol sözlüğü segmenti olarak yazılır. ISO 32000-1 §7.4.7'nin tanımladığı gömülü JBIG2 organizasyonu içinde, bir metin bölgesi segmenti, segment başlığındaki başvurulan segment alanı aracılığıyla sembol kaynağı olarak başka bir segmenti adlandırabilir ve HotPDF'in dayandığı tam mekanizma budur: globals akışı büyük sembol sözlüğünü taşır ve her sayfanın kendi JBIG2 akışı, bir sayfa bilgisi segmenti artı başvurulan listesi globals segmentine geri işaret eden bir metin bölgesi segmentine küçülür. Sayfa başına kendi kendine yeterli bir bit akışı olan şey, konumlar ve sembol kimliklerinin kısa bir listesine dönüşür ve bu şekilde inşa edilen her sayfa, onun bir kopyasına değil, aynı dolaylı /JBIG2Globals nesnesine referans verir. HotPDF'in kendi regresyon kapsamı tam olarak bunu kontrol eder: her sayfanın farklı bir glif düzenine sahip olduğu kısa bir belgeyi kodlayın, yeniden yükleyin ve dosyada kaç farklı /JBIG2Globals nesne referansının göründüğünü sayın — kaç sayfanın sembol katkısında bulunduğundan bağımsız olarak bir belge, bir nesne referansı

Sayfalar arası sembol sözlüğü birikimini açmak

Anahtar, tamamlayıcı makalede ele alınan aynı seçenekler kaydında oturur ve birikimin gerçekten devreye girmesi için dört ayarın birbiriyle uyuşması gerekir

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;

Bu eşleştirme isteğe bağlı bir süsleme değildir. İki seviyeli sıkıştırma makalesinde anlatılan harici kodlayıcı arayüzü — üretim dereceli oranlar için RegisterJBIG2EncoderBackend aracılığıyla kaydettiğiniz — görüntü başına kodlama etrafında inşa edilmiştir ve HotPDF'in kendi birikim demoları ve regresyon testleri her zaman AccumulateGlobalsAcrossPages'i UseExternalEncoder := False ile eşleştirir. Bunu bir öneri yerine sert bir gereklilik olarak ele alın: sayfalar arası paylaşım yerel-kodlayıcı bir özelliktir ve kayıtlı bir harici arka uç, paylaşılan sözlüğü inşa eden yolun basitçe bir parçası değildir

Çok sayfalı bir tarama gerçekte ne kadar küçülür?

Dürüst cevap, ilk önce ibreyi hareket ettirmeyen şeyle başlar. Önceki bir sürüm, /JBIG2Globals akışları için içerik adresli bir önbellek ekledi — akış byte'larının 64 bit FNV-1a hash'i ile anahtarlanan bir arama; böylece tesadüfen byte-özdeş globals verisi üreten iki görüntü tek bir PDF nesnesini paylaşabilirdi. Gerçek çıktıya karşı ölçüldüğünde, o önbellek neredeyse hiç yardımcı olmadı, çünkü HotPDF'in mevcut tam görüntü yinelenen tespiti, önbellek çalışma şansı bulmadan önce zaten byte-özdeş görüntüleri çöktürüyordu. Ders şu oldu: akış düzeyi tekilleştirme yalnızca iki gerçekten farklı sayfa görüntüsü hâlâ büyüyen tek bir sözlüğü paylaşabildiğinde karşılığını verir; gerçek sayfalar arası birikimin sunduğu şey tam olarak budur

Bu daha zor durum için, HotPDF'in kendi mühendislik tahmini, tekrarlayan tek bir yazı tipinden inşa edilmiş tipik çok sayfalı bir tarama için ek tasarrufu, yalnızca akış düzeyi tekilleştirmenin başardığından yaklaşık %30 ila %60 daha küçük olarak koyar — aralık, belgenin görsel dağarcığının ne kadarının gerçekte tekrarlandığına göre hareket eder, çünkü benzersiz diyagramlarla dolu bir sayfa sözlüğe yeniden kullanacak hiçbir şey vermez. Bunu belirli bir girdi için bir garanti yerine bir tasarım hedefi olarak ele alın ve tek bir sayıya güvenmek yerine kendi belgelerinizi ölçün. HotPDF ile birlikte gelen JBIG2Benchmark demosu tam olarak bu amaç için var: aynı çok sayfalı taramayı dört farklı şekilde kodlar ve her yapılandırma için ortaya çıkan dosya boyutunu yazdırır; böylece karşılaştırma sentetik bir taramaya değil, kendi tarama karışımınıza karşı çalışır

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.

Sayfalar arası birikimin sınırlarına ulaştığı yer

Biriktirilen sözlük, görüntü başına yerel kodlayıcının zaten tek bir sayfada uyguladığı aynı tavan olan 4096 sembolle sınırlıdır. Belge ortasında bu sınırı aşın ve HotPDF bir istisna fırlatmaz veya çalışmayı iptal etmez: biriktirici yeni glifi reddeder ve onu tanıtan sayfa otomatik olarak bağımsız görüntü başına kodlamaya geri döner; bu yüzden belge yine de doğru çıkar — yalnızca tavanı aşan hangi sayfalarsa onlar için sayfalar arası tasarrufu almayı bırakırsınız. İkinci bir koruma, sembol sayısı yerine toplam boyutu izler: biriktirilen sözlüğün birleşik sembol genişliği 131071 pikseli aştığında, HotPDF geçerli toplu işi otomatik olarak diske döker ve tek bir bellek içi yapının sınırsızca büyümesine izin vermek yerine yeni bir globals grubu başlatır. Her iki sınır da tarafınızdan hiçbir kod gerektirmez, çünkü ikisi de yakalamanız gereken istisnalar yerine otomatik yedeklerdir

PDF/A uyumluluğu, tüm mekanizmayı yalnızca sınırlamak yerine tamamen kapatan tek ayardır. HotPDF, PDFACompliance boş olmadığı anda, her sayfada, AccumulateGlobalsAcrossPages'den veya JBIG2Options'taki başka herhangi bir şeyden bağımsız olarak JBIG2 yerine sessizce CCITT Group 4'ü ikame eder — bilinçli bir uyumluluk seçimi, bir hata değil, ancak bu, bir arşiv profili ile sayfalar arası sembol paylaşımının bugün karşılıklı olarak dışlayıcı olduğu anlamına gelir. Hangi yapılandırmaya inerseniz inin, ona güvenmeden önce yazdığınızı kod çözün: dosyayı LoadFromFile ile geri yükleyin ve her sayfayı, herhangi bir uyumlu okuyucunun yapacağı şekilde paylaşılan globals'ı sizin için çözen ExtractLoadedImage üzerinden çekin ve sonucu kaynak bit eşlemlerinizle karşılaştırın

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;

Sayfalar arası sözlük paylaşımı yalnızca bir belgenin iki seviyeli görüntü tarafına dokunur. Aynı boru hattı taramaların yanında oluşturulmuş metin sayfaları da yayınlıyorsa — kapak sayfaları, dizin sayfaları, bir OCR metin katmanı — nesne akışları ve xref akışları, o sayfaların eklediği belge yapısını sıkıştırarak dosya boyutu bütçesinin diğer yarısına saldırır. Sayfalar arası JBIG2 globals, görüntü başına JBIG2 seçenekleri ve sıkıştırma boru hattının geri kalanının yanı sıra, Delphi ve C++Builder için HotPDF Bileşeni'nin bir parçası olarak gönderilir