HotPDF חושף שלושה kernels להקטנת תמונות דרך המאפיין ImageDownsampleKernel ומעבר Floyd-Steinberg נפרד דרך RenderOutputDither. הראשון שולט איך תמונות נראות אחרי שמקטינים אותן כדי לעמוד בתקציב גודל, השני שולט איך הן נראות אחרי שעמוד הופחת לשחור-לבן. אף אחד מהם לא מופעל כברירת מחדל, ושניהם opt-in מאותה סיבה: הם עולים בזמן אמיתי
הלחץ שמוביל אנשים לכאן מוכר. חוזה סרוק של 60 MB חייב לצאת דרך שער דואר שדוחה כל דבר מעל 10 MB, או אצוות מצהירים חייבת להגיע להתקן חד-גוני בסגנון פקס שמעבד כל פיקסל אפור כנייר או כטונר. שני הבעיות הן בעיות resampling, ולשתיהן יש תשובה מהירה שנראית רע ותשובה איטית שנראית נכון
במה שלושת ה-kernels באמת נבדלים
ל-THPDFResampleKernel שלושה ערכים, והם יושבים בנקודות שונות באמת על עקומת המהירות והאיכות. rkHalftone מאציל לנתיב ה-GDI ההיסטורי של StretchBlt עם מצב HALFTONE, שחרף שמו הוא סינון ברמת bilinear: מהיר, מספק ל-line art ולצילומי מסך, ונוטה לקצוות גרגיריים שמזהים מיד על תמונות מוקטנות. rkBicubic מריץ kernel Catmull-Rom נפרד, ו-rkLanczos3 מריץ sinc מוחלון נפרד עם תמיכה של שלושה אונות
שני ה-kernels הנפרדים רצים כשני מעברים, אופקי ואז אנכי, עם 6 עד 12 taps לכל פיקסל יעד בפסקל טהור. זה בערך סדר גודל איטי יותר מהנתיב של GDI, וזו בדיוק הסיבה ש-rkHalftone נשאר ברירת המחדל. באצווה לילית של אלפי עמודים ההפרש הוא החלטת תזמון, לא העדפה. במסמך יחיד שמשתמש מחכה לו, Lanczos3 כמעט חינם וטוב יותר בעין
שני מאפייני מימוש שווה להכיר כי הם קובעים מה הפלט יכול ולא יכול לעשות. הגבולות נחתכים בשכפול קצה ולא בעטיפה או דהייה, והמשקלים מנורמלים לכל פיקסל יעד. יחד שני הדברים האלה אומרים שהתוצאה לעולם לא מצלצלת מתחת לשחור או מעל ללבן, כך שההילה הקלאסית של עודף Lanczos סביב קצה חד לא מופיעה כארטיפקטים חתוכים בתמונה המקודדת
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 למעלה, הוא המגן ששומר על הפעולה כנה. קידוד מחדש של תמונה שכבר נדחסה ביעילות יכול להניב stream גדול מהמקור, ומקטין שמחליף כל תמונה בעיוורון יגדיל לפעמים את הקובץ שהתבקש לצמצם. הסף אומר: מאשרים את ההחלפה רק כשהיא חוסכת לפחות את מספר הבייטים הזה. PreservedCalibratedImageCount מדווח את ההחלטה השמרנית השנייה, תמונות שהושארו ללא מגע כי הן נושאות מרחב צבע מכויל ש-resampling היה מפגע בו
מדוע מקדם פולינום שגוי כה קשה לאתר?
כי kernel אינטרפולציה שבור לא קורס ולא זורק, הוא פשוט מניב תמונה שנראית שגויה בעדינות באופן שאף אחד לא יכול לייחס. ה-kernel של Catmull-Rom הוא שלישוני לחתיכות, והענף החיצוני שלו בצורת Horner מקוננת הוא ((-0.5t + 2.5)t - 4)t + 2. כותבים את המקדם האמצעי כ--5 במקום -4 והפונקציה עדיין מחושבת, עדיין מחזירה מספרים בטווח סביר, ועדיין מייצרת תמונה
הנזק מופיע כ-W(1) שמחושב ל--1 שם שחייב להיות 0. משקלים שליליים מצטברים, הסכום נחתך באפס, והסימפטום הנראה הוא מעבר גוון שקצהו השמאלי מתקרר וקצה מדרגה שמאבד את הגוונים הביניים שלו. שום דבר בכישלון לא מצביע על פולינום. הבדיקה שתופסת את זה בשניות היא אריתמטית ולא ויזואלית: kernel מאנדק חייב לקיים W(0) = 1 ו-W(±1) = W(±2) = 0, וכל kernel שמחמיץ את שלוש הנקודות האלה מסתיר טעות מקדם, נקודה. טוענים על שלושת הערכים האלה בבדיקת יחידה ומעמד הפגמים של הקלדה נעלם כולו
dithering של Floyd-Steinberg, ואיפה שהוא יושב ב-pipeline
מעבר ה-dithering הוא בעיה אחרת מ-resampling ויושב בנקודה אחרת ב-pipeline. RenderOutputDither מחיל הפצת שגיאה של Floyd-Steinberg אחרי ההרכבה של העמוד, וזה המקום היחיד שהגיוני לתצוגה מקדימה של הדפסה חד-גונית או לייצוא בסגנון פקס: הפעולה היא על הפחתת raster מוגמר לסיבית אחת לפיקסל, ולא על איך תמונות בודדות עובדו בכניסה
האלגוריתם עצמו קצר. הבהירות נחתכת ב-50 אחוז, ושגיאת הקוונטיזציה מופצת לארבעה שכנים עם המשקלים הקלאסיים 7/16, 3/16, 5/16 ו-1/16, ימינה, שמאלה-מטה, מטה וימינה-מטה. הפיקסל בפלט הוא 0 או 255 בכל ערוץ. מה שהאלטרנטיבה הנאיבית נותנת במקום, חיתוך חד בלי הפצה, הופך תמונה לצללית ומאבד כל גוון ביניים שנשא את התוכן
// dithering בזמן העיבוד עבור התקן תצוגה מקדימה חד-גוני
Pdf.RenderOutputDither := True;
// או מחילים את אותו מעבר על bitmap שכבר בבעלותך. ה-bitmap חייב
// להיות pf24bit; הפונקציה מחזירה False במקום לנחש
if not HPDFFloydSteinbergDitherBitmap(Preview) then
raise Exception.Create('dither expects a 24-bit bitmap');
// גישה ישירה ל-kernel כשמדגימים מחדש מחוץ ל-pipeline של המסמך
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
Small.SaveToFile('thumb.bmp');
finally
Small.Free;
end;
יש פרט מימוש אחד בהפצת שגיאות שנושך כל אחד פעם אחת. חוצץ השגיאות משורה לשורה חייב לצבור. כל פיקסל בשורה הבאה מקבל תרומות משלושה פיקסלים שונים בשורה הנוכחית, ה-taps של 3/16, 5/16 ו-1/16, ואם הקוד משייך במקום להוסיף, כל כתיבה משליך את התרומה הקודמת ורק ה-tap האחרון שורד. התמונה עדיין נראית דיתרה, וזה מה שמקשה לשים לב, אבל הטקסטורה שגויה והשבת הגוונים סוטה. הבדיקה שתופסת את זה כמותית: דיתר שדה אפור אחיד בינוני ודרוש שהכיסוי הפנימי ינח בין 40 ל-60 אחוז
איזו שילוב צריך pipeline של הקטנת גודל להשתמש בו?
מתאימים את ה-kernel למה שהתמונות באמת הן, ומתייחסים ל-dithering כעניין של התקן ולא כעניין של דחיסה. לסריקות צילום שחייבות לעמוד בתקציב גודל, rkLanczos3 ב-150 או 200 DPI שומר על הפרטים שאנשים מבחינים בהם תוך חיתוך מספר הפיקסלים פי ארבעה ויותר. לצילומי מסך, דיאגרמות ו-line art, rkHalftone באמת מספיק והרבה יותר מהיר, כי לתמונות האלה מעט מעברי גוון לשמר. לאצווה מעורבת שלא ניתן לבחון בה כל תמונה, rkBicubic הוא אמצע הדרך הסביר: טוב יותר מ-bilinear, בערך חצי מספר ה-taps של Lanczos3
הקטנת רזולוציה היא מנוף אחד מכמה, ולא תמיד הגדול. סריקות דו-גוניות מגיבות בדרך כלל הרבה יותר טוב למקודד שמכוסה בדחיסת JBIG2 דו-גונית ילידית ב-Delphi, שם הזכייה באה ממילוני סמלים ולא מספירות פיקסלים. לפני שמחליטים, עוזר לדעת מה באמת יש בקובץ, ולזה משמשת חילוץ תמונות ומסנני הפענוח שלהן: מלאי של אובייקטי תמונה והדחיסה הקיימת שלהם אומר לך אם ל-resampling יש מה להרוויח
אם בונים את משטח התצוגה המקדימה שמציג את התוצאה, אותו נתיב עיבוד שמתועד בעיבוד עמוד PDF ל-bitmap הוא המקום שבו RenderOutputDither נכנס לפעולה, כך שהתצוגה המדותרת והפלט המדותר מגיעים מנתיב קוד אחד ולא משני מימושים שמתפצלים
העיקרון הרחב מאחורי שתי התכונות הוא שהגדרות איכות צריכות להיות מפורשות והפיכות. HotPDF משאיר את ההתנהגות ההיסטורית כברירת מחדל כך שיישום קיים משתדרג בלי שינוי הפתעה בפלט או בתזמון, ומציב את הנתיבים היפים והאיטיים במרחק הקצאת מאפיין אחת. שניהם חלק מרכיב ה-PDF של HotPDF ל-Delphi, לצד מנגנוני אופטימיזציית המשאבים והעיבוד שהם נשענים עליהם