Diskte bir PDF'niz var, bir müşteri onu bir fatura yığınından taradı ve sizin göreviniz sayfa görüntülerini OCR aşaması için yeniden bitmap olarak çıkarmak. Dosyayı yüklersiniz, image XObject'leri bulursunuz ve sonra kimsenin sizi uyarmadığı kısmı keşfedersiniz: bu akışlardaki baytlar piksel değildir. Onlar bir JPEG codestream'dir, ya da dalgaçık sıkıştırmalı bir JPEG 2000 bloğudur, ya da Group 4 fax satırlarıdır, ya da Flate filtresinin arkasında paletin arkasında duran indeksli bir rasterdır. Görüntü nesnesi genişliğini ve yüksekliğini bilir, ama gerçek örnekler üreticinin seçtiği filtrenin içinde mühürlenmiştir. Kullanılabilir bir TBitmap elde etmek, o filtreyi geri çözmeyi gerektirir ve PDF, baytların mühürlenmesi için yaklaşık sekiz farklı yol sunar
Bu boşluğu ExtractLoadedImage HotPDF'de doldurur, Delphi ve C++Builder için yerel VCL PDF bileşeni. Yüklediğiniz bir belgede image XObject'leri numaralandırır, her birinin ne olduğunu bildirir ve çözebildiği nesneleri 24 bitlik bir bitmap'e geri açar. İlginç olan kısım API yüzeyi değil, üç metottur. Asıl mesele, ayrı bir çözümleme yoluna neden ihtiyaç duyulduğu ve bunun hangi durumlarda piksellere geri dönüştürebildiğidir
Yüklü görseller neden zaten çözülmüş değildir
HotPDF'nin yükleyicisi geçiş kalitesi üzerine kuruludur. LoadFromFile çağırdığınızda, görüntü akışları kaynak dosyada göründükleri haliyle aynen korunur: özgün filtre, özgün sıkıştırılmış baytlar, özgün sözlük. Bu bilinçli bir tercihtir. Bir belge yüklemenin bütün amacı genellikle sayfaları kopyalamak, dosyaları birleştirmek, damgalamak, yeniden yetkilendirmek ve geri yazmaktır; bunların hepsi için en ucuz ve en güvenli yol her görüntü akışını olduğu gibi bırakmaktır. Yükleme sırasında her görseli rastere çözmek, çoğu çağıranın asla ihtiyaç duymadığı bir iş için bellek ve CPU harcar; kaydederken yeniden kodlamak da olduğu gibi kopyalanması gereken görselleri bozar
Bunun sonucu olarak yüklenen nesne grafiği hiçbir piksel taşımaz. /Filter olarak /DCTDecode tutan bir image XObject, JPEG baytlarını barındırır; HotPDF buna karşı bir JPEG çözücüsü çalıştırmamıştır, çünkü kopyala ve yeniden yaz yolunda buna ihtiyaç duyulan hiçbir şey yoktur. Bu yüzden gerçekten piksel istediğinizde, çıkarma API'sinin söz konusu görselin hangi filtreyi kullanıyorsa onu sıfırdan çözmesi gerekir. Yükleyiciden ayrı duran kod çözme katmanlarının nedeni de budur: Delphi'de PDF'lere JPEG 2000 görselleri ekleme makalesi, JPX motorunun üretim tarafına nasıl eklendiğini anlatır ve bu motor çıkarma API'si ona ihtiyaç duyana kadar okuma yoluna hiç bağlanmamıştı
Üç metotlu API
Yüzey küçüktür. GetLoadedImageCount yüklü belgenin kaç image XObject içerdiğini döndürür. GetLoadedImageInfo bunlardan birinin indeksine göre tanım kaydını doldurur. ExtractLoadedImage çözümlenmiş bitmap'i döndürür veya nil o görseli çözümlenemediğinde /Subtype ögesinin /Image olarak çözüldüğü bütün dolaylı nesne tablosunu tarar, bu yüzden GetLoadedImageInfo için verdiğiniz indeks ile ExtractLoadedImage için verdiğiniz indeks aynıdır
var
Pdf: THotPDF;
Info: THPDFLoadedImageInfo;
Bmp: TBitmap;
I, Count: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('scanned-invoices.pdf', '') <= 0 then
Exit;
Count := Pdf.GetLoadedImageCount;
for I := 0 to Count - 1 do
begin
if not Pdf.GetLoadedImageInfo(I, Info) then
Continue;
if not Info.Decodable then
Continue; // filter or colour space not supported
Bmp := Pdf.ExtractLoadedImage(I);
if Bmp <> nil then
try
Bmp.SaveToFile(Format('img_%d.bmp', [I]));
finally
Bmp.Free; // caller owns the bitmap
end;
end;
finally
Pdf.Free;
end;
end;
Burada iki sözleşme ayrıntısı önemlidir. İlk olarak, döndürülen TBitmap sizin tarafınızdan serbest bırakılmalıdır; belge onu ne önbelleğe alır ne de sahiplenir. İkinci olarak, çağırmadan önce Decodable değerini kontrol edin ve sonucu sonra nil ile karşılaştırın. Metot desteklenmeyen bir filtrede istisna fırlatmaz, nil döndürür ve toplu bir döngüde sessiz bir nil kimsenin fark etmeden bin sayfalık işin bir sayfasını yutması için gereken türden bir şeydir
Tanımı çözmeden önce okumak
THPDFLoadedImageInfo bir görselin ne olduğunu tam çözüme girmeden söyler. Alanları doğrudan image dictionary'den gelir: Width ve Height örnek cinsinden, BitsPerComponent, ColorComponents ve ColorSpace çözüm sonrası yorumu tanımlar (gri için 1, RGB için 3, CMYK için 4), Filter adlandırılmış sıkıştırma olarak, IsImageMask stencil maskeleri için, ObjectNumber alttaki dolaylı nesne için ve Decodable
Son bayrak dürüst olanıdır. Decodable = True yalnızca çalışma derlemesi bu belirli filtre ve renk uzayı birleşimini gerçekten bir bitmap'e dönüştürebildiğinde geçerlidir. Gerçek destek matrisini kodlar, bir istek değil: bir görselin Filter geçerli derlemenin anlamadığı durumda rapor ettiği Decodable = False, ve buna göre günlükleyebilir, atlayabilir ya da ham akışı kendiniz çıkarmaya geri dönebilirsiniz. Bunu bir önkoşul olarak görün, ipucu olarak değil
// Triage every image before committing to a decode.
var
Pdf: THotPDF;
Info: THPDFLoadedImageInfo;
I: Integer;
begin
// ... Pdf loaded ...
for I := 0 to Pdf.GetLoadedImageCount - 1 do
begin
if not Pdf.GetLoadedImageInfo(I, Info) then
Continue;
if Info.Decodable then
// ExtractLoadedImage(I) will return a TBitmap
else
// unsupported filter/colour space: log the object and skip
Writeln(Format('Image %d obj %d: %dx%d %s/%s not decodable',
[I, Info.ObjectNumber, Info.Width, Info.Height,
String(Info.Filter), String(Info.ColorSpace)]));
end;
end;
El ile tanım kaydı oluşturanları tökezleten bir uygulama ayrıntısı vardır. THPDFLoadedImageInfo iki AnsiString alan, Filter ve ColorSpace. Bunlar başvuru sayımlı yönetilen türlerdir, bu yüzden bir kaydı FillChar(Info, SizeOf(Info), 0) ile sıfırlama refleksi burada yanlıştır: string başvurusunun referans sayacını azaltmadan üzerine yazar, bu da sızıntı ya da bozulmaya yol açar. HotPDF bu yüzden kaydı alan alan başlatır ve kendi kodunuzda bu kalıbı kopyalarsanız aynı şeyi yapın
Tek bir dağıtıcı, sekiz filtre yolu
Bu özelliğin tek bir sürüm yerine bir dizi sürümle gelmesinin nedeni, PDF'nin bir görüntü biçimine sahip olmamasıdır. Filtreleri vardır ve ISO 32000-1'in §8.9.5 maddesi, bir image XObject'in bunlardan herhangi birini /Filter içinde adlandırmasına izin verir; örnek yorumu ise ayrı olarak /ColorSpace, /BitsPerComponent ve isteğe bağlı bir /Decode dizisi tarafından yönetilir. ExtractLoadedImage filtre adını okur ve her durum için özel bir çözücüye yönlendirir. v2.229 ile v2.231 arasında oluşturulan destek kümesi artık sekiz ayrı yolu kapsıyor
- Ham rasterler (FlateDecode, LZWDecode veya filtre yok) 8 bit DeviceRGB ya da DeviceGray biçimindedir. Baytlar sıkıştırması açılmış bir rastera dönüşür ve tek dönüşüm, aşağıda anlatılan kanal değişimidir
- DCTDecode (JPEG). Codestream, VCL'nin
TJPEGImageine teslim edilir, bu sınıf geometriyi ve rengi çözer ve sonuç 24 bitlik bir bitmap'e atanır - JPXDecode (JPEG 2000). OpenJPEG arka ucu üzerinden çözümlenir, aynı motor JPEG 2000 makalesinde anlatılan motordur ve yüksek bit derinliğine sahip bileşenler 8 bite yeniden örneklenir
- Indexed renk. Palet,
[/Indexed base hival lookup]dizisinden okunur ve her örnek arama tablosu üzerinden gerçek renge genişletilir - DeviceCMYK. Dört kanallı örnekler, standart mürekkep-üzerinde-beyaz formülüyle RGB'ye dönüştürülür
- 8 bit altı DeviceGray ve Indexed için, bileşen başına 1, 2 veya 4 bit olduğunda örnekler tek tek açılır ve 0-255 aralığına ölçeklenir
- CCITTFaxDecode, Group 3 ve Group 4 fax filtreleri, özel bir T.4/T.6 arka ucu tarafından çözümlenir
- JBIG2Decode, yüksek oranlı ikili düzey filtresi, native JBIG2 compression article covers from the encode side
Sonuç herkesin beklediği aynı yere gelir: 24 bitlik bir BGR bitmap, çünkü bir VCL TBitmap bunu yerel olarak saklar ve tüm aşağı akış tüketicileri bunu bekler
Sessizce piksel değiştiren dönüşümler
Bu yolların ikisi, fark edilmesi kolayca kaçabilen ve çözücüye hiç dokunmasanız bile anlamaya değer bir dönüşüm içerir. İlki renk sırası değişimidir. Bir PDF DeviceRGB rasteri örnekleri kırmızı-yeşil-mavi sırasıyla, en üst satırdan başlayarak saklar. Bir VCL 24 bit scanline ise bunları mavi-yeşil-kırmızı sırasıyla saklar. Dolayısıyla düz bir RGB görseli çözmek bir memcpy değildir; her pikselin ilk ve üçüncü baytları scanline içine yazılırken yer değiştirir. Bunu ters yaparsanız kırmızılarınız ve mavileriniz yer değiştirir, bu da gri tonlamalı bir test görselinde normal görünür ama renkli bir görselde felaketle yanlıştır. Sıra düzeni, değeri ne olursa olsun, doğrudan aktarılır: PDF'nin yukarıdan aşağı rasterleri VCL'nin ScanLine[0] üst görsel satırı olarak hizalanır, bu yüzden dikey çevirme gerekmez
İkincisi CMYK'dır. PDF DeviceCMYK görselleri dört mürekkep taşır ve RGB'ye dönüşüm arama tablosu değil, kanal başına bir hesaplamadır: her çıktı kanalı (255 - ink) * (255 - K) / 255 bir aygıt yaklaşık değeridir, ICC profili üzerinden geçen renk yönetimli bir dönüşüm değildir, bu yüzden sonuç gösterim ve yeniden rasterleme için yeterince doğrudur ama baskı doğruluğu gerekiyorsa doğru yol değildir. İş akışınız sadakat gerektiriyorsa, çıkarılan bitmap'i bir önizleme olarak düşünün ve özgün CMYK akışını renk yönetimli ardışık işlem hattı için saklayın
Indexed yolu kendi içinde bir ayrıştırma tuzağı gizler. Bir /Indexed /Indexed renk uzayındaki palet, değişmez dize ya da onaltılık dize olarak saklanabilir ve HotPDF, bir hex string değerini hex metin olarak değil, çözümlenmiş baytlar olarak saklar. Palet bir hex string olduğunda, arama tablosu önce hex'ten bayta çözümden geçirilmelidir; düz dize zaten ham baytlardır. Bu dalı kaçırırsanız dört renkli indeksli görsel çöp çıkar, çünkü her palet girdisi yanlış bayt sınırından okunuyordur
Filtre zincirleri: son filtre görselin kendi filtresidir
Tek bir /Filter adı kolay durumdur. PDF ayrıca bir zincir filtresine izin verir; akış ardışık olarak birkaç filtreden geçirilir ve bunlar /Filter dizisinde sıralı olarak listelenir; [/ASCII85Decode /FlateDecode] ya da [/ASCIIHexDecode /DCTDecode] (ISO 32000-1 §7.4) gibi. Anlam açıktır: filtreler kodlamada soldan sağa uygulanır, bu yüzden çözmede onları sağdan sola geri alırsınız ve dizideki son filtre aslında görüntü biçimini tanımlayan filtredir. Baştaki filtreler yalnızca bunun etrafına sarılmış aktarım kodlamalarıdır
Çıkarıcı bunu soyup ayırarak ele alır. Herhangi bir görüntü çözücü çalışmadan önce, zincirdeki son filtreden başka her filtre uygulanır, son filtrenin beklediği girdi üretilir ve yönlendirme ancak o son filtre üzerinde yapılır. Yani [/ASCII85Decode /DCTDecode] önce ASCII85'ten arındırır, sonra sonucu JPEG yoluna yönlendirir; [/FlateDecode] ile sarılmış bir ham raster sıkıştırması açılır ve ardından raster yolu çalıştırılır. Sekiz çözücünün sade kalmasını sağlayan şey budur. Hiçbirinin ASCII85 ya da hex aktarım sarmalayıcılarını bilmesi gerekmez, çünkü bir çözücü baytları gördüğünde sarmalayıcılar çoktan gitmiştir. Ayrıca son filtresi desteklenmeyen bir zincir de yarı yolda değil, yönlendirme adımında temizce başarısız olur
Çıkarma nerede durur, sonra ne yapılır
Sınırlar konusunda kendinize dürüst olun. Son filtresi desteklenen kümenin dışında kalan bir görsel nil, renk uzayı derlemenin yorumlayamadığı bir görsel de aynı şekilde. Soft maskeler ve alfa bitmap içine yeniden oluşturulmaz; birleşik sonucu değil, temel görseli alırsınız. JPEG 2000'den 8 bit üzerindeki bit derinlikleri aşağı yeniden örneklenir; bu bilerek kayıplıdır ve yeniden arşivleme yapıyorsanız, görüntüleme için değil, yanlış hamledir. Kendi rengi olmayan tek bitlik bir stencil olan image mask ise tanım kaydında açıklanır ama resimsel bir görselden farklıdır; onu bir fotoğraf bekleyerek çözerseniz şaşırırsınız
Çıkarma yeterli olmadığında, ham akış hâlâ yüklü nesne grafiğinin içinde, filtresiyle birlikte durur; onu bayt bayt çekip kendi özel kod çözücünüze verebilirsiniz. Geçiş-koruyucu tasarımın bilerek koruduğu yedek yol budur: özgün baytlar asla atılmaz, yani en kötü durumda veri kaybolmaz, yalnızca siz kendiniz çözersiniz. Yine de çoğu gerçek işte desteklenen sekiz filtre, tarayıcıların, ofis paketlerinin ve rapor motorlarının gerçekten ürettiği şeyleri kapsar ve GetLoadedImageCount üzerinde bir Decodable korumasıyla yapılan bir döngü, yüklü bir PDF'yi birkaç satırda bitmap klasörüne çevirir
Burada anlatılan çözüm filtrelerinin tam kümesiyle birlikte yüklü-görsel çıkarma API'si, HotPDF Component içinde Delphi ve C++Builder için sunulur