İlk band bütün çizimi tek bir şerit içine sıkıştırdı, ardından gelen beş band boş döndü. Eski banded export böyleydi; PDFiumPas bunu v3.66.0'da düzeltti: RenderPageBanded artık her band'de FPDF_RenderPageBitmap'e tam sayfa hedef genişliğini ve yüksekliğini, negatif dikey ofsetle birlikte geçiriyor; böylece native clip yalnızca geçerli band'in satırlarını yazar, sayfa da full-page coordinate geometry'sini korur. Bütün bunların arkasındaki kullanım sıkıcı ama kaçınılmazdır. Birisi size E-size bir plot veya stitch edilmiş bir panorama sayfası verir ve 600 DPI raster ister. 600 DPI'da ISO A0 sayfası 19866 x 28086 pikseldir ve 32 bitlik destination bitmap'inin contiguous belleği 2 GB'ın biraz üzerindedir. 32 bit Delphi'de bu allocation doğrudan başarısız olur. 64 bit'te yeterince sık başarılı olur ki sorun test sorunu değil müşteri sorunu haline gelsin. Banded rendering peak allocation'ın bir page değil tek strip olması için vardır
Her band neden bütün sayfayı içeriyordu?
Eski kod PDFium page-rendering çağrısında iki farklı argument çiftini birbirine karıştırıyordu. FPDF_RenderPageBitmap, start_x, start_y, size_x ve size_y alır; size çifti bütün sayfanın ne kadar büyük ölçekleneceğini, start çifti ise bu ölçeklenmiş sayfanın destination bitmap içinde nereye yerleşeceğini söyler. v3.66.0 öncesindeki band loop, library RenderPage helper'ına band top'ını destination offset, band height'ını ise page height olarak veriyordu. Bu iki sayı native call'a aynen gitti; dolayısıyla PDFium bütün sayfayı yalnızca BandHeight satır yüksekliğinde bir rectangle'a ölçekledi ve sonra onu bitmap içinde y = BandTop konumuna çizdi; bitmap'in kendisi yalnızca BandHeight satır yüksekliğindeydi. Sonuç, görünce tahmin edeceğiniz şeydi. Band zero bütün sayfayı dikey olarak band yüksekliğine sıkıştırılmış halde aldı. Sonraki her band aynı sıkıştırılmış sayfayı bitmap'in altına itilmiş olarak aldı ve background fill döndürdü. Bug, smoke test'lerin kullandığı özel durumda gizlenir: render yüksekliği band yüksekliğinden küçük bir sayfa tek band'e sığar ve yanlış geometry doğru olanla çakışır. Bir band'den daha uzun her şey onu hemen ortaya çıkarır
Negatif ofset neyi garanti eder?
Düzeltilmiş implementation her band'i, farkı zaten anlamış component'teki tek yer olan RenderTile üzerinden yönlendirir. RenderTile, full-page pixel coordinates içinde tile origin'i ile ayrı PageWidth ve PageHeight alır; PDFium'a page size'ı değiştirmeden -Left ve -Top geçirir. Ofseti negatife çevirmek, istenen band destination bitmap'in 0. satırına gelene kadar full-size sayfayı yukarı kaydırır; ardından PDFium bitmap bounds'a karşı native clip yapar ve band dışındaki hiçbir şey rasterize edilmez. ISO 32000-1 clause 8.3.2'de açıklanan page-to-device mapping ilk band'den son banda kadar aynı kalır; bütün amaç budur: band N, aynı boyutlardaki tek full-page render'ın BandTop ile BandTop + h arasındaki satırlarıyla bit özdeştir ve regresyon paketi bunu RenderPage çıktısına karşı piksel piksel doğrular
// Elle tek band. Destination bitmap yalnızca BandHeight kadar yüksek,
// ancak page target size full Width x Height olarak kalır
Band := Pdf.RenderTile(0, BandTop, // page pixel'larında tile origin
Width, BandHeight, // destination bitmap boyutu
Width, Height); // tam sayfa hedef boyutu
try
// Band artık sayfanın BandTop .. BandTop + BandHeight - 1 satırlarını taşır
finally
Band.Free;
end;
Public band API bir callback loop'tur. RenderPageBanded(Width, Height, BandHeight, BandCallback, Rotation, Options, Color), gerçekte render ettiği band sayısını döndürür veya argument'ler reddedilirse 0 döndürür ve bütün geçiş boyunca component render lock'unu tutar. Callback signature TPdfBandCallback = function(BandIndex, BandTopY: Integer; Bitmap: TBitmap): Boolean of object'tir. Bitmap pf32bit, Width piksel genişliğinde ve BandHeight'tan uzun olmayan bir bitmap'tir; handler'ınız döndüğü anda serbest bırakılır, bu nedenle tutmak istediklerinizi kopyalayın. False döndürmek geçişi geçerli band'den sonra durdurur; bu, Delphi'de cancellable progressive PDF rendering için kullanılan aynı cooperative cancellation modelidir, yalnızca PDFium continuation granularity yerine strip granularity düzeyinde
type
TBandSink = class
private
FCancelled: Boolean;
FRows: Integer;
public
function HandleBand(BandIndex, BandTopY: Integer;
Bitmap: TBitmap): Boolean;
property Rows: Integer read FRows;
end;
function TBandSink.HandleBand(BandIndex, BandTopY: Integer;
Bitmap: TBitmap): Boolean;
begin
// Bu metod döndüğünde Bitmap yok olur - burada tüket
Inc(FRows, Bitmap.Height);
Result := not FCancelled;
end;
// ...
Pdf.PageNumber := 1;
Bands := Pdf.RenderPageBanded(19866, 28086, 256, Sink.HandleBand);
Full-page bitmap olmadan PNG ve TIFF stream etmek
Band'lerle render etmek ancak encoder da sequential ise yararlıdır; bu yüzden v3.66.0, PNG veya TIFF'i doğrudan çağıran stream'ine yazan RenderPageBandedToStream'i ekledi. TPdfBandedImageStreamOptions.Default, 256 satır band yüksekliği, PNG compression level 6 ve 0 MaxOutputBytes ile başlar; 0 sınırsız demektir. Dönen TPdfBandedImageReport, Format, Width, Height, BandsRendered, BandsEncoded, RowsEncoded, PeakBandBytes, OutputBytes ve Completed taşır. İş planlarken gerçekten önemseyeceğiniz sayı PeakBandBytes'tır: Width * BandHeight * 4 değeridir, yani yukarıdaki A0 sayfasında page buffer'ın 2 GB'ı yerine yaklaşık 19 MB band buffer tepe yapar
PNG encoder bilerek dardır. Sabit RGB8 üretir; bit depth 8 ve color type 2 ile bir IHDR yazar, sonra her scanline'ı filter type 0 (ISO/IEC 15948 filter method 0, filter type None) ile kurar ve platform zlib compression stream'inden geçirir. Sıkıştırılmış baytlar CRC taşıyan IDAT chunk'ları olarak sırayla çıkar. İlginç kısıt deflate layer'ın altındaki stream'dedir: compression stream istediği için position query'lerine cevap verir ama gerçek bir seek denemesi hata yükseltir. Bu kasıtlıdır. IDAT chunk ve CRC wire üzerine çıktıktan sonra onu düzeltmeye dönüş yoktur; sessiz bir seek yapısal olarak hâlâ geçerli görünen ama bozuk output üretir
TIFF encoder little-endian classic TIFF yazar: II byte order mark'ı, ardından magic 42 ve band başına bir strip. Piksel önce stream edilir, on entry'li IFD ise strip offset ve byte count değerleri bilindiğinde sonda üretilir. Compression tag 259 value 1'dir; yani hiç entropy coding yoktur: payload tam olarak Width * Height * 3 bayttır, PhotometricInterpretation RGB, PlanarConfiguration chunky'dir ve RowsPerStrip band yüksekliğini kaydeder; son kısa strip kendi StripByteCounts entry'siyle tanımlanır. Bu yüzden band yüksekliği peak memory ve strip count'u değiştirir, output size'ı değil; tuning yapmadan önce bunu bilmek değerlidir. Kayıpsız yerine küçük dosya istiyorsanız PDFium VCL component ile PDF sayfalarını JPEG görüntülerine dönüştürme sayfa başına yol daha iyi araçtır
var
StreamOptions: TPdfBandedImageStreamOptions;
Report: TPdfBandedImageReport;
Output: TFileStream;
begin
StreamOptions := TPdfBandedImageStreamOptions.Default(pbifPng);
StreamOptions.BandHeight := 512;
StreamOptions.CompressionLevel := 6;
StreamOptions.MaxOutputBytes := Int64(256) * 1024 * 1024;
Output := TFileStream.Create('sheet-a0-600dpi.png', fmCreate);
try
Report := Pdf.RenderPageBandedToStream(Output, 19866, 28086,
StreamOptions);
finally
Output.Free;
end;
if not Report.Completed then
raise Exception.Create('Banded export stopped before the last row');
// Report.PeakBandBytes = 19866 * 512 * 4, 19866 * 28086 * 4 değil
end;
Banded export nerede durur?
Output'u iki tavan sınırlar ve bunlar bilerek farklı yerlerde başarısız olur. Birincisi çağıranın budget'ıdır: MaxOutputBytes, sınırı aşacak herhangi bir write'dan önce EPdfError yükselten bounded write stream tarafından uygulanır; bu yüzden budget sonradan raporlanan bir sayı değil hard cap'tir. İkincisi yapısaldır. Classic TIFF strip offset'lerini 32 bitlik değerler olarak saklar; bu nedenle BeginImage, Width * Height * 3 ile header ve directory toplamını bu tavana karşı doğrular ve tek piksel yazılmadan işi reddeder. Aynı kontrol baştan MaxOutputBytes'a karşı da çalışır; kendi pixel payload'ını karşılayamayan TIFF'i başlatmaya değmez. PNG'de eşdeğer bir sınır yoktur, çünkü IDAT chunk'ları bütünüyle sequential'dir ve taşacak 32 bitlik offset table bulunmaz
Durdurulmuş bir export'un arkasında ne bıraktığı konusunda gözünüz açık olsun. Geçiş son satıra ulaşmazsa Completed stays False ve encoder EndImage(False) ile sökülür; bu, ne PNG IEND chunk'ının ne de TIFF IFD'nin yazılmasını bilerek sağlar. Bu nedenle partial file geçersizdir ve her decoder bunu söyler; eksik satırlara rağmen makul görünen bir görüntü üretmez. Bu cleanup, EndImage içindeki ikincil bir hatanın özgün exception'ın yerini alamaması için sarılır; gerçek nedeni adlandıran stack trace ile temizlik görevlisini adlandıran stack trace arasındaki fark budur. Kalıcı progress gerekiyorsa callback'iniz içinde band başına checkpoint alın; PDFium Delphi render cache ve zoom kılavuzundaki strip-level caching taktikleri burada da geçerlidir
Kendi codec'inizi bağlamak
Hedef PNG veya TIFF değilse RenderPageBandedToEncoder bir TPdfBandedImageEncoder descendant'ı alır ve aynı loop'u sürer. Yaşam döngüsü açık ve kısadır: BeginImage(Width, Height), sonra her strip için kesin olarak artan sırada bir kez WriteBand(BandIndex, BandTopY, Bitmap), ardından Report.OutputBytes'ı besleyen EndImage(Completed) ve GetBytesWritten. Built-in encoder'lar out-of-order band'i buffer'lamaya çalışmak yerine doğrudan reddeder; yazdığınız encoder da aynısını yapmalıdır, çünkü strip'leri sessizce yeniden sıralayan bir codec açılan ama yalan söyleyen bir dosya üretir. JPEG 2000 tile'ları, her seferinde bir MCU row band ile beslenen JPEG writer veya print spooler'a doğrudan feed için kullanacağınız seam budur
type
TCodecBandEncoder = class(TPdfBandedImageEncoder)
private
FNextBand: Integer;
FWritten: Int64;
public
procedure BeginImage(Width, Height: Integer); override;
function WriteBand(BandIndex, BandTopY: Integer;
Bitmap: TBitmap): Boolean; override;
procedure EndImage(Completed: Boolean); override;
function GetBytesWritten: Int64; override;
end;
function TCodecBandEncoder.WriteBand(BandIndex, BandTopY: Integer;
Bitmap: TBitmap): Boolean;
begin
if BandIndex <> FNextBand then
raise EPdfError.Create('Bands must arrive in order');
Bitmap.PixelFormat := pf32bit;
// Bitmap.ScanLine[0 .. Bitmap.Height - 1] değerlerini burada codec'e besle
Inc(FNextBand);
Result := True;
end;
Bilinmeye değer tek cross-compiler tuzağı
zlib unit'i desteklenen her toolchain'de farklı yazılır: Delphi XE5 ve sonrası System.ZLib, FPC zstream, eski Delphi ise düz ZLib kullanır. Bu kadar conditional compilation rutindir. Tuzak, üçünün de graphics unit'teki aynı adlı TColor üyeleriyle kafa kafaya çakışan clNone ve clDefault adındaki compression-level constant'larını dışa aktarmasıdır. zlib unit implementation uses clause'a girdiğinde render code içindeki niteliksiz clNone diagnostic vermeden color yerine compression level olarak çözülebilir. PDFiumPas bunu, fully qualified graphics constant'larına bir kez bağlanan ve render background veya color-scheme sentinel karşılaştırmalarında her yerde kullanılan açık color sentinel alias'ları PdfGraphicsColorNone ve PdfGraphicsColorDefault ile sabitler. Üç satır kod ve symbol resolution compiler'lar arasında kaymayı bırakır
Banded rendering, RAM'e sığmayacak sayfayla karşılaşana kadar bir convenience feature gibi görünür; o zaman çalışan tek yol olur. Düzeltilmiş band geometry, sequential PNG ve TIFF encoder'ları ve custom encoder seam, Delphi, Lazarus ve C++Builder arasında çalışan full band-versus-page pixel comparison regresyon paketiyle birlikte PDFium Delphi component'in parçasıdır