Teknik Makale

PDF'te CMYK JPEG: Delphi'de DCTDecode ColorTransform

PDF'ten çıkan bir CMYK JPEG'in renkleri bulanık, kaymış ya da negatifse neredeyse her zaman çözücünün iki gerçekten birini yanlış bilmesi söz konusudur: örnekler YCCK olarak saklanıp mı dönüştürülmeliydi, yoksa Adobe kuralına göre ters mi saklanmışlardı. HotPDF'nin çözücüleri, üç ve dört kanallı görüntüler için DCTDecode /ColorTransform parametresine uyar, belirtimin tanımlamadığı değerleri reddeder ve JPEG verisinin içindeki bir Adobe APP14 markerının, ISO 32000-1 Tablo 13'ün istediği gibi o sözlük girişine üstünlük kurmasına izin verir. Aynı tuzak ailesinden iki komşu kural daha vardır: çok filtreli akışlar aşamaya hizalı /DecodeParms dizileri taşımak zorundadır ve JPEG 2000 görüntüler açık /Decode eşlemelerini korur

Üçü de aynı temayı paylaşır. PDF zaten ne demek istediğini söyler; tuzak, biraz farklı bir soruya cevap veren bir renderer'dır

PDF'te bir CMYK JPEG neden ters ya da soluk render olur?

PDF'te bir CMYK JPEG iki nedenden biri yüzünden bozulur ve ikisi ekranda farklı görünür. Beyaz kağıdın siyaha döndüğü, açık tonların koyulaştığı negatif bir görüntü, ters saklamaya işaret eder. Cilt yeşile dönerken siyahların griden kaldığı, K detayının keskin kaldığı soluk ya da ton kaymış bir görüntü, gerektiğinde uygulanmamış ya da gereksiz uygulanan bir renk dönüşümüne işaret eder

Delphi PDF'inde bozuk bir CMYK JPEG için belirti haritası: negatif görüntü ters Adobe saklamaya, soluk ve kaymış renkler uyuşmayan bir ColorTransform'a işaret eder
negatif görüntü, Decode dizisi isteyen ters saklamaya işaret eder; renk kayması uyuşmayan bir dönüşüme, keskin K düzlemi ise önce dönüşüme işaret eder

Dönüşüm sorunu, JPEG'in rengi nasıl sıkıştırdığından gelir. Baseline JPEG, parlaklık krominansdan ayrıldığında en iyi sıkıştırdığı için kodlayıcılar genelde sıkıştırmadan önce RGB'yi YCbCr'ye çevirir; dört kanallı kodlayıcılar ise CMY'yi YCC'ye çevirip K'yi olduğu gibi geçirir, bu biçimin adı YCCK'dir. Çözücü, kodlayıcının uyguladığı dönüşümü tam tersiyle geri almak zorundadır. Hiç olmamış bir dönüşümü geri alın ya da olmuş birini atlayın; her piksel kayar. Siyah kanal her iki durumda da hayatta kalır; bu yüzden bozuk bir CMYK görüntü, her şey kayarken keskin siyah metni sık sık korur

Tersleme sorunu tarihseldir. Photoshop ve öteki Adobe araçları, CMYK JPEG'leri her örneği 255 - value olarak saklayarak yazar ve dosyayı bir Adobe APP14 markerıyla işaretler. JPEG standardında böyle bir şey yoktur; bir kuraldır ve her tüketicinin ondan haberi olmak zorundadır. PDF'te terslemenin, tahmine bırakılmak yerine görüntü sözlüğünde ifade edilmesi beklenir

/ColorTransform çözücüye ne söyler?

/ColorTransform, çözücünün hangi dönüşümü geri alması gerektiğine dair PDF düzeyindeki beyandır. ISO 32000-1 §7.4.8, Tablo 13, iki değer tanımlar. Sıfır, dönüşüm olmadığı anlamına gelir: çözücü örnekleri sıkıştırıldıkları gibi döndürür. Bir, kodlayıcının standart dönüşümü uyguladığı anlamına gelir; üç kanallı bir görüntü YCbCr'den RGB'ye, dört kanallı bir görüntü YCCK'den CMYK'ye döner. Parametre, bir ve iki kanallı görüntülerde yok sayılır. Tablo 13, gözden kaçması kolay bir öncelik kuralı da koyar: kodlanmış veri kendi transform kodunu taşıyan bir Adobe markerı içeriyorsa o kod yönetir ve sözlük girişi yok sayılır. Yalnızca markerı olmayan bir JPEG sözlüğe düşer; girişin de yokluğunda üç kanallı görüntü dönüşüme, dört kanallı görüntü ise dönüşümsüzlüğe varsayılan

Delphi için DCTDecode ColorTransform karar matrisi: Adobe APP14 markerı sözlüğün üstünde durur ve tanımsız değerler HotPDF tarafından reddedilir
JPEG verisinin içindeki Adobe markerı, sözlük girişini geçersiz kılar; markerı olmayan bir JPEG, /ColorTransform'a ya da bileşen sayısı varsayılanına düşer

Kolay hata, parametre sözlüğünü kabul eden ama onu asla JPEG çözücüsüne ulaştırmayan bir görüntü yoludur. (128, 63, 31) rengini taşıyan tek piksellik bir RGB JPEG bunu hemen açığa çıkarır: böyle bir çözücü, /ColorTransform 0 ile /ColorTransform 1 için de [128, 63, 31] döndürür; sıfır için doğru cevap ise ham YCbCr üçlüsü [79, 101, 163]'tür

HotPDF parametreyi bayt düzeyinde uygular. HPDFDecodeHeadlessTerminalImage önce JPEG segment yapısını start-of-scan markerına dek gezdirir ve bir Adobe APP14 segmenti arar; bir comment segmenti içinde ona benzeyen baytlar sayılmaz. Veri markerı taşıyorsa karar onun transform kodunundur ve sözlük değeri yok sayılır. Taşımıyorsa çözücü, JPEG'in start-of-scan markerından hemen önce bir APP14 segmenti yerleştirilmiş özel bir kopyasını kurar; kopya 0, 1 ya da değeri 1 olan dört kanallı görüntü için YCCK kodu 2'yi taşır. Üç kanallı bir görüntünün etkin dönüşümü 0 olduğunda kopya ayrıca JFIF başlığını etkisizleştirir, çünkü tek başına bir JFIF başlığı libjpeg-turbo'ya YCbCr varsaymasını söyler. Çağıranın tamponuna hiç dokunulmaz ve bir segment yerleştirildiğinde 16 ek baytı, çözücü hiçbir şey ayırmadan önce çalışma baytı bütçesinden düşülür

Yalnızca marker yokken yerleştirmek önemli, çünkü libjpeg'in bir detayı var: gördüğü son APP14 segmentini kullanır. Var olan bir markerdan sonra segment yerleştirirseniz açık bir sözlük değeri, verideki çelişen bir markerı sessizce geçersiz kılar; bu, Tablo 13'ün tam tersidir. HotPDF, Tablo 13 sırasını hem tam görüntü XObject yolunda hem inline görüntü yolunda uygular

Tek bir görüntü yanlış görünüyorsa ve sayfayı render etmeden örneklerini görmek istiyorsanız aynı terminal çözücüyü doğrudan çağırabilirsiniz. İşlev HPDFHeadlessImageCodecs unit'inde yaşar; filtre adını, kodlanmış baytları, isteğe bağlı bir parametre sözlüğünü ve görüntü sözlüğünden gelen, JPEG frame başlığıyla eşleşmek zorunda olan geometriyi alır

uses
  SysUtils, HPDFObjs, HPDFHeadlessImageCodecs;

// ColorTransform < 0, girişin yok demektir: JPEG markerları karar versin
function DecodeDCT(const Encoded: TBytes; Width, Height, Channels,
  ColorTransform: Integer): TBytes;
var
  Parms: THPDFDictionaryObject;
begin
  Parms := nil;
  if ColorTransform >= 0 then
  begin
    Parms := THPDFDictionaryObject.Create(nil);
    Parms.AddNumericValue('ColorTransform', ColorTransform);
  end;
  try
    Result := HPDFDecodeHeadlessTerminalImage('DCTDecode', Encoded, nil, Parms,
      Width, Height, Channels, 8, 64 * 1024 * 1024, nil);
  finally
    Parms.Free;
  end;
end;

Çözücü, neyin meşru bir değer sayılacağı konusunda katıdır. Açık bir -1, bir 2 ya da 0.5 gibi tam sayı olmayan bir değer, varsayılana düşmek yerine EHPDFHeadlessImageError fırlatır; çünkü belirtimin tanımlamadığı bir değer, çözücünün güvenebileceği hiçbir şey vermez. Verideki bir Adobe markerı girişi ilgisiz kılsa bile kontrol yerinde kalır, çünkü tanımsız değer belgenin kendisinde yapısal bir hatadır. Dönüştürülmüş kopya için çok küçük bir bütçe de aynı biçimde başarısız olur; mesaj, çalışma baytı bütçesini adlandırır

Açık bir null başka bir durumdur, çünkü hiçbir değer değildir. ISO 32000-1 §7.3.9'a göre değeri null olan bir sözlük girişi, bulunmayan bir girişe eşdeğerdir; dolayısıyla /ColorTransform null, anahtar hiç yazılmamış gibi marker'a ya da bileşen sayısı varsayılanına düşer. HotPDF onu her yolda böyle işler; headless ile VCL, akış ile inline ayrımı gözetmez

Bir varsayılan daha anılmayı hak eder, çünkü HotPDF'nin burada Tablo 13'ün harfinin kasıtlı olarak ötesine geçtiği yer burasıdır. Ne JFIF başlığı ne Adobe markerı taşıyan ve frame başlığı bileşenlerini R, G ve B olarak etiketleyen üç bileşenli bir JPEG, kendi baytlarıyla RGB tuttuğunu söyler. Tablo 13 onu yine dönüşüme varsayılan yapardı, ama libjpeg ve üzerine kurulu okuyucular onu dönüşümsüz çözer; HotPDF da öyle. Headless ile VCL çözücüleri de öyle işler, kırmızı ile mavi verinin koyduğu yerde kalır. Açık bir sözlük 0 ya da 1 bu çıkarımı yine de geçersiz kılar

uses
  System.IOUtils;

var
  Jpeg, Stored, Converted, ByMarkers: TBytes;
begin
  // PDF akışından kaydedilen 640 x 480 CMYK görüntünün ham DCTDecode baytları
  Jpeg := TFile.ReadAllBytes('suspect-image.jpg');
  try
    // Veride Adobe APP14 markerı varken üç çağrı da aynı sonucu verir:
    // karar markerındır, sözlük değeri yok sayılır
    Stored    := DecodeDCT(Jpeg, 640, 480, 4, 0);   // marker yok: örnekler sıkıştırıldığı gibi
    Converted := DecodeDCT(Jpeg, 640, 480, 4, 1);   // marker yok: YCCK geri CMYK'ye çevrilir
    ByMarkers := DecodeDCT(Jpeg, 640, 480, 4, -1);  // marker yok: dört bileşen varsayılanı, dönüşüm yok
    Writeln(Format('pixel 0: stored %d,%d,%d,%d  converted %d,%d,%d,%d',
      [Stored[0], Stored[1], Stored[2], Stored[3],
       Converted[0], Converted[1], Converted[2], Converted[3]]));
  except
    on E: EHPDFHeadlessImageError do
      Writeln('Decoder refused the image: ', E.Message);
  end;
end;

Dört kanallı bir referans piksel, neye bakılacağını gösterir. YCCK olarak saklanan (10, 20, 30, 40) CMYK pikseli, YCCK dönüşümü geri alınmadığında [245, 235, 225, 215], alındığında [0, 116, 0, 215] döner. 215 olan K baytı ikisinde de aynıdır. K düzlemi doğru görünüyorsa ve öteki üç bileşen görünmüyorsa ilk şüpheli, uyuşmayan bir dönüşümdür

Ters CMYK kuralı /Decode'e aittir

HotPDF terminal çözücüsü CMYK örneklerini tam olarak libjpeg-turbo'nun ürettiği gibi döndürür ve bir Adobe markerının gücüyle onları çevirmez. Yukarıdaki sayılar kuralı görünür kılar: 245, 235, 225, 215, düz meansıyla 255 eksi 10, 20, 30, 40'tır; Adobe tarzı kodlayıcıların sakladığı ters biçimdir. Bunu tekrar mürekkep miktarına çevirmek görüntü sözlüğünün işidir; böyle bir JPEG gömen üretici oraya /Decode [1 0 1 0 1 0 1 0] yazmalıdır. Renderer /Decode dizisini her bileşene uygular; doğru yazılmış bir dosya doğru basılır

Tersi de açıkça söylenmeyi hak eder. Adobe tersli bir CMYK JPEG'i, eşleşen /Decode dizisi olmadan gömen bir PDF, HotPDF'de negatif render olur; çünkü dosya görüntüyü zaten öyle anlatıyor. Bazı görüntüleyiciler APP14 markerını sezip sessizce telafi eder. HotPDF o sezgiyi eklemez; böyle bir dosyanın düzeltmesi onu yazan yazılıma aittir

İki filtre için tek DecodeParms sözlüğü neden hatadır?

/Filter değeri birkaç aşamalı bir dizi olan bir akış, /DecodeParms değerini aynı uzunlukta bir dizi olarak vermek zorundadır: aşama başına bir parametre sözlüğü ya da null. ISO 32000-1 §7.3.8.2, Tablo 5, o kuralı koyar ve HotPDF onu zorunlu kılar: tek paylaşılan sözlük veren çok filtreli bir akış, kod çözülmek yerine geçersiz belge olarak raporlanır

Hoşgörülü alternatif göründüğünden kötüdür. /DecodeParms zaten bir dizi olduğunda yalnızca uzunluğu kontrol eden bir çözücü, tek sözlük ve iki filtre verildiğinde o sözlüğü her aşamada sessizce yeniden kullanır. Sözlük boşken bu zararsızdır, boş değilken yıkıcıdır: iç Flate aşaması için kastedilen bir /Predictor 12, dış aşamaya da uygulanır ve çıktı, temiz bir hata yerine inandırıcı görünen çöp olur. Böyle bir dosyanın niyeti geri alınamayacağı için HotPDF, /Filter [/FlateDecode /FlateDecode] ile /DecodeParms << >> taşıyan bir sayfa içerik akışı gibi boş durumda bile onu reddeder

Delphi'de HotPDF'in çok filtreli PDF akışları için kabul ettiği DecodeParms biçimleri: hizalı diziler geçer, paylaşılan sözlük ya da uyuşmayan dizi uzunluğu reddedilir
çok filtreli bir akış, aşama başına bir sözlük ya da null ister; paylaşılan bir sözlük, boşken bile reddedilir

Kabul edilen ve reddedilen biçimleri saymak kolaydır:

  • Kabul: herhangi sayıda filtre için /DecodeParms atlanmış ya da null
  • Kabul: uzunluğu filtre sayısına eşit ve her girişi bir sözlük ya da null olan bir dizi
  • Kabul: /Filter tam olarak bir filtre adlandırdığında tek bir sözlük
  • Ret: iki ya da daha fazla filtre tarafından paylaşılan tek sözlük; teşhis: "Stream filter chain requires array DecodeParms"
  • Ret: uzunluğu filtre sayısından farklı bir dizi; "DecodeParms array must match the filter chain"

Aynı kontrol, sıradan içerik akışları ve son filtresi DCTDecode gibi terminal bir codec olan görüntü akışları için de çalışır; böylece bozuk bir zincir, akışın taşıdığından bağımsız olarak aynı yerde başarısız olur. Sıkıştırma açma sınırlarıyla etkileşim, filtre zinciri bütçeleri ve PDF decode bombaları yazısında kapsanıyor. Uygulama kodundan bakıldığında hata, sayfa render edildiğinde yüzeye çıkar

uses
  SysUtils, HPDFHeadlessDocument, HPDFHeadlessCairoDevice;

var
  Document: THPDFHeadlessDocument;
  Options: THPDFHeadlessRenderOptions;
  Info: THPDFHeadlessRenderResult;
begin
  Document := THPDFHeadlessDocument.Create;
  try
    try
      Document.LoadFromFile('catalog-cmyk.pdf');
      Options := THPDFHeadlessRenderOptions.Default;
      Info := HPDFRenderHeadlessPagePNGFile(Document.GetRenderAccess(0),
        'page1.png', Options);
      Writeln('images=', Info.Images, ' decoded bytes=', Info.DecodedBytes);
    except
      on E: Exception do
        Writeln('Rejected: ', E.ClassName, ': ', E.Message);
    end;
  finally
    Document.Free;
  end;
end;

JPEG 2000 görüntüler açık /Decode değerlerini korur

Bir JPEG 2000 görüntüsü, /Decode değerini yalnızca sözlüğünde /ColorSpace yoksa ve görüntü bir maske değilse yok sayar. ISO 32000-1 §8.9.5 bu koşulu koyar ve HotPDF uyar. Her stencil olmayan JPX görüntüsü için /Decode değerini atmak kolay kestirmedir ve açık bir terslemeyi tamamen yok eder: /Decode [1 0] altında 64 değerli bir DeviceGray JPX örneği 191 olarak render olmak zorundadır, 64 değil

O tek koşulu doğru yakalamak tek başına yetmez. Görüntü JPX olduğunda varsayılan bileşen aralıklarını kuran Lab ve ICCBased gibi renk uzayı dalları, meşru biçimde uygulanmış bir diziyi ikinci kez üzerine yazar; bu yüzden renderer, geçerli bir /Decode dizisinin gerçekten uygulanıp uygulanmadığını kaydeder ve varsayılan aralıkları yalnızca hiçbiri uygulanmamışsa doldurur. Böylece açık Lab ve ICCBased aralıkları hayatta kalır, sözlük sessizken codestream renk beyanı yine yönetir. Biçimin çözücü tarafı JPEG 2000 ve JPXDecode yazısında anlatılıyor

Hangi kod çözme yolları bu kuralları izler?

İki hattı da izler. Headless tarafta bu, THPDFHeadlessDocument yüklemesi, paylaşılan akış çözücüsü ve HPDFRenderHeadlessPagePNGFile arkasındaki Cairo tabanlı sayfa rendererı demektir. VCL tarafta ise yüklenmiş görüntü çıkarımının bir TBitmape yapılması ve VCL sayfa renderı demektir; ikisi de tam görüntü ve küçültülmüş bölge kod çözmede aynı Tablo 13 sırasını uygular, böylece bir JFIF başlığı Adobe dönüşüm 0 üzerinde YCbCr dayatamaz ve illegal bir değer görüntüyü orada da çözülemez kılar. Inline DCT görüntüleri, görüntü XObjectlerle aynı marker, null, varsayılan ve red kurallarını izler; kısaltılmış ve tam anahtar adları aynıdır, /DP << … >> sözlüğü ile /F [/AHx /DCT] gibi kısaltılmış filtre dizileri dahil; illegal parametreli bir inline görüntü atlanır, sayfanın geri kalanı render olur. Yüklenmiş bir PDF'ten görüntü çıkarma rehberi, o API'nin filtre işlemenin geri kalanını kapsıyor

Bu davranışa güvenmeden önce birkaç pratik not:

  • JPEG kod çözme, çalışma zamanında uyumlu bir TurboJPEG kütüphanesi ister; HOTPDF_TURBOJPEG_LIBRARY varsayılan turbojpeg.dll ya da libturbojpeg.so.0 değerini geçersiz kılar
  • Katı yapısal kontroller, hoşgörülü okuyucuların açtığı dosyaları reddeder; özellikle paylaşılan boş /DecodeParms sözlüğü, o dosyaların düzeltmesi üretici tarafındadır

Renk yönetimli baskı iş akışı için pratik çıkarım kısadır: CMYK JPEG'leri, verideki herhangi bir Adobe markerıyla uyuşan açık bir /ColorTransform ve saklamalarına uyan /Decode dizisiyle yazın, her çok filtreli akışa aşamaya hizalı bir /DecodeParms dizisi verin ve HotPDF'in dosyaları gerçekte söylediklerine tuttuğunu bekleyin

Burada gösterilen headless yükleyici, akış çözücüleri ve Cairo sayfa rendererı, klasik VCL belge motorunun yanında, Delphi, C++Builder ve Lazarus için HotPDF Delphi PDF bileşeni ile gelir