Teknik Makale

Delphi'de Indexed PDF Paletleri: Taban Uzayları, Döngüler

Bir /Indexed arama tablosu, RGB üçlülerini değil taban renk uzayının bileşenlerini saklar. DeviceGray tabanı giriş başına bir bayt paketler, DeviceCMYK dört, ölçekleme gerektiren Lab üç ve ICCBased, profilin /N bildirdiği her şeyi. HotPDF hepsini ayrıştırma anında, THPDFColorSpace'nin açığa çıkardığı genel RGB paletine normalleştirir

O cümle, kusur sınıfının ta kendisidir. Hemen hemen her palet render edici, gerçek dünyadaki /Indexed uzayların ezici çoğunluğu /DeviceRGB üzerinde oturduğu ve varsayımın yıllarca tuttuğu için, üç baytlık bir stride varsayarak hayata başlar. Sonra taranmış bir fatura [/Indexed /DeviceCMYK 15 <...>] ile gelir, okuyucu arama dizesini üçer bayt yürür ve sıfırın ötesindeki her dizin başka birinin renginin ortasına düşer. Çıktı boş değildir ve çökmez. Makul görünen çöptür, ki bu çok daha kötüdür, çünkü kimse yalnızca hafifçe yanlış görünen renkler için hata açmaz

Bir Indexed palet neden yanlış çıkar?

Stride yanlıştır, çünkü stride bir sabit değildir. ISO 32000-1 §8.6.6.3, arama tablosunu m × (hival + 1) bayt olarak tanımlar; burada m, taban uzayındaki bileşen sayısıdır ve tabanın RGB olduğuna dair hiçbir şey söylemez. Aynı bölüm yalnızca iki tabanı doğrudan yasaklar: /Indexed'in kendisi ve /Pattern. Geri kalan her şey serbesttir; buna §8.6.5.2'den §8.6.5.5'e kadar dört CIE tabanlı ailenin tamamı dahildir

HotPDF'te bir Indexed PDF arama tablosu diyagramı; giriş stridi taban renk uzayından gelir: yanlış 3 baytlık RGB yürüyüşüyle kesilen 8 baytlık bir DeviceCMYK araması çöp girdiler üretir, doğru 4 baytlık gruplama RGB üçlülerine normalleştirilen iki CMYK girdisi verir ve bir tablo DeviceGray, DeviceRGB, DeviceCMYK, Lab ve ICCBased için giriş başına bileşenleri listeler
HotPDF önce taban renk uzayını çözer ve bileşen sayısını ona sorar; böylece bir CMYK araması RGB'ye normalleştirilmeden önce giriş başına dört bayt yürür

Yani gerçek iş bayt okumak değil, önce tabanı çözmek ve ona bir girdinin ne kadar geniş olduğunu sormaktır. HotPDF'teki ResolveIndexedPalette tam olarak bunu yapar: tabanı bir THPDFColorSpace'ye çözer, ComponentCount'u çözülmüş kayıttan alır ve taban csfIndexed, csfPattern veya csfUnsupported olarak dönerse diziyi doğrudan reddeder. Ancak ondan sonra bayt bütçesini PaletteCount * Count olarak boyutlandırır; hival 255'in yasal maksimumu ve dört bileşenli bir taban için bu, en fazla 1024 bayta çıkar. O boyutlandırmadaki iki ayrıntı, düşmanca bir dosyanın sizi yoklayacağı yerlerdir. HotPDF, §8.6.6.3 sınırına göre hival'i kullanmadan önce 0..255'e kenetler; böylece hival 100000 iddia eden bir arama dizisi gigabaytlık bir ayırma dönüşmez ve bütçeyi arama verisinin gerçekte içerdiği şeye kırpar, sonra paleti baytları sonun ötesine taşan ilk girdide doldurmayı bırakır. Kısa bir tablo, arabelleğin ötesine bir okuma değil kısa bir palet üretir

Dört farklı taban uzayını tek bir RGB tablosuna normalleştirmek

HotPDF paletleri çizim anında değil ayrıştırma sırasında RGB'ye dönüştürür ve bu karar tasarımın geri kalanını yönlendirir. Genel THPDFColorSpace.Palette alanı, uzunluğu 3 * PaletteCount olan bir RGB üçlüleri TBytes'idir ve CIE tabanlı tabanlar desteklenmeden çok önce bu şekildedir. Kayıt düzenini yerel taban bileşenlerini taşıyacak şekilde yeniden yazmak, alanın her tüketicisini kırardı. Ayrıştırma anında dönüştürmek düzeni sağlam tutar ve bonus olarak Gray, CMYK, Lab, CalGray, CalRGB ve ICCBased girdilerini, render edicinin doğrudan renk operatörleri için zaten kullandığı HPDFResolveColor yolundan geçirir

Ölçekleme adımı, taban uzaylarının birbirinin yerine geçemez olduğu yerdir. Bir arama baytı her zaman 0..255'tir, ama o baytın anlamı, tabanın bildirdiği bileşen aralığına bağlıdır. Aygıt ve kalibre edilmiş bileşenler 0..1'e eşlenir. Lab öyle değildir: L* bileşeni 0..100 arasında çalışırken a* ve b*, uzayın bildirdiği /Range üzerinde çalışır. HotPDF bu sınırları, Lab ayrıştırıcısının sözlük /Range girdisinden doldurduğu ve girdi yokken −108..101'e varsayılan yaptığı THPDFCIEParams'ın AMin, AMax, BMin ve BMax alanlarından çeker; bu yüzden ezberden simetrik bir aralık sabit kodlamayın. ICCBased tabanlar üçüncü bir doğruluk kaynağı ekler: bileşen sayısı, ICCN olarak yüzeye çıkan profil akışı /N'den gelir ve bir ICCBased akışı, HotPDF'in HPDFReadNumericArrayUpTo üzerinden aynı bileşen başına sınır tablosuna okuduğu bir /Range dizisi taşıyabilir. Üç bileşenli bir ICC paleti ve üç bileşenli bir Lab paleti bu yüzden arama dizesini özdeş stridlarla ama tamamen farklı aritmetikle yürür; sabit bir RGB stridinin üretemeyeceği sonuç tam olarak budur

var
  CS: THPDFColorSpace;
  Entry: Integer;
  R, G, B: Byte;
begin
  // ColorSpaceObj, bir görüntü XObject'inin veya bir kaynak
  // sözlüğünün /ColorSpace girdisidir; bir ad, dizi veya dolaylı başvuru olabilir
  CS := HPDFResolveColorSpace(ColorSpaceObj);
  if CS.Family <> csfIndexed then
    Exit;

  // Palet, taban uzayı ne olursa olsun zaten RGB'dir
  for Entry := 0 to CS.PaletteCount - 1 do
  begin
    if (Entry * 3 + 2) >= Length(CS.Palette) then
      Break;                       // kısa arama tablosu, temiz dur
    R := CS.Palette[Entry * 3];
    G := CS.Palette[Entry * 3 + 1];
    B := CS.Palette[Entry * 3 + 2];
    UseColor(Entry, R, G, B);
  end;
end;

Arama tablosu bir akışta yaşadığında

ISO 32000-1 §8.6.6.3, arama tablosunun bir dize veya bir akış olmasına izin verir ve ısıran biçim akış biçimidir. Bir akış /FlateDecode taşıyabilir, dosyanın alakasız kısımlarıyla paylaşılan dolaylı bir nesne olabilir ve ayrıştırıcınız ona ulaştığı anda TStream'i herhangi bir konumda olabilir. HotPDF sıkıştırılmış durumu, Type 0 örneklenmiş fonksiyon motorunun kullandığı aynı geri çağrı olan HPDFSetColorStreamReader üzerinden kaydedilen çözülmüş bayt geri çağrısına devrederek halleder

Hiçbir okuyucu kayıtlı değilse, geri dönüş bilinçli olarak dar tutulur, en iyi çaba değil. HotPDF akış sözlüğünü /Filter için kontrol eder ve varsa paleti tamamen reddeder; sıkıştırılmış baytları aramaya besleyip kendinden emin yanlış bir tablo üretmek yerine. Filtre yoksa, hival ve taban bileşen sayısından hesaplanan bayt bütçesiyle sınırlı ham baytları okur. Akış dalının tamamı, reddetmeler dahil her çıkış yolunda özgün Stream.Position'u geri yükleyen bir try..finally içinde oturur, çünkü nesne paylaşılıdır ve sonraki tüketici hareket ettirilmiş bir imleç için size teşekkür etmez. Regresyon paketindeki LookupStreamUsesDecodedReaderAndRestoresPosition, bu davranışın iki yarısını da sabitler

HotPDF'te bir PDF akışı olarak saklanan Indexed arama tablosu için karar diyagramı: kayıtlı bir HPDFSetColorStreamReader geri çağrısı filtrelenmiş baytları çözer, /Filter girdili kayıtsız bir akış doğrudan reddedilir ve filtresiz bir akış, try..finally her çıkışta Stream.Position'ı geri yüklerken bayt bütçesine kadar ham okunur
Filtrelenmiş bir akışı reddetmek, sıkıştırılmış baytları aramaya beslemekten iyidir ve özgün akış konumunu geri yüklemek paylaşılan nesneyi sonraki tüketici için güvende tutar
// Herhangi bir renk uzayını çözmeden önce iki geri çağrıyı da kaydedin, aksi hâlde
// dolaylı tabanlar ve sıkıştırılmış arama akışları doğrudan reddedilir
HPDFSetColorSpaceResolver(MyResolveIndirectObject);
HPDFSetColorStreamReader(MyDecodedStreamReader);
try
  CS := HPDFResolveColorSpace(ColorSpaceObj);
  if (CS.Family = csfIndexed) and (Length(CS.Palette) = 0) then
    LogWarning('lookup table unreadable: filtered stream or no reader');
finally
  HPDFSetColorStreamReader(nil);
  HPDFSetColorSpaceResolver(nil);
end;

Bir Indexed renk uzayının sonsuza dek özyinelemesini ne durdurur?

/Indexed'i doğrudan bir taban olarak reddetmek gereklidir ama uzaktan yeterli değildir. İlginç saldırı yandan gelir. Geçerli bir [/Indexed /Separation ...] alın ve §8.6.6.4'ün bir Separation'ın bir alternatif uzay adlandırmasına ve §8.6.6.5'in DeviceN'in aynısını yapmasına izin verdiğini hatırlayın. O alternatifi, başladığınız özgün Indexed dizisine geri işaret eden dolaylı bir başvuru yapın. Zincirdeki hiçbir şey tek bir atlamada doğrudan taban kuralını ihlal etmez: taban bir Separation'dır, Separation alternatifi bir dizidir ve dizi yalnızca başladığınız dizidir. Onu safça çözün ve ayrıştırıcı yığın tükenene kadar döngüyü yürür. Bu uzayların dayandığı tint dönüşüm mekanizması başlı başına bir konudur; Separation ve DeviceN spot renklerini render etme yazısında ele alınır; burada yalnızca döngüdeki bir halka olarak önemlidirler

HotPDF bunu her yolun zaten aktığı tek giriş noktasında kapatır. HPDFResolveColorSpace, dahili çözücüyü HPDFColorSpaceMaxDepth ile sınırlanan, 64 olan bir derinlik sayacına sarar. Bu sınırı aşın ve koruma bir bayrak set eder; önemli olan bayraktır: geri sarma sırasında bayrağın set edildiğini gören herhangi yuvalama düzeyi, kendi kısmen çözülmüş kaydını atar ve csfUnsupported döndürür; böylece grafiğin yarısı korunmak yerine tamamı reddedilir ve en dıştaki çağrı döndüğünde iki sayaç da sıfırlanır. Bu sayaçlar birim genelleri değil threadvar olarak bildirilir ve bu tesadüfi bir düzen değildir: GResolveObj ve GReadStream da aynı nedenle, sayfalar paralel render edildiği için iş parçacığı yerel depolamada yaşar. Paylaşılan bir genel, bir iş parçacığının yuvalanmış bir DeviceN zincirinin derinlerindeyken tamamen sıradan bir [/Indexed /DeviceRGB 255 <...>] ayrıştıran başka bir iş parçacığı için sınırı tetiklemesine ve en az hata ayıklanabilir tür olan, yüke bağlı, deterministik olmayan bir başarısızlık üretmesine izin verirdi. Altmış dört, nadiren dört veya beş düzeyden derine yuvalanan meşru içerik için rahat bir paydır; bu yüzden yapısal olarak geçerli görünen bir diziden gelen csfUnsupported'ı günlüğe kaydetmeye değer sayın: pratikte bir döngü veya hiçbir gerçek üreticinin yaymadığı bir derinlik anlamına gelir. Separation ve DeviceN üzerinden parametrelenen IndirectAlternateCycleIsUnsupported ve ExcessivePatternDepthIsUnsupported iki durumu da kapsar

HotPDF renk uzayları için döngü diyagramı ve koruma: alternatifi aynı diziye geri işaret eden dolaylı bir başvuru olan bir Separation tabanlı Indexed dizi, HPDFColorSpaceMaxDepth ile 64'te sınırlanan bir derinlik sayacıyla HPDFResolveColorSpace tarafından çözülür; sonrasında her geri sarma düzeyi kaydını atar ve csfUnsupported döndürür
Tek bir atlamada hiçbir şey doğrudan taban kuralını bozmaz; bu yüzden HotPDF çözümleme derinliğini 64 düzeyle sınırlar ve yarısını tutmak yerine tüm grafiği reddeder
function TryResolveImageSpace(Obj: THPDFObject;
  out CS: THPDFColorSpace): Boolean;
begin
  CS := HPDFResolveColorSpace(Obj);
  // burada csfUnsupported, gerçekten bilinmeyen bir aileyi, dolaylı
  // bir alternatif üzerinden bir döngüyü ve 64 düzeyin ötesindeki derinliği kapsar
  Result := CS.Family <> csfUnsupported;
  if not Result then
    CS := HPDFDefaultColorSpace;   // DeviceGray, PDF başlangıç uzayı
end;

Çağıranlarınız için belgelemeye değer bir API sınırı

HotPDF bir Indexed palet kurarken ayrıştırma anında yürürlükte olan ICC dönüştürme yapılandırmasını uygular ve HPDFSetColorSpaceRenderingIntent'e yapılan sonraki çağrılar zaten çözülmüş bir paleti yeniden üretmez. Bu fonksiyon, elinize verdiğiniz kaydın ICCBased ve alternatif dönüşümlerini yürür ve bu hatları yeniden kurar; Palette'teki düzleştirilmiş RGB baytları ayrıştırma anı eseridir ve oldukları gibi kalır. Bu, erken normalleştirmenin bir sonucudur, bir gözden kaçırma değil ve bir müşterinin keşfetmesine bırakmak yerine kendi API dokümantasyonunuza yazmaya değer. Uygulamanız bir kullanıcının oturum ortasında render amacını değiştirmesine izin veriyorsa, değişiklikten sonra renk uzaylarını yeniden çözün; amacı boru hattı kurulumu sırasında bir kez ayarlıyorsa — ezici çoğunlukta olan durum — yapacak bir şey yoktur. Aynı sıralama HPDFSetICCColorPipeline üzerinden kurulan prova profilleri için de geçerlidir: çözmeye başlamadan önce yapılandırın, sonra değil

Yazma tarafındaki ayna görüntüsü hata

Yukarıdakilerin hepsi okumayla ilgilidir. Yazma tarafındaki simetrik kusurun sevk edilmesi daha kolay ve gözden kaçırılması çok daha kolaydır. HotPDF bir Windows 8 bit bitmap'ten bir /Indexed dizisi kurarken GetPaletteEntries'ı çağırır ve bu API, paletin gerçekte tuttuğu girdi sayısı kadar doldurur ve o sayıyı döndürür. Özgün kod sonucu = 0 için test etti ve ardından yığın dizisi üzerinde sabit 256 girdi yineledi. 16 renkli bir bitmap için bu, 240 girdilik başlatılmamış yığın belleğini, sonra bir müşteriye verdiğiniz bir PDF'in arama dizesine yazdı; hival 255'e sabit kodlandığı için her okuyucu kalıntıyı gerçek renk verisi olarak özenle ele aldı. Aynı kıyafetleri giyen bir doğruluk hatası ve bir bilgi ifşası hatasıdır. Düzeltme gösterişsiz ve eksiksizdir: çağrıdan önce diziyi sıfırlayın, ardından döndürülen sayıyı hem döngü sınırı hem hival için onurlandırın. Kendi kodunuzda herhangi bir yerde /Indexed görüntüleri üretiyorsanız, şimdi hival'i kontrol edin

İki bitişik parça resmi tamamlar: Indexed paletler ve Type 0 örneklenmiş fonksiyonlar, Type 0 fonksiyon renk arama tabloları yazısında ele alınan çözülmüş akış okuyucuyu ve aynı kenetleme disiplinini paylaşır; arama akışlarını yöneten filtre kuralları, çözme filtreleri üzerinden yüklenmiş görüntüleri çıkarma yazısındaki görüntü verilerini yönetenlerle aynıdır. Burada anlatılan renk uzayı çözücüsü, Delphi ve C++Builder için HotPDF Delphi Component'in bir parçası olarak gelir; ürün sayfasında eksiksiz THPDFColorSpace referansı vardır