HotPDF เปิดเคอร์เนลลดขนาดรูปภาพสามแบบผ่าน property ImageDownsampleKernel และเปิด pass dithering แบบ Floyd-Steinberg แยกต่างหากผ่าน RenderOutputDither ตัวแรกคุมว่ารูปถ่ายหน้าตาเป็นอย่างไรหลังคุณย่อลงให้เข้างบประมาณขนาดไฟล์ ตัวหลังคุมว่ารูปหน้าตาเป็นอย่างไรหลังหน้ากระดาษถูกยุบเป็นขาวดำ ทั้งสองตัวปิดอยู่เป็นค่าเริ่มต้น และเป็น opt-in ด้วยเหตุผลเดียวกัน มันแลกเวลาจริง ๆ มาให้
แรงกดดันที่พาคนมาถึงจุดนี้คุ้นเคยดี สัญญาที่สแกนมา 60 MB ต้องออกผ่านเกตเวย์อีเมลที่ปฏิเสธทุกอย่างเกิน 10 MB หรือชุดใบแจ้งยอดต้องไปลงบนอุปกรณ์ขาวดำสไตล์แฟกซ์ที่เรนเดอร์พิกเซลเทาเป็นกระดาษหรือหมึกเท่านั้น ทั้งสองปัญหาคือปัญหา resampling และทั้งสองมีคำตอบเร็วที่หน้าตาแย่กับคำตอบช้าที่หน้าตาถูก
เคอร์เนลสามแบบต่างกันจริงตรงไหน
THPDFResampleKernel มีสามค่า และมันนั่งอยู่บนจุดที่ต่างกันจริงของเส้นความเร็วต่อคุณภาพ rkHalftone ส่งงานต่อให้เส้นทาง GDI คลาสสิก StretchBlt พร้อมโหมด HALFTONE ซึ่งแม้ใช้ชื่อนี้แต่เป็นการกรองระดับ bilinear: เร็ว เพียงพอกับ line art กับ screenshot และชอบมีขอบหยัก ๆ ที่คุณจำได้ทันทีจากรูปถ่ายที่ถูกย่อ rkBicubic รันเคอร์เนล Catmull-Rom แบบแยกแกน ส่วน rkLanczos3 รัน windowed sinc แบบแยกแกนที่มี support สามกลีบ
เคอร์เนลแบบแยกแกนทั้งคู่รันเป็นสอง pass แนวนอนก่อนแนวตั้ง ใช้ 6 ถึง 12 taps ต่อพิกเซลปลายทางใน Pascal ล้วน ๆ ซึ่งช้ากว่าเส้นทาง GDI ประมาณหนึ่งลำดับขนาด และนั่นคือเหตุผลเป๊ะ ๆ ที่ rkHalftone ยังคงเป็นค่าเริ่มต้น บน batch กลางคืนที่มีหน้ากระดาษเป็นพัน ความต่างคือการตัดสินใจเรื่องตารางงาน ไม่ใช่เรื่องรสนิยม แต่บนเอกสารเดียวที่ผู้ใช้กำลังรออยู่ Lanczos3 เกือบฟรีและดีกว่าจนตาเห็น
มีสองคุณสมบัติการ implement ที่ควรรู้เพราะมันกำหนดว่า output ทำอะไรได้และทำไม่ได้ ขอบของภาพถูก clamp ด้วยการ replicate ขอบ ไม่ใช่การวนรอบหรือเฟด และค่าน้ำหนักถูก normalize ต่อหนึ่งพิกเซลปลายทาง สองอย่างรวมกันแปลว่าผลลัพธ์ไม่มีวัน ringing ต่ำกว่าดำหรือสูงกว่าขาว วง halo overshoot แบบคลาสสิกของ Lanczos รอบขอบแหลมจึงไม่โผล่มาเป็น artifact ที่ถูกตัดช่วงในภาพที่เข้ารหัสแล้ว
var
Pdf: THotPDF;
Info: THPDFLoadedResourceOptimizationInfo;
Changed: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('scanned-contract.pdf');
Pdf.ImageDownsampleKernel := rkLanczos3; // ตั้งก่อนเรียกเมธอด
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;
อาร์กิวเมนต์ MinimumSavingsBytes ที่เป็น 4096 ในโค้ดข้างบนคือกันชนที่คอยกุมให้ operation นี้ซื่อสัตย์ การ encode รูปที่ถูกบีบอัดอย่างมีประสิทธิภาพอยู่แล้วใหม่สามารถได้สตรีมที่ใหญ่กว่าเดิม และตัวลดขนาดที่แทนที่ทุกรูปแบบเชื่องโง่จะมีโอกาสทำให้ไฟล์ที่ถูกขอให้ย่อกลับอ้วนขึ้นแทน ค่า threshold บอกว่า: ยอมรับการแทนที่เมื่อมันประหยัดได้อย่างน้อยเท่านี้ไบต์เท่านั้น PreservedCalibratedImageCount รายงานการตัดสินใจอนุรักษ์นิยมอีกฝั่ง คือรูปที่ถูกปล่อยไว้ไม่แตะ เพราะมันพก color space แบบ calibrated ที่การ resample จะไปทำลาย
ทำไมสัมประสิทธิ์พอลิโนเมียลผิดตัวถึงหายากถึงขั้นสุด
เพราะเคอร์เนล interpolation ที่พังไม่ crash ไม่ throw มันแค่ผลิตภาพที่หน้าตาเพี้ยนเบา ๆ แบบที่ไม่มีใครโยนความผิดให้ต้นเหตุได้ เคอร์เนล Catmull-Rom เป็น piecewise cubic และสายนอกของมันในรูป Horner ซ้อนคือ ((-0.5t + 2.5)t - 4)t + 2 พิมพ์สัมประสิทธิ์ตรงกลางเป็น -5 แทน -4 ฟังก์ชันยังคำนวณได้ ยังคืนตัวเลขในช่วงที่ดูสมเหตุสมผล และยังผลิตภาพออกมาเฉย ๆ
ความเสียหายโผล่มาในรูปที่ W(1) คำนวณได้ -1 ทั้งที่ต้องเป็น 0 ค่าน้ำหนักติดลบสะสมขึ้น ผลรวมถูก clip ที่ศูนย์ และอาการที่มองเห็นคือเกรเดียนต์ที่ปลายซ้ายดำคลึ้งกับขอบชนที่เสียโทนกลางไป ไม่มีอะไรในความล้มเหลวชี้มาที่พอลิโนเมียลเลย การเช็กที่จับมันได้ในไม่กี่วินาทีเป็นเรื่องเลขคณิต ไม่ใช่เรื่องสายตา เคอร์เนล interpolation ต้องผ่าน W(0) = 1 และ W(±1) = W(±2) = 0 และเคอร์เนลใดก็ตามที่พลาดสามจุดนี้คือมีสัมประสิทธิ์ผิด จบเรื่อง Assert สามค่านี้ใน unit test แล้วบั๊กพิมพ์ผิดทั้งชนิดนี้หายไปจากชีวิต
dithering แบบ Floyd-Steinberg และตำแหน่งที่มันควรอยู่ใน pipeline
pass dithering เป็นปัญหาคนละเรื่องกับ resampling และอยู่ต่างจุดกันใน pipeline RenderOutputDither ใช้การกระจายความผิดพลาดแบบ Floyd-Steinberg หลังการประกอบหน้าเสร็จ ซึ่งเป็นตำแหน่งเดียวที่มีความหมายสำหรับพรีวิวพิมพ์ขาวดำหรือ export สไตล์แฟกซ์: operation นี้เป็นเรื่องการยุบ raster ที่เสร็จแล้วให้เหลือหนึ่งบิตต่อพิกเซล ไม่ใช่เรื่องว่ารูปแต่ละรูปถูกย่อมาอย่างไรตอนเข้าสู่หน้า
ตัวอัลกอริทึมเองสั้น ความสว่างถูกตัดที่ 50 เปอร์เซ็นต์ และความผิดพลาดจากการ quantize ถูกกระจายไปเพื่อนบ้านสี่ทิศด้วยค่าน้ำหนักคลาสสิก 7/16, 3/16, 5/16 และ 1/16 ตามลำดับ ไปทางขวา ล่างซ้าย ล่างตรง และล่างขวา พิกเซล output เป็น 0 หรือ 255 ทุกแชนเนล ทางเลือกแบบขี้เกียจที่ให้กับคุณแทน คือ threshold แข็ง ๆ ไม่กระจายอะไรเลย จะเปลี่ยนรูปถ่ายเป็นเงาตัดและเผาโทนกลางที่พกเนื้อหาทิ้งหมด
// เติม dither ตอนเรนเดอร์สำหรับอุปกรณ์พรีวิวขาวดำ
Pdf.RenderOutputDither := True;
// หรือเอา pass เดียวกันไปใช้กับ bitmap ที่คุณมีอยู่แล้ว bitmap ต้อง
// เป็น pf24bit ฟังก์ชันคืน False แทนการเดา
if not HPDFFloydSteinbergDitherBitmap(Preview) then
raise Exception.Create('dither expects a 24-bit bitmap');
// เข้าถึงเคอร์เนลตรง ๆ เมื่อ resample นอก pipeline ของเอกสาร
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
Small.SaveToFile('thumb.bmp');
finally
Small.Free;
end;
มีรายละเอียด implementation หนึ่งใน error diffusion ที่กัดทุกคนรอบเดียว buffer ความผิดพลาดระหว่างแถวต้องสะสม พิกเซลแต่ละตัวในแถวถัดไปได้รับส่วนแบ่งจากสามพิกเซลที่ต่างกันในแถวปัจจุบัน คือ taps 3/16, 5/16 และ 1/16 ถ้าโค้ด assign แทนที่จะบวก การเขียนแต่ละครั้งจะทิ้งส่วนแบ่งเก่าทิ้ง เหลือ tap สุดท้ายตัวเดียวรอด ภาพยังดูมี dither อยู่ ซึ่งเป็นเหตุผลที่มันจับยาก แต่ texture ผิดและการถ่ายทอดโทนเหลื่อมออกไป เทสต์ที่จับมันได้เป็นแบบเชิงปริมาณ: dither พื้นเทากลางให้สม่ำเสมอ แล้วบังคับให้ความครอบคลุมของส่วนภายในตกอยู่ระหว่าง 40 ถึง 60 เปอร์เซ็นต์
pipeline ลดขนาดไฟล์ควรใช้ชุดไหน
จับคู่เคอร์เนลให้ตรงกับธรรมชาติของรูป แล้วถือว่า dithering เป็นเรื่องของอุปกรณ์ ไม่ใช่เรื่องของการบีบอัด สำหรับสแกนที่เป็นรูปถ่ายและต้องอยู่ใต้งบขนาดไฟล์ rkLanczos3 ที่ 150 หรือ 200 DPI เก็บรายละเอียดที่คนสังเกตเห็นไว้ได้ ขณะที่ตัดจำนวนพิกเซลลงสี่เท่าหรือมากกว่า สำหรับ screenshot แผนภาพและ line art rkHalftone ใช้ได้จริงและเร็วกว่ามาก เพราะรูปพวกนี้มีเกรเดียนต์โทนให้รักษาไม่กี่จุด ส่วน batch ผสมที่คุณไม่มีโอกาสไล่ดูทีละรูป rkBicubic คือจุดกลางที่เหมาะสม ดีกว่า bilinear และใช้ taps ราวครึ่งหนึ่งของ Lanczos3
การลดขนาดเป็นคานโยกหนึ่งอันในหลายอัน และไม่เสมอไปที่จะเป็นอันใหญ่สุด สแกน bilevel มักตอบสนองต่อ encoder ที่เล่าไว้ใน การบีบอัด JBIG2 bilevel แบบเนทีฟใน Delphi ดีกว่ามาก ที่ซึ่งชัยชนะมาจาก symbol dictionary ไม่ใช่จำนวนพิกเซล ก่อนตัดสินใจ การรู้ว่าไฟล์มีอะไรอยู่ข้างในจริงช่วยได้ ซึ่งเป็นประโยชน์ของ การดึงรูปออกมาพร้อม decode filter ของมัน: รายชื่อ image object กับการบีบอัดเดิมจะบอกคุณว่า resampling มีอะไรจะได้ไหม
ถ้าคุณกำลังสร้างพื้นที่พรีวิวที่แสดงผลลัพธ์ เส้นทางเรนเดอร์ชุดเดียวกับที่เขียนไว้ใน การเรนเดอร์หน้า PDF เป็น bitmap คือจุดที่ RenderOutputDither ออกฤทธิ์ พรีวิวที่มี dither กับ output ที่มี dither จึงมาจาก code path เดียว ไม่ใช่สอง implementation ที่จะเหลื่อมกันในวันหน้า
หลักการกว้าง ๆ ที่อยู่หลังทั้งสองฟีเจอร์คือการตั้งค่าด้านคุณภาพควรชัดเจนและย้อนกลับได้ HotPDF รักษาพฤติกรรมเดิมไว้เป็นค่าเริ่มต้น แอปที่มีอยู่จึงอัปเกรดโดยไม่เจอ output หรือเวลาที่เปลี่ยนไปโดยไม่บอกกล่าว แล้ววางเส้นทางที่สวยกว่าแต่ช้ากว่าไว้ห่างจากมือแค่การ assign property หนึ่งครั้ง ทั้งสองเป็นส่วนหนึ่งของ HotPDF Delphi PDF component ควบคู่กับเครื่องจักร optimization ทรัพยากรและการเรนเดอร์ที่พวกมันสร้างอยู่บน