Teknik Makale

Delphi ile PDF'e TIFF 12-Bit, Lossless ve Arithmetic JPEG

PDFlibPas, Delphi için PDF kütüphanesi, JPEG sıkıştırması 8 bitlik baseline'ın ötesine geçen TIFF görüntülerini içe alır: 12 bitlik extended sequential (SOF1), Huffman lossless (SOF3), arithmetic kodlu (SOF9 ve SOF10) ve tablo ofsetli eski tip TIFF 6.0 JPEG. PDF DCTDecode bu süreçleri taşıyamadığı için PDFlibPas onları yerel Pascal'da çözümler ve örnekleri bileşen başına 8 ya da 16 bitle kayıpsız olarak gömer

Tam set v3.724.0 ve sonrasında gelir ve doğrudan tarayıcılardan, tıbbi ile bilimsel görüntüleme yazılımlarından ve 1990'larda donmuş arşiv sistemlerinden çıkan TIFF'leri hedefler. Aşağıda, bu varyantların neden olduğu gibi PDF'e kopyalanamadığı, PDFlibPas'ın onları nasıl konumlandırdığı ve yolda bulunan Pascal tuzakları var

12 bitlik JPEG neden doğrudan PDF'e giremiyor?

12 bitlik JPEG geçirilemez, çünkü PDF'te onu çözmekle yükümlü bir filtre yoktur. ISO 32000-1 §7.4.8, DCTDecode'u JPEG baseline biçimi etrafında tanımlar ve progressive uzantıyı PDF 1.3'te ekler. O maddede 12 bitlik sample hassasiyeti, ITU-T T.81 Ek H'nin kayıpsız süreci ya da Ek D'nin arithmetic entropi kodlayıcısı için hiçbir şey yoktur. DCTDecode akışının içinde bir SOF1 12 bitlik frame ya da SOF9 marker'la karşılaşan bir okuyucunun hiçbir şey çizmemesi meşrudur ve çoğu görüntüleyici tam olarak bunu yapar

TIFF içindeki JPEG dosyalarının çoğunu oluşturan 8 bitlik Huffman baseline ve progressive şeritler için PDFlibPas her scan'i doğrulamaya devam eder ve sıkıştırılmış baytları DCTDecode olarak geçirir; PDF küçük kalır. Gerisi kod çözülür. PDFlibPas ilk JPEG bloğunun frame marker'ını ve sample hassasiyetini okur; SOF3, SOF9, SOF10 ya da 8 dışında bir hassasiyet bulduğunda sayfa, geçiş yoluna değil sample çözücüsüne gider. Arithmetic kodlamayı frame marker'dan tanımak göründüğünden daha önemlidir: yalnızca sample hassasiyetine bakan bir kontrol, klasik bir TIFF'in 8 bitlik bir SOF9 şeridini DCTDecode yoluna sızdırmasına ve okuyucuların gösteremediği bir PDF üretmesine izin verir

Delphi için PDFlibPas'ta TIFF JPEG blokları için karar akışı: frame marker, 8 bitlik taramaları DCTDecode'a, öteki süreçleri yerel kod çözmeye yönlendiriyor
TIFF JPEG şeridinin DCTDecode olarak mı geçirileceğine yoksa yerel olarak mı çözüleceğine frame marker karar verir; tek başına sample precision değil

PDFlibPas onun yerine PDF'e ne yazıyor?

PDFlibPas, kodu çözülmüş JPEG bloklarını BitsPerComponent değeri 8 ya da 16 olan sıradan bir image XObject'e çevirir. Örnekler varsayılan olarak sıkıştırılmamış saklanır; görüntüyü eklemeden önce CompressImages(1) çağırın ve PDFlibPas onları FlateDecode ile yazar. ISO 32000-1 §8.9.5.1, PDF 1.5'ten itibaren bileşen başına 16 bite izin verir; PDFlibPas da 16 bitlik örnekli bir görüntü eklendiğinde belge sürümünü en az 1.5'e yükseltir. Bu, çok şeritli ve döşenmiş görüntüleri de kapsar; onların kök nesnesi, kendi içinde incelenecek bir BitsPerComponent taşımayan bir Form XObject'tir

Eşleme, JPEG sürecine göre sabittir:

  • 12 bitlik Huffman JPEG (sequential SOF1, progressive SOF2) dekuantalanır, ters dönüştürülür ve 16 bitlik örneklere genişletilir
  • Arithmetic kodlu JPEG (sequential SOF9, progressive SOF10) 8 bit hassasiyette 8 bitlik örneklere, 12 bit hassasiyette 16 bitlik örneklere dönüşür
  • Huffman lossless JPEG (SOF3), beyan ettiği hassasiyetle tam eşleşen 8 ya da 16 bitlik örneklere dönüşür

12'den 16 bite genişletme, bit çoğaltma ile yapılır: (V shl 4) or (V shr 8); böylece 4095, 65535'e eşlenir ve beyaz, 65520'de tıkanmak yerine beyaz kalır. PDFlibPas bu görüntüleri arkanızdan 8 bite düşürmez. Kütüphane böyle bir sayfayı kendi görüntüleme yüzeyi için rasterleştirdiğinde her örneğin yüksek baytını kullanır, ama gömülü PDF görüntüsü on altı bitin tamamını korur

Delphi için PDFlibPas'ta kodu çözülmüş JPEG süreçlerinin örnek genişliklerine eşlenmesi: 12 bitlik Huffman 16 bite genişler, arithmetic ve lossless beyan edilen hassasiyete göre eşlenir
her JPEG sürecinin PDF'te sabit bir örnek genişliği vardır ve 12 bitlik veri, beyazın beyaz kalması için bit çoğaltmayla genişletilir

Bedeli dosya boyutudur: CompressImages(1) ile bile Flate ile sıkıştırılmış 16 bitlik örnekler, geldikleri DCT verisinden büyüktür; onsuz daha da büyüktür. Tanı ve arşiv iş akışlarında bu takas genelde doğrudur. Belirli bir işte boyut daha önemliyse Delphi'de PDF dosya boyutunu küçültme rehberi, baytların gerçekte nereye gittiğini gösteriyor

Bu TIFF'leri TPDFlib ile içe almak

Çağıran kod hiç değişmez. TPDFlib.AddImageFromFile(Const FileName: WideString; Options: Integer): Integer, 1 tabanlı TIFF sayfa numarasını Options içinde alır ve bir görüntü kimliği döndürür; sayfa içe alınamadığında 0 döner. Desteklenmeyen JPEG varyantları, görüntü belgeye konmadan önce reddedilir; yani 0, yarısı çözülmüş hiçbir şeyin belgeye girmediği anlamına gelir

uses
  PDFlibrary;

function TiffToPdf(const TiffName, PdfName: WideString): Boolean;
var
  Lib: TPDFlib;
  Page, PageCount, ImageID: Integer;
  DPI, W, H: Double;
begin
  Result := False;
  Lib := TPDFlib.Create;
  try
    Lib.SetMeasurementUnits(0);   // varsayılan kullanıcı uzayı, 1/72 inç
    Lib.SetOrigin(1);             // DrawImage için sol üst köken
    Lib.CompressImages(1);        // kodu çözülen örnekler için Flate
    PageCount := Lib.GetImagePageCount(TiffName);
    if PageCount = 0 then
      Exit;
    for Page := 1 to PageCount do
    begin
      ImageID := Lib.AddImageFromFile(TiffName, Page);  // Options = TIFF sayfası
      if ImageID = 0 then
        Exit;                     // desteklenmeyen ya da bozuk görüntü verisi
      Lib.SelectImage(ImageID);
      case Lib.ImageResolutionUnits of
        2: DPI := Lib.ImageHorizontalResolution;          // inç başına nokta
        3: DPI := Lib.ImageHorizontalResolution * 2.54;   // cm başına nokta
      else
        DPI := 0;
      end;
      if DPI <= 0 then
        DPI := 300;
      W := Lib.ImageWidth * 72 / DPI;
      H := Lib.ImageHeight * 72 / DPI;
      if Page > 1 then
        Lib.NewPage;
      Lib.SetPageDimensions(W, H);
      Lib.DrawImage(0, 0, W, H);
    end;
    Result := Lib.SaveToFile(PdfName) = 1;
  finally
    Lib.Free;
  end;
end;

Biçime özel dallanma gerekmez; AddImageFromStream ve AddImageFromString de bellekte tutulan TIFF verisi için aynı kuralları izler

Eski tip JPEG: ofsetlerdeki tablolar, başlıksız şeritler

v3.713.0'dan beri PDFlibPas, tabloları JPEGQTables, JPEGDCTables ve JPEGACTables ofset etiketlerinden ya da yalnızca tablo taşıyan bir JPEGInterchangeFormat akışından alan eski tip JPEG TIFF'leri (Compression = 6) içe alır. TIFF 6.0 Section 22 o düzeni tanımlamıştı; TIFF Technical Note 2 sonra onu Compression = 7 ve paylaşımlı bir JPEGTables etiketiyle değiştirdi, ama tarayıcılar eski biçimi yıllarca yazmayı sürdürdü

Kapsayıcı tarafı, BigTIFF ve döşenmiş düzenler, TIFF çözücüsünün sertleştirilmesi yazısında ele alınıyor; buradaki odak JPEG yükü. Her tablo ofset etiketi, ham 64 baytlık bir kuantizasyon tablosunu ya da 16 adetlik sayım ve ardındaki değerlerinden oluşan bir Huffman tablosunu, bileşen başına bir girdi olarak işaret eder. PDFlibPas bunları, sentezlenen bir JPEGTables akışında DQT ve DHT segmentlerine çevirir. Sonra her çıplak entropi kodlu şerit kendi başlığını alır: SOI, o şeridin satır sayısını taşıyan bir frame marker, JPEGRestartInterval tanımlıysa DRI, SOS, entropi verisi ve EOI. Şeritler tek bir scan'e yapıştırılamaz, çünkü eski tip JPEG her şerit sınırında DC öngörüsünü sıfırlar

İki ayrıntıda hata yapmak kolaydır. YCbCr bir görüntünün üç bileşeni, tablo ofsetlerini sık sık aynı baytlara çevirir; PDFlibPas tablo numaralarını atamadan önce tabloları içeriğe göre tekilleştirir. Bir sınıftan ikiden fazla Huffman tablosu kalırsa sentezlenen frame SOF0 yerine SOF1 kullanır, çünkü bir baseline frame yalnızca iki DC ve iki AC tablosuna atıf yapabilir. JPEGInterchangeFormat bunun yerine scan taşıyan eksiksiz bir JPEG akışına işaret ediyorsa PDFlibPas o akışı bütün görüntü sayar ve tek bir yeni tip şerit olarak içe alır. Lossless, döşenmiş ve ayrı düzlemli eski tip JPEG ise tahmin yürütülmek yerine açık bir hatayla reddedilir; onları akış yukarında yeni tip JPEG'e çevirin

TIFF içindeki CMYK JPEG neden ters çıkar?

TIFF içindeki bir CMYK JPEG, importer tek başına JPEG dosyalarına ait Adobe APP14 kuralını uyguladığında ters çıkar. Photoshop, tek başına JPEG'lere ters CMYK yazar; PDF üreticileri bu yüzden geleneksel olarak telafi için /Decode [1 0 1 0 1 0 1 0] ekler. TIFF PhotometricInterpretation 5, örnekleri 0'ın mürekkep yok anlamına geldiği mürekkep miktarları olarak tanımlar ve ana akım TIFF okuyucuları, APP14'e bakmaksızın çözülen JPEG örneklerini olduğu gibi alır

PDFlibPas, JPEG ile sıkıştırılmış CMYK TIFF şeritlerini ters Decode dizisi olmadan gömer; APP14 marker'ı olsun olmasın. APP14 transform bayrağı yine de YCCK'nin CMYK'ye çevrilip çevrilmeyeceğine karar verir ve DCTDecode o adımı kendisi halleder. Tek başına dört bileşenli JPEG dosyaları ters Adobe kuralını korur; yani aynı JPEG baytları, iki biçimin talebine göre kapsayıcıya göre farklı işlenir

Lossless JPEG kod çözme bir Pascal kaydırmasında neden bozuluyor?

Lossless JPEG kod çözme Pascal'da bozulur, çünkü 5 ve 6 numaralı öngörücüler aritmetik bir sağa kaydırma ister, Pascal'ın shr'si ise mantıksaldır. SOF3 her örneği sol (Ra), üst (Rb) ve sol üst (Rc) komşularından öngörür ve yalnızca farkı kodlar. T.81 Tablo H.1'deki yedi öngörücüden 5 numaralı olan Ra + ((Rb - Rc) >> 1), 6 numaralı olan Rb + ((Ra - Rc) >> 1) formülüdür ve parantez içindeki fark, sol üst komşu daha parlak olduğunda negatife düşer. Negatif bir Integer'ı shr'ye verirseniz işaret biti büyüklük bitine dönüşür ve görüntü gürültüye çözülür

Delphi ve PDFlibPas'ta lossless JPEG öngörücüleri için kaydırma davranışı: mantıksal shr negatif değerleri bozar, div 2 sıfıra doğru kırpar, HalfDown referans gibi tabana yuvarlar
5 ve 6 numaralı öngörücüler taban yuvarlama semantiği ister; küçük bir yardımcı, Delphi ve Free Pascal'da referans çözücüyle eşleşir

Kaydırmayı div 2 ile değiştirmek de çare değil. div sıfıra doğru kırpar, aritmetik kaydırma ise negatif sonsuza yuvarlar; ikisi her tek negatif değerde ayrışır. Free Pascal'ın System unit'inde SarLongint vardır; Delphi'de eşdeğeri yoktur, bu yüzden PDFlibPas ikisinde de aynı derlenen bir yardımcı kullanıyor:

// Bir sağa aritmetik kaydırma: floor(Value / 2)
// shr mantıksal bir kaydırmadır ve div 2 sıfıra doğru kırpar
function HalfDown(Value: Integer): Integer;
begin
  if Value >= 0 then
    Result := Value shr 1
  else
    Result := -((1 - Value) shr 1);
end;

// Value = -3 için:  -3 shr 1 = 2147483646,  -3 div 2 = -1,  HalfDown(-3) = -2

Aynı sorun sınıfının tür genişliği tarafı için bkz. Delphi Linux64'te Longint ve işaretçi tuzakları

Lossless çözücünün geri kalanının kendi sınır kuralları var. İlk satır ve her restart marker'dan sonraki ilk satır yalnızca sol komşudan öngörür ve P hassasiyeti ile Pt point transform için 2^(P−Pt−1) değerinden başlar; sonraki her satır üstteki örnekten başlar. Geri yapılama mod 2^16 ile sarar ve point transform yalnızca çıktıyı ölçekler, öngörü zincirine dokunmaz. Ekstra biti olmayan 32768'lik bir fark demek olan Huffman DC kategorisi 16, yalnızca lossless modda meşrudur; tablos çözücü, frame'in hangi süreci beyan edeceğini bilmeden önce onu kabul etmek zorundadır

PDFlibPas, tam satırları kapsayan yedi öngörücünün, point transformların ve restart aralıklarının tamamını destekliyor. JPEG hassasiyeti BitsPerSample ile aynı ve 8 ya da 16 olmalı; her bileşen 1x1 örnekleme kullanmalı. YCbCr lossless veri ile 12 bitlik lossless JPEG açık bir hatayla reddediliyor

12 bitlik DCT ve arithmetic kodlama yerel Pascal'da

PDFlibPas'ın 12 bitlik ve arithmetic çözücüleri tek bir ters DCT paylaşır ve yalnızca entropi kod çözmede ayrışır. 12 bitlik Huffman JPEG'de DC kategorileri 15'e, AC kategorileri 14'e ulaşır; kuantizasyon tabloları 16 bitlik girdiler taşıyabilir ve DC öngörüsü her scan'de ve her restart'ta sıfırlanır. Katsayılar dekuantalanmaya dek zigzag düzeninde kalır; dekuantalama aynı zigzag indeksindeki kuantizasyon değerini kullanmak zorundadır ve katsayılar ancak ondan sonra, ayrıştırılabilir iki geçişli IDCT için doğal düzene geçer. Çözülen örnekler, bağımsız bir kayan noktalı referans çözücünün ±1 yakınına düşer

Arithmetic kodlama, Huffman'ı T.81 Ek D'nin QM kodlayıcısıyla değiştirir: 114 girişli bir olasılık durum tablosu, 64 bölmeli bir DC istatistik alanı ve 256 bölmeli bir AC alanı; DAC segmentinden gelen koşullandırma parametreleriyle yönetilir (varsayılanlar L = 0, U = 1, Kx = 5). İstatistikler, DC bağlamı ve öngörü her restart aralığında ve her scan'in başında sıfırlanır. PDFlibPas'ın arithmetic görüntüler için paylaşılan JPEGTables'ı normalleştirirken DAC segmentlerini korumasının nedeni budur; onları düşürürseniz her şerit yanlış istatistik modeliyle çözülür

v3.723.0, bu çözülen yollara alt örneklemeli bileşenleri ve JFIF denklemleriyle YCbCr'den RGB'ye dönüşümü ekledi; v3.724.0, 12 bitlik Huffman (SOF2) ve arithmetic (SOF10) verisinin ikisi için progressive taramaları ekledi. Alt örneklemeli chroma, yumuşatılmak yerine tam çözünürlüğe çoğaltılır; bu, referans bir çözücünün chroma yumuşatması kapalıyken ürettiği sonuçtur. Hierarchical JPEG ile arithmetic kodlu lossless JPEG (SOF11) açık bir hatayla reddediliyor

Delphi'de ikili JPEG test verilerini sınamak

Delphi unit testlerinde elle kurulan JPEG verileri, tam da bir çözücü hatasına benzeyen biçimde başarısız olabilir. #$xx karakterlerinden kurulan ve 255 karakteri aşan bir string sabiti UnicodeString olarak tip alır; onu bir AnsiString'e atamak, etkin kod sayfası üzerinden dönüştürür. Entropi kodlu veride rutin olan $80-$9F baytları, yol üstünde yeniden yazılır. Marker başlıklarını çalışma zamanında kurmanın da aynı tuzağı var: Delphi'de AnsiChar + AnsiChar bir UnicodeString üretir; bu yüzden tek baytları bir AnsiString'e ekleyin ya da SetLength ve indeksli atamayla yazın. Test verileri için hex metni saklayın ve çalışma zamanında çözün:

uses
  SysUtils, PDFlibrary;

function HexToBytes(const Hex: AnsiString): AnsiString;
var
  I: Integer;
begin
  SetLength(Result, Length(Hex) div 2);
  for I := 1 to Length(Result) do
    Result[I] := AnsiChar(StrToInt('$' + string(Copy(Hex, I * 2 - 1, 2))));
end;

// Fixture sade ASCII kalır; hiçbir kod sayfası dokunamaz
ImageID := Lib.AddImageFromString(HexToBytes(TiffHex), 1);

Test görüntüleri üretirken şunu not edin: jpegtran -arithmetic sequential SOF9 yazar; SOF10 için ek olarak -progressive gerekir. Ve sıfır olmayan bir görüntü kimliği yalnızca içe almanın kabul edildiğini kanıtlar. Sayfayı render edip pikselleri karşılaştırın, çünkü bir geçiş hatası, hiçbir görüntüleyicinin çizemeyeceği iyi biçimli bir PDF üretir

Eski tarama arşivlerini PDF'e taşımak

Bu JPEG varyantları, arşiv göçleri sırasında hep birden ortaya çıkar. PDFlibPas, 12 bitlik, lossless, arithmetic ve eski tip JPEG TIFF'leri her şey gibi aynı AddImageFromFile çağrısıyla okur, PDF'te tam hassasiyeti korur ve o setin dışındaki nadir süreçleri açık bir hatayla reddeder. Lisans, tam özellik listesi ve deneme indirmeleri için bkz. Delphi için PDFlibPas PDF Library ürün sayfası