PDFlibPas יכול לקודד תמונות דו-מפלסיות כ-JBIG2 דרך שני backends שונים. האחד הוא מקודד MMR מקורי ב-Object Pascal שתמיד נוכח. האחר הוא מקודד symbol-dictionary חיצוני שמפיק פלט קטן במידה ניכרת על טקסט סרוק, והוא אופציונלי: פרויקט חייב לקשר את יחידת ה-backend כדי שיהיה קיים מלכתחילה. ההבחנה הזאת היא מקור ההפתעה הנפוצה ביותר עם התכונה הזאת, ולכן שווה לומר זאת קודם: DefaultJBIG2EncodeOptions מבקש את המקודד החיצוני כברירת מחדל, וכשיחידת ה-backend אינה מקושרת הבקשה חוזרת בשקט לנתיב ה-MMR של פסקל
ב-Delphi וב-C++Builder ה-backend החיצוני הוא קבוצת objects סטטיים שנבנו מראש. ב-Free Pascal הוא היה חייב להפוך ל-DLL, והדרך אל המסקנה הזאת היא סיפור של מקשר שימושי לכל מי שניסה לקשר objects של C++ לתוך תוכנית Free Pascal
הרישום הוא החוזה
יחידת ה-backend רושמת את עצמה מחלקת ה-initialisation שלה בקריאה ל-RegisterJBIG2EncoderBackend. קוראים מבקשים אותה בין דרך ביט האפשרויות, PDF_JBIG2_OPTION_EXTERNAL_ENCODER, שערכו 4, ובין דרך הפרמטר UseExternalEncoder של נקודות הכניסה המורחבות לתמונות. המטרייה של הספרייה אינה מושכת את יחידת ה-backend בכוונה, כי נשיאת קבוצת objects גדולה צריכה להיות החלטה של כל פרויקט; בעץ ה-C++Builder, למשל, היא נכללת מפורשות על ידי הפרויקטים שרוצים אותה
ההשלכה עבור הקוראים היא שבקשת המקודד החיצוני היא העדפה ולא ערובה, ובנייה ששוכחת את היחידה מפיקה קבצים גדולים יותר ולא שגיאה. אם גודל הפלט חשוב מספיק כדי לבקש את המקודד הטוב יותר, הוא חשוב מספיק כדי לבדוק שקיבלתם אותו
uses
PDFlibrary,
{$IFDEF FPC}
PDFlibJBIG2EncDLL; // backend דינמי עבור Free Pascal
{$ELSE}
PDFlibJBIG2EncC; // קבוצת objects סטטית עבור Delphi / C++Builder
{$ENDIF}
var
Pdf: TPDFlib;
ImageId: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.NewDocument;
Pdf.NewPage;
// Interpolate, SymbolExtract, UseExternalEncoder, SkipBlackDots,
// BlackDotSize, LossyLevel
ImageId := Pdf.AddImageJBIG2FromFileEx('scan-page-1.tif',
0, 1, 1, 0, 0, 0);
if ImageId = 0 then
raise Exception.Create('JBIG2 encoding failed');
Pdf.SaveToFile('archive.pdf');
finally
Pdf.Free;
end;
end;
היחידה קומפלה בשתי שורות. הסימבולים היו העבודה
גרימת יחידת ה-backend עצמה להתקמפל תחת Free Pascal לקחה בדיוק שני שינויים: קביעת הדיאלקט של ה-assembler, והחלפת constructor של הגדרות פורמט מבוסס record במשתנה ברירת המחדל הגלובלי. זה שיקוף הוגן של כמה פסקל ישיר הוא נייד בין שני המהדרים
צד הסימבולים היה העבודה האמיתית. קבוצת ה-objects מפנה ל-176 סימבולים של C. מתוכם, ל-128 כבר היו מימושי פסקל בתוך היחידה ורק היו זקוקים לשמות export מצורפים, כי Delphi משתמשת בשם הפונקציה כשם הסימבול ואילו Free Pascal דורש הצהרת שם public מפורשת. עשרים ושבעה היו משותפים עם הקודק JPEG 2000 והיו חייבים להיות מיוצאים ממקום אחד בדיוק, כי הגדרה כפולה שלהם שוברת כל תוכנית שמקשרת את שניהם. 21 הנותרים היו כניסות פלטפורמה ו-runtime של C, שש עשרה פונקציות קבצים של Win32 ועוד קומץ קריאות ספרייה סטנדרטית, והם נכנסו ליחידת תאימות חדשה
אף אחד מאלה אינו קשה מבחינה מושגית, והכול הכרחי לפני שהמקשר בכלל ינסה. המקשר הוא המקום שבו זה נעצר
שלושה נתיבי קישור, שלושה רחובות ללא מוצא
המקשר הפנימי של Free Pascal אינו מסוגל לקרוא את קבצי ה-object, כי הם הופקו על ידי מהדר שפולט חלקי COMDAT אסוציאטיביים והמקשר הפנימי מדווח שאינו תומך בהם. זו סירוב מוחלט, לא אזהרה
מעבר למקשר חיצוני נראה כמו התשובה. המקשר של binutils המצורף ל-Free Pascal קורס לגמרי תוך כדי החלת איסוף זבל חלקים על הארכיון הזה, והדגל הזה הוא חלק מקבוצת הפרמטרים הקבועה ש-Free Pascal מעביר עבור יעד Windows בן 64 הביט, ולכן אי אפשר להסיר אותו משורת הפקודה; המתגים המתועדים לדיכויו מתעלמים בנתיב הזה. אספקת binutils הרבה יותר חדש נכשלת אחרת: הוא אינו מסוגל לעבד את סקריפט הקישור של Free Pascal בכלל, ומפיק פלט ריק בלי הסקריפט וקיר של שגיאות relocation איתו
גבולה שהתגלתה בדרך שווה היכרות גם אם לעולם לא תיתקלו בבעיית המקשר. המקשר החיצוני פותר נתיבי קבצי object ביחס לספריית פלט ההרצה ולא ביחס לעץ המקור, ולכן הנחיית include-object יחסית עובדת רק כשספריית הפלט מקרה זהה לספריית העבודה של זמן הקימפול. ספרייה לא יכולה להניח זאת לגבי פרויקט של צרכן, וזה כשלעצמו סיבה להעדיף ספרייה מקושרת על פני objects פזורים
למה מהדר C++ אחר לא עוזר
הרעיון הבא המובן מאליו הוא לבנות מחדש את צד ה-C++ עם מהדר שאת ה-objects שלו Free Pascal יכול לקרוא. זה גם לא עובד, והסיבה יסודית ולא עניין של מתגים. יחידת תרגום מינימלית של C++ שמכילה template, שקומפלה עם כל תכונות יצירת הקוד כבויות, עדיין פולטת סימבולים חיצוניים חלשים, כי מימוש template ו-inline מפיק אותם על פי הבנייה. Free Pascal דוחה את מחלקת הסימבולים הזאת באופן מוחלט. הכיוון ההפוך נכשל גם הוא: מקשר C++ מרכזי אינו מסוגל לצרוך objects מהמהדר השני בגלל אותו טיפול בחלקי COMDAT
לכן את הקוד של C++ אי אפשר לספק ל-Free Pascal כ-objects באף נתיב זמין. אפשר לספק אותו כ-DLL, וזה מה שקרה: המקודד והתלות שלו בעיבוד תמונה נבנו לספרייה אחת שחושפת שתי כניסות C שטוחות, ויחידת ה-backend של Free Pascal קושרת אותן דינמית ורושמת את עצמה בדיוק כמו ה-backend הסטטי. נתיב Delphi ו-C++Builder לא נגע כלל, וזו התוצאה הנכונה; בעיית ניידות ב-toolchain אחד לא צריכה לזעזע את ה-toolchain שכבר עבד
הקוטביות היא הדבר האחד שינשך אתכם
בין bitmap דו-מפלסי של Windows ובין מקודד JBIG2 יש אי התאמת מוסכמה שאף מערכת טיפוסים לא תתפוס. שורת סריקה של device-independent bitmap עם ביט אחד לפיקסל מתייחסת לביט דלוק כלבן. המקודד מתייחס לביט דלוק כשחור. מוסרים את שורות הסריקה ללא שינוי ומקבלים stream JBIG2 תקין לחלוטין של השלילה הצילומית של העמוד שלכם
// DIB בן ביט אחד: ביט דלוק פירושו לבן. מקודד JBIG2: ביט דלוק פירושו
// שחור. הפוך כל בייט בדרך פנימה
for I := 0 to RowBytes - 1 do
Row[I] := Row[I] xor $FF;
שיטת האימות חשובה לא פחות מהתיקון. השוואת אורכי stream דחוסים לא אומרת דבר, כי תמונה שלילית דוחסת לגודל דומה. מבט בעמוד מוכיח רק שהוא אינו מופוך בבירור. הבדיקה האמינה היא לרנדר את הפלט של שני נתיבי הקידוד, פסקל מקורי וחיצוני, ל-PNG ולהשוות אותם בייט-בייט: שני המקודדים חסרי איבוד על אותה תמונת מקור, ולכן כל דבר מלבד התאמה מדויקת הוא באג באחד מהם. ההשוואה הזאת היא עכשיו בדיקת רגרסיה קבועה, וזהו סוג הטענה ששווה לבנות בכל פעם ששני מימושים אמורים להסכים במדויק
באיזה backend לבחור
עבור תוכן דו-מפלסי כללי, halftones משוננים, אמנות קווים וגרפיקה מעורבת, המקודד הפסקלי המקורי מסוג MMR מספיק ואין לו עלות פריסה. עבור טקסט סרוק, שהוא המקרה שלשמו JBIG2 תוכנן, המקודד החיצוני מסוג symbol-dictionary הוא המקום שבו חי החיסכון בגודל, כי הוא מפקטר צורות glyph חוזרות למילון במקום לקודד מחדש כל מופע. אם אתם מפיקים ארכיונים של מסמכים סרוקים, ההבדל הזה גדול מספיק כדי לשנות תכנון אחסון
השאלה שבמעלה הזרם, איך תמונת הדו-מפלס מופקת מלכתחילה, חשובה לא פחות לגודל הפלט; רינדור מונוכרום מבוסס אזורים מכוסה במאמר רינדור אזור מונוכרומי, ואסטרטגיית גודל ברמת המסמך כולו באופטימיזציה של גודל קובץ PDF ו-subsetting של גופנים. עבור סטים סרוקים עם עמודים חוזרים, deduplication מנצח לעיתים קרובות דחיסה טובה יותר, וזה נושאו של deduplication פרספטואלי של תמונות. זמינות toolchain ו-backend לפי פלטפורמה מופיעה בדף המוצר של losLab PDF Developer Library