Teknik Makale

Delphi'de PDF Sayfalarını 1-Bit Monokroma Render Etme

Bir faks geçidi 24-bit sayfa render'ınızı istemez. Milyonlarca taranmış görünümlü faturayı saklayan arşiv hattı da istemez; karakter aramaya başlamadan önce her şeyi siyah-beyaza eşikleyen OCR ön ucu da istemez. Üçünün de istediği aynı şeydir: piksel başına bir bit taşıyan temiz 1-bit bitmap; her nokta ya mürekkep ya da kâğıttır. Bunlara tam renkli BMP verirseniz piksel başına 23 biti zaten atarlar; üstelik çoğu zaman sizin kendiniz yapabileceğinizden daha kötü bir dithering geçişiyle. İlginç soru, bu aşağı dönüştürmenin nerede yapılması gerektiğidir; PDF Library for Delphi içindeki cevap da, yeniden yazmayı istemediğiniz bir renderer'ı genişletme konusunda faydalı bir şey söyler

PDF Library for Delphi, Delphi ve C++Builder için yerel bir Object Pascal PDF kütüphanesidir. Rendering çekirdeği sayfayı bitmap'e rasterize eder ve BMP, PNG, JPEG, WMF ve birkaç başka biçime çıktı verebilir. Yakın zamana kadar yapamadığı şey, gerçek bir monokrom bitmap geri döndürmek ya da sayfanın yalnızca bir bölümünü render etmekti. Her ikisi de v3.83.0'da geldi ve ikisi de rasterizer'ın kendisindeki değişiklikler olarak değil, mevcut renderer üstüne oturan ince convenience katmanları olarak inşa edildi. Hikâyenin bütünü bu kısıtta yatıyor

Aşağı dönüştürme neden renderer içinde değil, rendering sonrasında yapılır

1-bit görsel üretmenin bariz yolu, rasterizer'a 1-bit çizmesini söylemektir. Bu aynı zamanda diğer her şeyi bozan yoldur. Renderer'ın iç bitmap'i PixelFormat := pf24bit ile PDFlibRenderer constructor'ı içinde sabit olarak oluşturulur ve bu 24-bit yüzey bütün render yollarınca paylaşılır: PNG dışa aktarımı, device-context preview, JPEG çıktısı; hepsi. Bunu kaynakta pf1bit olarak değiştirirseniz monokrom özellik eklemiş olmazsınız; kütüphanedeki her çağıran için renk sadakatini düşürür ve aşağı akışta bir düzine gerilemeyi ayıklama işini üstlenirsiniz

Bu yüzden RenderPageToMonochromeFile tam ters yolu izler. Sayfayı önce normal şekilde, geçici 24-bit BMP'ye render eder ve ancak ondan sonra 1-bit'e çökertir. Renderer'a hiç dokunulmaz. Monokrom davranış bütünüyle convenience yönteminde yaşar; dolayısıyla onu çağırmayan hiç kimse etkilenmez. Bu tür ödünleşimi açıkça adlandırmaya değer: sonradan işleme bir ek bitmap tahsisi ve geçici dosya maliyeti öder, karşılığında yük taşıyan çekirdeği tamamen kapsam dışında tutar. Faks ve arşiv gibi kenar durumlara hizmet eden özellik için bu, muhasebe defterinin doğru tarafıdır

PDF Library for Delphi hattı: bir PDF sayfası geçici 24 bitlik bir BMP'ye işlenir, bir GDI HALFTONE blit ile 1 bitlik tek renkli bitmap'e daraltılır ve faks, arşiv ve OCR iş akışları tarafından tüketilir
Yeni yöntemler önce tam renkli bir sayfa işler, sonra biten raster'ı işleyicinin dışında aşağı dönüştürür. Faks ağ geçitleri, arşiv depoları ve OCR ön uçları gerçek bir pf1bit bitmap alır
var
  Pdf: TPDFlib;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.LoadFromFile('invoice.pdf');
    // 200 DPI klasik Group 4 faks çözünürlüğüdür; sayfa indeksi 1 tabanlıdır
    Pdf.RenderPageToMonochromeFile(200, 1, 'invoice-page1.bmp');
  finally
    Pdf.Free;
  end;
end;

1-bit çökertme gerçekte nasıl olur

Aşağı dönüştürme, el yazımı eşik döngüsü yerine GDI'ye yaslanır ve bu seçim çıktı kalitesi açısından önemlidir. Yöntemin içinde 24-bit geçici bitmap bir TBitmap içine yüklenir, aynı boyutlarda TBitmap ile ikinci bir PixelFormat := pf1bit oluşturulur ve pikseller tek blit ile taşınır:

PDF Library for Delphi: GDI daraltma ayrıntısı: grileri nokta desenlerine titreyen bir HALFTONE stretch blit ile kare kare BLACKONWHITE varsayılan eşiğini karşılaştırır
RenderPageToMonochromeFile içinde tek bir StretchBlt her pikseli aynı boyutta bir pf1bit yüzeyine taşır. HALFTONE etkinken griler, varsayılan eşiğin ürettiği bloklu biçimler yerine titremeli mürekkep noktası örüntülerine dönüşür
// RenderPageToMonochromeFile içinde, 24-bit ColorBmp yüklendikten sonra
MonoBmp.PixelFormat := pf1bit;
MonoBmp.Width  := ColorBmp.Width;
MonoBmp.Height := ColorBmp.Height;
// HALFTONE, GDI'ye 24-bit kaynağı 1-bit'e dither etmesini söyler
SetStretchBltMode(MonoBmp.Canvas.Handle, HALFTONE);
StretchBlt(MonoBmp.Canvas.Handle, 0, 0, MonoBmp.Width, MonoBmp.Height,
  ColorBmp.Canvas.Handle, 0, 0, ColorBmp.Width, ColorBmp.Height, SRCCOPY);
MonoBmp.SaveToFile('out.bmp');

İşin püf noktası SetStretchBltMode ile HALFTONE seçimidir. Kaynak ve hedef aynı boyutta olduğu için gerçek ölçekleme yapılmasa bile stretch mode, GDI'nin renkleri 1-bit palette'e nasıl eşleyeceğini yine belirler. HALFTONE, gri bölgeler ve antialias uygulanmış metin kenarlarını iki renkten en yakınına sertçe kesmek yerine siyah-beyaz nokta desenlerine çevirecek halftone dithering uygulamasını sağlar. Mode çağrısını düşürürseniz veya varsayılan BLACKONWHITE kullanırsanız, gri tonlamalı içerik bloklu, posterize eşiklenmiş şekillere dönüşür. Taratılmış belge ve OCR ön işleme çıktısı için dither edilmiş sonuç neredeyse her zaman istediğiniz şeydir

Müzakereye kapalı ve yanlış yapması kolay bir ayrıntı daha vardır: geçici render mutlaka BMP olmalıdır. RenderPageToMonochromeFile, genel renderer'ı 0 seçenek koduyla çağırır; bu, BMP'dir. RenderPageToFile üzerindeki options bağımsız değişkeni küçük tamsayı enum'udur ve bu amaç için değerler birbirinin yerine geçmez: 0 BMP'dir, 1 JPEG'dir, 2 WMF'dir, 3 EMF'dir, 5 PNG'dir ve böyle sürer. Aşağı dönüştürücü sonra geçici dosya üzerinde TBitmap.LoadFromStream yapar. 2 geçirerek ona WMF verirseniz yükleme "Bitmap image is not valid" hatasını fırlatır; çünkü Windows Metafile vektör kayıt akışıdır, DIB değildir. Monokrom aşağı dönüştürme baştan sona raster işlemidir; dolayısıyla ara format da raster olmak zorundadır

Sayfanın yalnızca alt bölgesini render etme

İkinci yöntem olan RenderPageRegionToFile, tüm sayfa yerine sayfanın yalnızca bir dikdörtgenini render eder. Herhangi bir belge görüntüleyici kurduysanız kullanım durumları tanıdıktır: sözleşmeden imza bloğunu kırpmak, büyük çizimin yakınlaştırılmış haritası için karo üretmek ya da yüksek DPI ile tüm sayfayı rasterize etme maliyetine girmeden tek damgalı bölgeyi küçük görsel olarak çekmek. İmza basittir:

PDF Library for Delphi: PDF noktalarında bölge kırpma işleme: tam çözünürlüklü işlenmiş bir PDF sayfasındaki 72,72,180,72 dikdörtgeni, kırpma ölçeklemek yerine kırptığı için 150 DPI'da 375'e 150 piksel bitmap olur
RenderPageRegionToFile, tam çözünürlüklü bir işlemeden PDF punto cinsinden ölçülmüş bir pencere keser. Çıktı bitmap'inin boyutu genişlik ve yükseklik çarpı DPI bölü 72'den gelir; asla küçültülmüş bütün sayfadan değil
// Clip, PDF point'lerinde "Left,Top,Width,Height" biçimindedir (72 pt = 1 inç)
// Burada: sayfanın sol üstünden bir inç içeride, 2,5x1 inçlik bir kutu
Pdf.RenderPageRegionToFile(150, 1, '72,72,180,72', 'sig-block.bmp');

Clip dizgesi, locale ve DelimitedText tuhaflıklarını aşmak için yöntemin içinde elle ayrıştırılan, virgülle ayrılmış dört double'dan oluşur. Genişlik ve yükseklikten yöntem çıktı bitmap boyutunu Round(Width * DPI / 72) ile Round(Height * DPI / 72) olarak hesaplar, tam bu boyutta bellekte pf24bit bitmap ayırır ve bunun device context'ine RenderPageToDCClip üzerinden render eder. Sonuç dosyası, tam sayfa yerine yalnızca kırpılmış dikdörtgeni içerir ve tüm sayfaya değil bu bölgeye göre boyutlanır

Hiçbir şey yapmayan clip parametresi

İşin, göründüğünden daha keskin olduğu nokta burasıdır. RenderPageToDCClip uzun süredir Clip parametresi taşıyordu ve bu bir yalandı. Çağrı bağımsız değişkeni kabul ediyor, bunu TPDFPageTree.RenderPageToDC içine iletiyor ve uygulama bunu bütünüyle yok sayıyordu; renderer'a hiç vermiyordu. İstediğiniz dikdörtgeni geçebiliyor ve yine tam sayfayı geri alıyordunuz. Kırpma bekleyerek RenderPageToDCClip bağlamış olan herkes tam sayfa render alıyordu; yerleşimine bağlı olarak bunu fark etmemiş bile olabilir

v3.83.0 bu teli gerçekten bağladı. RenderPageToDC artık aynı "Left,Top,Width,Height" point dikdörtgenini ayrıştırıyor ve renderer çizmeden önce bunu hedef device context üzerinde gerçek GDI clip region olarak uyguluyor. Point'ten cihaz pikseline dönüşüm, her dört kenara da uygulanan alışılmış DPI / 72 ölçek katsayısıdır. Render çevresindeki sıra, standart save/clip/restore dansıdır:

// TPDFPageTree.RenderPageToDC içinde, Clip boş olmadığında
ScaleFactor := DPI / 72;
SaveDC(TargetDC);
IntersectClipRect(TargetDC,
  Round(ClipLeft * ScaleFactor),
  Round(ClipTop * ScaleFactor),
  Round((ClipLeft + ClipWidth) * ScaleFactor),
  Round((ClipTop + ClipHeight) * ScaleFactor));
// ... renderer sayfayı burada çizer ...
// finally bloğunda:
RestoreDC(TargetDC, -1);

Buradaki SaveDC / RestoreDC(-1) çifti, bunun tekrar tekrar güvenle çağrılmasını sağlayan şeydir: clip region DC durum yığınına itilir, sayfa çizilir ve orijinal clip, render nasıl çıkarsa çıksın geri alınır. RestoreDC(TargetDC, -1), en son kaydedilmiş durumu geri yükler; dengeli save/restore için standart deyim budur. Restore'u atlarsanız, aynı DC'yi daha sonraki tam sayfa render için yeniden kullanan çağıran, gizemli biçimde son bölgeye kırpılmış çıktıyla karşılaşır. Ölü parametreyi düzeltmek RenderPageRegionToFile yöntemini de bedavaya düzeltti; çünkü yeni yöntem tam olarak bu yol üzerinden gider

İçselleştirilmesi gereken davranış noktası şudur: clip kırpar, ölçeklemez. Sayfa hâlâ istediğiniz DPI ile normal konumunda rasterize edilir; clip region yalnızca dikdörtgen dışındaki her şeyi atar. Bölgeyi çıktıyı dolduracak şekilde yakınlaştırmıyorsunuz; tam çözünürlüklü render'dan pencere kesiyorsunuz. Büyütülmüş bölge istiyorsanız DPI'ı yükseltin. Dikdörtgen koordinatları, point'ten piksele ölçekleme sonrasında cihaz uzayında yorumlanır; render yüzeyinin sol üst köşesinden ölçülür. Bu yüzden Left ve Top değerlerini sayfanın tepesinden aşağıya doğru planlayın. PDF Library for Delphi'ın device context'i ekrandaki çıktı için nasıl sürdüğüne dair daha derin tur isterseniz, yazdırma önizleme ve device-context çıktısı üzerine eşlikçi yazı aynı DC tesisatını görüntü tarafında anlatır

Dürüst sınır: 1-bit BMP, G4 TIFF değil

Bunu "faks hazır çıktı" diye abartmak kolay olurdu; bu yüzden sınırı açıkça koyalım. RenderPageToMonochromeFile, bir pf1bit BMP üretir. Gerçek faks iş akışının veya TIFF arşivinin tipik olarak beklediği biçim olan CCITT Group 4 TIFF üretmez. Bunun nedeni somuttur, gözden kaçma değildir: PDF Library for Delphi'ın CCITT birimi şu anda G4 stream'lerini decode eder ama G4 encoder içermez. Encoder olmayınca sıkıştırılmış monokrom run'ları yazacak yer yoktur; dolayısıyla monokrom yol sıkıştırılmamış 1-bit DIB'de biter

Pratikte bu yine de faydalıdır. 1-bit BMP doğru piksel biçimidir; dither edilmiştir ve hazırdır. Çoğu faks, arşiv veya OCR hattı bunu memnuniyetle içeri alır ya da tek bir aşağı akış adımıyla kendisi G4'e çevirir. Ama gereksiniminiz gerçekten kütüphaneden doğrudan Group 4 TIFF ise, henüz bu o değildir ve kendi sıkıştırma aşamanızı planlamalısınız. Özelliğin ne yaptığını bilmek kadar, nerede bittiğini bilmek de değerlidir

Her iki yöntem de bilinçli olarak küçüktür ve bu sayfadan alınması gereken tasarım dersi budur: renderer üstüne oturan convenience API, rasterizer'a girmeden ve diğer tüm çağıranları kararsızlaştırmadan gerçek yetenek ekleyebilir; monokrom çıktı ve bölge kırpma gibi. Alttaki rasterization için rendering motorları arasında seçim yapmanız gerektiğinde, Delphi'de çok motorlu PDF rendering genel bakışı ödünleşimleri derinlemesine ele alır. Tam rendering yüzeyi ve API'nin geri kalanı için PDF Library for Delphi Delphi PDF Library ürün sayfası bütün resmi sunar