Teknik Makale

HotPDF ile Delphi'de PDF Predictor ve LZWDecode Çözümleme

/Predictor 12 ile işaretlenmiş bir akış, her satırın PNG filtre 2 kullandığı anlamına gelmez. Delphi ve C++Builder için yerel VCL PDF bileşeni olan HotPDF, 10 ile 15 arasındaki predictor değerlerini tek bir aile olarak ele alır: gerçek filtre etiketi, 0 ile 4 arasında, kodlanan her satırın ilk baytıdır ve HPDFDecodePredictor bu etiketi satır satır okuyup doğrular. Bu ayrım, PDF'in bu köşesindeki neredeyse her hatanın şeklidir, çünkü yanlış yaptığınızda hiçbir şey exception fırlatmaz. Filtre zinciri çalışır, raster beklediğiniz boyuttadır ve görüntü, çapraz statik parazit ya da her tarama satırıyla biraz daha kayan bir degrade olarak çıkar. /DecodeParms'daki beş sayı (ISO 32000-1 §7.4.4), çoğunlukla baytların uzunluğunu değil anlamını değiştirir, bu yüzden yanlış bir tanesi bir hata yerine makul görünen çöp üretir

/Predictor 12 neden her satırda PNG filtre 2 anlamına gelmez?

Çünkü predictor sayısı yalnızca "PNG prediction kullanımda" der, hangi filtre olduğunu değil. PNG kodlayıcıları tarama satırı başına bir filtre seçer ve PDF filtresi bunu miras alır, bu yüzden predictor değerleri 10 (None), 11 (Sub), 12 (Up), 13 (Average), 14 (Paeth) ve 15 (Optimum) hepsi aynı şekilde çözülür: çözücünün uyması gereken şey her satırın baştaki etiket baytıdır. Yerleşim sonucu, anlambilim kadar önemlidir. Kodlanan her satır 1 + RowBytes bayt uzunluğundadır, girdi bu yüzden çıktıyı tam olarak satır sayısı kadar aşar ve uzunluğu RowBytes + 1'in tam katı olmayan bir akış tanım gereği kesilmiştir. HotPDF bir bayta dokunmadan önce bu sınırı kontrol eder, 4'ün üzerindeki herhangi bir etiketi Invalid PNG predictor row tag ile reddeder ve önceki satırı iki boyutlu bir satır dizisi oluşturmak yerine doğrudan tek çıktı tamponundan okur. Filtre 1 ve 3, geçerli satır içinde BytesPerPixel kadar geriye uzanır, filtre 2 doğrudan yukarı okur, filtre 4 Paeth seçimini sol, yukarı ve yukarı-sol üzerinden çalıştırır — ve dördü de zaten yeniden yapılandırılmış çıktı üzerinde çalışır; bu da yukarı-satırın neden her zaman çözülmüş satır olması gerektiğini ve asla filtrelenmiş girdi olmaması gerektiğini açıklar

uses
  HPDFPredictor;

var
  Filtered, Raster: AnsiString;
  ErrorText: string;
begin
  // /DecodeParms << /Predictor 12 /Colors 3 /BitsPerComponent 8 /Columns 1024 >>
  if HPDFTryDecodePredictor(Filtered, 12, 3, 8, 1024,
       Int64(1024) * 3 * 8192, Raster, ErrorText) then
    ConsumeRaster(Raster)
  else
    LogStreamDefect('predictor', ErrorText);   // no exception, no partial raster
end;

MaxOutputBytes argümanı süs değildir. Bir predictor aşaması, kılık değiştirmiş bir sıkıştırma açma aşamasıdır ve düşman ya da yalnızca bozuk bir /Columns değeri, birkaç kilobayt girdiyi çok gigabaytlık bir tahsis isteğine dönüştürür. HotPDF, satır başına bit, satır başına bayt ve toplam raster boyutunu önce Int64 içinde hesaplar, taşan geometriyi reddeder ve çağıranın sağladığı üst sınıra uyar. Görüntü sözlüğünden türetilen gerçek bir sınır geçirin, hata modu bir müşteri makinesinde bellek-yetersiz iletişim kutusu yerine günlüğe kaydedilmiş bir mesaj haline gelir

TIFF Predictor 2 neden 4-bit görüntüleri bozar?

Çünkü Predictor 2, bayt başına değil örnek başına yatay farklamadır ve bileşen başına 1, 2 veya 4 bitte, birkaç örnek bir baytı paylaşır. Yaygın uygulama, N-Colors baytını N baytına ekler; bu, bileşen başına 8 bitte doğru olur ve her yerde başka sessizce yanlıştır. 8-bit RGB bir tarama mükemmel çözülür, sonra aynı kod, üretimde ilk kez görüldüğünde 4-bit indeksli bir görüntüyü yok eder

Doğru aritmetik bit alanı içinde çalışır. HotPDF, örnekleri Colors indeksinden Colors * Columns - 1'e kadar gezer, örneği ve aynı-bileşen sol komşusunu uygun kaydırmada (1 shl BitsPerComponent) - 1 maskesiyle çıkarır, bunları o maske modülünde toplar ve sonucu aynı bayta paketlenmiş diğer örnekleri bozmadan geri yazar. Kuyruk da önemlidir: bir satır, bayt sınırına dolgulanır, bu yüzden son örnekten sonraki dolgu bitleri aritmetiğe katılmadan sağlam kalmalıdır. Bileşen başına 16 bitte her örnek büyük-endian bir bayt çiftidir ve toplama, baytlar arasında bağımsız olarak taşınmak yerine çift boyunca $FFFF'de sarar; 8 bitte basit bayt yinelemesi doğrudur ve Colors kadar adım atar, böylece kırmızı kırmızıya karşı, alfa alfaya karşı birikir. Her varyantta bir satırın ilk pikseli her zaman bir literal'dir, asla bir fark değildir ve yineleme her satır sınırında yeniden başlar — TIFF prediction, PNG ailesi ile arasındaki tüm farkı oluşturan şekilde, yukarıdaki satırı asla okumaz

LZWDecode'da EarlyChange gerçekte neyi kontrol eder?

Okuyucunun kod boyutunu bir bit genişletme zamanını kontrol eder ve bir kod dışında olmak, ardından gelen her şeyi bozar. HotPDF, kuralı tek bir değişmez olarak ifade eder: bir sözlük girdisi eklendikten sonra, bir sonraki okuma, NextCode, (1 shl CodeSize) - Ord(EarlyChange)'e ulaştığında genişler. ISO 32000-1 §7.4.4 varsayılanı olan /EarlyChange 1 ile, geçiş bir kod erken gerçekleşir; /EarlyChange 0 ile tam olarak sınırda gerçekleşir. İkisi de gerçek dosyalarda görülür ve bit akışındaki hiçbir şey size kodlayıcının hangisini kullandığını söylemez. Durum makinesinin geri kalanının kilitli adımda hareket etmesi gerekir: bir temizleme kodu, kod boyutunu, bit maskesini, bir sonraki serbest kodu ve ifade depolamasını birlikte sıfırlar ve dosya-sonu kodu, başlangıçtaki 9 bitte değil, o anda geçerli olan genişlikte okunur. HotPDF, InitialCodeSize 9'da başlar, kod boyutunu 12'de ve sözlüğü 4096 girdide sınırlar ve PDF kodları yüksek-sıra biti önce paketlediği için FillOrderfoTop'a varsayılan yapar — foBottom, bunu yapmayan TIFF-tarzı akışlar için mevcuttur

uses
  HPDFLZW;

var
  Decoder: TPDFLZWDecompressor;
  Parms: TPDFLZWParms;
  Plain: AnsiString;
begin
  Decoder := TPDFLZWDecompressor.Create;
  try
    Decoder.EarlyChange := True;      // /EarlyChange 1 is the PDF default
    Decoder.FillOrder := foTop;       // high-order bit first
    Decoder.MaxOutputBytes := 256 * 1024 * 1024;
    Decoder.RequireInitialClear := False;
    Decoder.RequireEndOfInformation := False;

    Parms.Predictor := 12;
    Parms.Colors := 3;
    Parms.BitsPerComponent := 8;
    Parms.Columns := 1024;
    Parms.ExpandedTo8Bit := False;
    Parms.ColorSpace := 'DeviceRGB';

    if Decoder.TryDecompress(RawStreamBytes, Parms, Plain) then
      LogDecodeStats(Decoder.PeakCodeSize, Decoder.DictionaryAdds,
        Decoder.KwKwKExpansions, Decoder.OutputBytes)
    else
      LogStreamDefect('lzw', Decoder.LastError);
  finally
    Decoder.Free;
  end;
end;

İstatistikler triyaj için vardır, gösteriş için değil. Bir dosya doğru uzunluğa ama yanlış piksellere çözüldüğünde, PeakCodeSize ve DictionaryAdds size okuyucunun yazıcının genişlediği yerde hiç genişleyip genişlemediğini hemen söyler. EarlyChange'i tersine çevirin, yeniden çözün, ikisini karşılaştırın: sayılar hareket ederse, bir bit okuyucuda adım adım ilerlemek yerine tek bir çalıştırmada cevabınız vardır

KwKwK dalı ve bir akışın basitçe başarısız olması gerektiği zaman

Yasadışı görünen tek yasal durum Code = NextCode'dur ve HotPDF bunu, girdiyi yayınlamadan önce oluşturarak ele alır. Bir kodlayıcı, girdi K w K w K biçiminde bir desen içerdiğinde, aynı adımda tanımladığı bir ifade için kodu yayınlayabilir; çözücü o kodu arayamaz çünkü henüz mevcut değildir, bu yüzden Previous + First(Previous)'ı oluşturmak, onu yeni girdi olarak eklemek ve az önce oluşturduğu girdiyi yayınlamak zorundadır. HotPDF bunları KwKwKExpansions'da sayar ve eklediği kodun istenen kod olduğunu çapraz kontrol eder. NextCode'un üzerindeki her şey bozulmadır ve orada bir çözücü doğaçlama yapmak yerine durmalıdır: HotPDF, gelecekteki bir kodda, ifade arenası dışını işaret eden bir sözlük önekinde, dolu bir sözlükte ve literal olmayan bir ilk kodda hata fırlatır. İki sıkılık anahtarı kasıtlı olarak varsayılan kapalıdır, RequireInitialClear ve RequireEndOfInformation, çünkü birçok üretim PDF'i baştaki temizleme kodunu atlar ya da sonlandırıcı olmadan veriyi tüketir. Kendi çıktınızı doğrularken bunları açın, vahşi doğadan gelen dosyaları tüketirken kapalı bırakın

/DecodeParms yüklenmiş-doküman tarafında gerçekte nerede okunur

HotPDF, /DecodeParms'ı ya da kısaltması /DP'yi görüntü akışı sözlüğü üzerinde çözer, bir sözlük ya da bir diziyi kabul eder ve bir dizi olduğunda son elemanı alır, ardından Predictor, Colors, BitsPerComponent, Columns ve EarlyChange'i raster yoluna taşır. Dizi durumu insanların unuttuğu şeydir: [/ASCII85Decode /FlateDecode] tarafından filtrelenmiş bir akış, paralel bir parametre dizisi taşır ve predictor ayarları ilk filtreye değil, son filtreye aittir

var
  Pdf: THotPDF;
  Info: THPDFLoadedImageInfo;
  Bmp: TBitmap;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('scanned.pdf', '') > 0 then
      for I := 0 to Pdf.GetLoadedImageCount - 1 do
        if Pdf.GetLoadedImageInfo(I, Info) then
        begin
          Bmp := Pdf.ExtractLoadedImage(I);   // nil when the raster is unusable
          if Bmp <> nil then
          try
            Bmp.SaveToFile(Format('image-%d.bmp', [I]));
          finally
            Bmp.Free;
          end;
        end;
  finally
    Pdf.Free;
  end;
end;

Bu yoldaki bir tarihsel kusur, adlandırılmaya değer, çünkü hata sınıfı tekrarlanır. Eski parametreli Flate rutini bir sıkıştırma açma akışı oluşturuyor ve sonra orijinal sıkıştırılmış girdiden kopyalıyordu, bu yüzden predictor aşaması sıkıştırılmış baytları alıyor ve onları sadakatle "un-predict" ediyordu: her zaman yanlış, asla hata fırlatmadan. Mevcut kod, sonucu ortak predictor'a teslim etmeden önce yalnızca çözücüden okur ve hesaplanan boyuttan kısa bir rasteri, hâlâ sıkıştırılmış baytlara geri dönmek yerine reddeder — bu geri dönüş, eskiden bir çözme başarısızlığını bozuk bir bitmap'e dönüştürüyordu. Aynı predictor uygulaması artık çapraz referans akışlarına da hizmet ediyor; bu, aynı zamanda nesne akışları ve artımlı güncellemelerle de çalışıyorsanız faydalı bir tutarlılıktır ve çevredeki çıkarma mekanizması, yüklenen görüntüleri ve çözme filtrelerini çıkarma üzerine yoldaş yazıda ele alınmıştır. DCTDecode ya da JPXDecode olarak gelen görüntüler predictor'a hiç ulaşmaz; kendi sıkıştırılmış piksel modellerini taşırlar

Verim: bitişik bir ifade arenası ile girdi başına dize karşılaştırması

Girdi başına dize sözlüğünü bitişik bir ifade arenasıyla değiştirmek, patolojik bir girdide yaklaşık 1,61 kat daha hızlı ölçüldü: en uzun tek ifadesi 7.370.880 bayta ulaşan bir benchmark'ta 969 MiB/s'ye karşı 1558 MiB/s. Bu girdinin şekli, farkı açıklar, çünkü klasik uygulamalar iki kötü takastan birini seçer. Bir AnsiString değerleri sözlüğü, 4096'ya kadar girdinin her biri için taze bir dize tahsis edip kopyalar, her yeni girdi ebeveynini tamamen kopyalar; bir önek/sonek yığını bu belleği tamamen kaçınır ama her ifadeyi zinciri geriye doğru bayt bayt gezip ters çevirerek yeniden inşa eder, bu sıradan metin için iyidir ve bir ifade megabaytlara ulaştığında acı vericidir. HotPDF, her ifadeyi geometrik olarak büyüyen bir arenaya bitişik olarak ekler, girdileri ofset ve uzunluğa göre indeksler ve bir ifadeyi çıktı tamponuna tek bir Move ile yayınlar. Dürüst maliyet bellektir: her ifadeyi tam olarak tutan bir arena, girdi sayısı yerine tüm ifade uzunluklarının toplamıyla sınırlıdır; bu tam olarak MaxOutputBytes'ın hem sıkıştırma açıcıda hem de predictor'da neden var olduğudur. Bu sınırı, görüntü sözlüğünün rasterin ne olması gerektiğini iddia ettiği şeyden türetin, yalan söyleyen bir akış hızlı başarısız olsun

Burada gösterilen LZW sıkıştırma açıcı, ortak predictor ve yüklenen-görüntü çıkarma yolu, Delphi ve C++Builder için standart HotPDF Component'in bir parçası olarak, ürün sayfasındaki tam filtre ve DecodeParms referansıyla birlikte sunulur