HotPDF mở ba kernel downsampling ảnh qua property ImageDownsampleKernel và một pass Floyd-Steinberg riêng qua RenderOutputDither. Cái đầu quyết định ảnh trông ra sao sau khi bạn thu nhỏ để chạm một giới hạn dung lượng, cái sau quyết định chúng trông ra sao sau khi page bị giảm về đen trắng. Không cái nào bật sẵn, và cả hai đều là opt-in vì cùng một lý do: chúng trả giá bằng thời gian thật
Áp lực đẩy người ta tới đây thì quen thuộc. Một hợp đồng scan 60 MB phải đi qua một email gateway từ chối mọi thứ trên 10 MB, hoặc cả lô statement phải đáp xuống một thiết bị mono kiểu fax mà render mọi pixel xám thành giấy hoặc mực. Cả hai đều là bài toán resampling, và cả hai đều có một câu trả lời nhanh mà xấu và một câu trả lời chậm mà đúng
Ba kernel thực sự khác nhau ở điểm nào
THPDFResampleKernel có ba giá trị, và chúng nằm ở những điểm khác biệt thật sự trên đường cong speed–quality. rkHalftone ủy thác cho đường GDI StretchBlt lịch sử với mode HALFTONE, mà dù mang cái tên đó vẫn là filtering hạng bilinear: nhanh, đủ dùng cho line art và screenshot, và hay cho ra những cạnh giòn tan bạn nhìn ảnh thu nhỏ là nhận ra ngay. rkBicubic chạy một kernel Catmull-Rom separable, còn rkLanczos3 chạy một windowed sinc separable với support ba lobe
Cả hai kernel separable chạy thành hai pass, ngang rồi dọc, với 6 tới 12 taps cho mỗi destination pixel bằng Pascal thuần. Chậm hơn đường GDI cỡ một bậc độ lớn, và đó chính là lý do rkHalftone giữ vị trí default. Với một batch đêm hàng nghìn page, sự khác biệt là một quyết định xếp lịch chứ không phải sở thích. Với một document đơn lẻ mà người dùng đang chờ, Lanczos3 gần như miễn phí và tốt hơn thấy rõ
Hai tính chất triển khai đáng nhớ vì chúng quyết định output làm được gì và không làm được gì. Border clamp bằng edge replication chứ không wrap hay mờ dần, và weight được normalize theo từng destination pixel. Hai điều đó cộng lại nghĩa là kết quả không bao giờ ringing xuống dưới đen hay lên trên trắng, nên cái halo overshoot Lanczos kinh điển quanh một cạnh cứng không xuất hiện thành artifact bị cắt trong ảnh đã encode
var
Pdf: THotPDF;
Info: THPDFLoadedResourceOptimizationInfo;
Changed: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('scanned-contract.pdf');
Pdf.ImageDownsampleKernel := rkLanczos3; // set trước khi gọi
Changed := Pdf.DownsampleLoadedImages(150, 82, 4096, Info);
if Changed > 0 then
begin
Writeln('resampled images: ', Info.DownsampledImageCount);
Writeln('kept calibrated : ', Info.PreservedCalibratedImageCount);
Pdf.SaveToFile('scanned-contract-150dpi.pdf');
end;
finally
Pdf.Free;
end;
end;
Tham số MinimumSavingsBytes — 4096 ở trên — là cái chốt giữ cho phép toán trung thực. Encode lại một ảnh vốn đã được nén hiệu quả có thể cho ra stream to hơn bản gốc, và một downsampler thay mù quáng mọi ảnh sẽ thỉnh thoảng làm phình đúng cái file nó được nhờ thu nhỏ. Ngưỡng này nói rằng: chỉ commit phép thay thế khi nó tiết kiệm được ít nhất bấy nhiêu byte. PreservedCalibratedImageCount báo quyết định bảo thủ còn lại: những ảnh được bỏ yên vì mang một calibrated color space mà resampling sẽ làm tổn hại
Vì sao một hệ số polynomial sai lại khó phát hiện đến thế?
Vì một interpolation kernel hỏng không crash cũng không throw, nó chỉ cho ra một ảnh sai một cách tinh vi theo cách chẳng ai quy trách nhiệm được. Kernel Catmull-Rom là piecewise cubic, và nhánh ngoài của nó ở dạng Horner lồng là ((-0.5t + 2.5)t - 4)t + 2. Viết hệ số giữa thành -5 thay vì -4 và hàm vẫn tính được, vẫn trả về những con số trong khoảng hợp lý, và vẫn tạo ra một tấm ảnh
Tổn hại lộ diện ở chỗ W(1) tính ra -1 trong khi lẽ ra phải là 0. Weight âm cộng dồn, tổng bị clip về 0, và triệu chứng nhìn thấy được là một gradient có đầu trái đen thui và một step edge mất các tone trung gian. Không gì trong cái failure đó chỉ tay vào một polynomial. Phép check bắt được nó trong vài giây là arithmetic chứ không phải thị giác: một interpolating kernel phải thỏa W(0) = 1 và W(±1) = W(±2) = 0, và bất kỳ kernel nào trượt cả ba điểm đó là có lỗi hệ số, chấm hết. Assert trên ba giá trị đó trong một unit test và cả lớp lỗi typo biến mất
Dithering Floyd-Steinberg, và nó nằm ở đâu trong pipeline
Pass dither là một bài toán khác với resampling và nằm ở một điểm khác trong pipeline. RenderOutputDither áp error diffusion Floyd-Steinberg sau khi compose page xong, và đó là vị trí duy nhất hợp lý cho một print preview monochrome hay một export kiểu fax: phép toán này là về việc giảm một raster đã hoàn thành xuống một bit mỗi pixel, chứ không phải về cách từng ảnh đã được scale lúc vào
Thuật toán thì ngắn. Luminance được threshold ở 50 phần trăm, và quantization error được diffusion sang bốn hàng xóm với các weight kinh điển 7/16, 3/16, 5/16 và 1/16, lần lượt sang phải, dưới-trái, dưới và dưới-phải. Output pixel là 0 hoặc 255 ở mọi channel. Phương án ngây thơ thay thế cho bạn cái gì — một hard threshold không diffusion — biến một tấm ảnh thành bóng đen và đánh mất mọi mid-tone đang mang content
// Dithering lúc render cho một thiết bị preview monochrome
Pdf.RenderOutputDither := True;
// Hoặc áp cùng pass đó lên một bitmap bạn đã có sẵn. Bitmap phải là
// pf24bit; hàm trả False thay vì đoán mò
if not HPDFFloydSteinbergDitherBitmap(Preview) then
raise Exception.Create('dither expects a 24-bit bitmap');
// Truy cập kernel trực tiếp khi bạn resample ngoài document pipeline
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
Small.SaveToFile('thumb.bmp');
finally
Small.Free;
end;
Có đúng một chi tiết triển khai trong error diffusion mà ai cũng bị cắn một lần. Error buffer giữa các row phải tích lũy. Mỗi pixel ở row kế nhận đóng góp từ ba pixel khác nhau ở row hiện tại — các tap 3/16, 5/16 và 1/16 — và nếu code assign thay vì cộng, mỗi lần ghi vứt bỏ đóng góp trước đó và chỉ tap cuối sống sót. Ảnh vẫn trông có dither, chính vì thế mà khó nhận ra, nhưng texture sai và khả năng tái tạo tone trôi dạt. Test bắt được nó là định lượng: dither một vùng xám giữa đồng nhất và yêu cầu coverage phần lòng nằm giữa 40 và 60 phần trăm
Pipeline giảm dung lượng nên dùng tổ hợp nào?
Hãy khớp kernel với ảnh thực sự là gì, và coi dithering là chuyện của thiết bị chứ không phải chuyện nén. Với scan chụp ảnh phải sống qua một giới hạn dung lượng, rkLanczos3 ở 150 hay 200 DPI giữ được phần chi tiết người ta để ý trong khi cắt pixel count đi bốn lần hoặc hơn. Với screenshot, diagram và line art, rkHalftone thật sự ổn và nhanh hơn nhiều, vì những ảnh đó có ít tonal gradient phải giữ. Với một batch lẫn lộn mà bạn không thể soi từng ảnh, rkBicubic là điểm giữa hợp lý: hơn bilinear, số tap chỉ bằng khoảng nửa Lanczos3
Downsampling là một trong vài cần gạt, và không phải lúc nào cũng to nhất. Scan bilevel thường ăn tiền hơn hẳn với encoder nói trong nén bilevel JBIG2 native trong Delphi, nơi mối lợi đến từ symbol dictionary chứ không phải số pixel. Trước khi quyết, biết file bên trong thực sự có gì sẽ giúp nhiều, và đó là việc của extract ảnh cùng decode filter của chúng: một bản kê khai image object và compression hiện có cho biết resampling còn gì để gặt
Nếu bạn đang dựng bề mặt preview hiển thị kết quả, cùng đường render được tài liệu hóa trong render một page PDF ra bitmap chính là nơi RenderOutputDither phát huy tác dụng, để preview có dither và output có dither đi ra từ một code path thay vì hai bản triển khai trôi dạt nhau theo thời gian
Nguyên tắc rộng đứng sau cả hai tính năng là các setting chất lượng nên tường minh và đảo ngược được. HotPDF giữ hành vi lịch sử làm default để application hiện có nâng cấp mà không gặp một thay đổi bất ngờ về output hay thời gian, và để những đường đẹp hơn, chậm hơn cách đúng một phép gán property. Cả hai là một phần của HotPDF Delphi PDF component, cạnh phần máy móc resource optimization và rendering mà chúng dựng trên