PDFlibPas може кодувати дворівневі зображення як JBIG2 через два різні бекенди. Один — власний кодувальник MMR на Object Pascal, присутній завжди. Інший — зовнішній кодувальник словника символів, що дає суттєво менший результат на сканованому тексті, і він необов'язковий: проєкт має під'єднати модуль бекенда, щоб той узагалі існував. Саме ця відмінність є джерелом найпоширенішого сюрпризу з цією функцією, тож варто сказати це спершу: DefaultJBIG2EncodeOptions типово запитує зовнішній кодувальник, і коли модуль бекенда не під'єднано, запит мовчки відкатується на шлях Pascal MMR
На Delphi та C++Builder зовнішній бекенд — це набір наперед зібраних статичних об'єктів. На Free Pascal йому довелося стати DLL, і шлях до цього висновку — історія про компонувальник, корисна кожному, хто намагався приєднати об'єкти C++ до програми Free Pascal
Реєстрація — це контракт
Модуль бекенда реєструє себе з секції ініціалізації викликом RegisterJBIG2EncoderBackend. Викликачі запитують його або через біт опцій PDF_JBIG2_OPTION_EXTERNAL_ENCODER зі значенням 4, або через параметр UseExternalEncoder розширених точок входу для зображень. Парасолька бібліотеки навмисно не підтягує модуль бекенда, бо носити великий набір об'єктів має вирішувати кожен проєкт; у дереві C++Builder, наприклад, його вмикають явно ті проєкти, яким він потрібен
Наслідок для викликачів: запит зовнішнього кодувальника — це уподобання, а не гарантія, і збірка, що забула модуль, дає більші файли, а не помилку. Якщо розмір результату настільки важливий, щоб просити кращий кодувальник, він настільки важливий, щоб перевірити, що ви його отримали
uses
PDFlibrary,
{$IFDEF FPC}
PDFlibJBIG2EncDLL; // динамічний бекенд для Free Pascal
{$ELSE}
PDFlibJBIG2EncC; // статичний набір об'єктів для 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;
Скомпілювати модуль було два рядки. Символи були роботою
Щоб модуль бекенда сам скомпілювався під Free Pascal, знадобилося рівно дві зміни: встановлення діалекту асемблера та заміна конструктора налаштувань формату на основі запису глобальною типовою змінною. Це чесне відображення того, наскільки переносним є прямолінійний Pascal між двома компіляторами
Символьна сторона була справжньою роботою. Набір об'єктів посилається на 176 символів C. З них 128 уже мали реалізації Pascal усередині модуля і потребували лише прикріплення імен експорту, бо Delphi використовує ім'я функції як ім'я символу, тоді як Free Pascal вимагає явного оголошення публічного імені. Двадцять сім були спільні з кодеком JPEG 2000 і їх довелося експортувати рівно з одного місця, бо подвійне визначення ламає будь-яку програму, що приєднує обидва. Решта 21 були записами платформи та виконавчої бібліотеки C — шістнадцять файлових функцій Win32 плюс кілька стандартних бібліотечних викликів, — і вони пішли в новий сумісний модуль
Нічого з цього не є концептуально складним, і все це необхідно, перш ніж компонувальник узагалі спробує. Саме на компонувальнику все зупинилося
Три маршрути компонування, три глухі кути
Внутрішній компонувальник Free Pascal не може читати об'єктні файли, бо їх створив компілятор, що випромінює асоціативні секції COMDAT, і внутрішній компонувальник повідомляє, що не підтримує їх. Це пласка відмова, а не попередження
Перехід на зовнішній компонувальник виглядав як відповідь. Компонувальник binutils, що йде в комплекті з Free Pascal, падає просто під час застосування збирання сміття секцій до цього архіву, а той прапорець є частиною фіксованого набору параметрів, який Free Pascal передає для 64-бітової цілі Windows, тож його не можна прибрати з командного рядка; задокументовані перемикачі для його придушення на цьому шляху ігноруються. Постачання значно новішого binutils замість нього зазнає іншої невдачі: він узагалі не може обробити сценарій компонування Free Pascal, даючи порожній результат без сценарію та стіну помилок релокації з ним
Межа, виявлена дорогою, варта знання, навіть якщо ви ніколи не натрапите на проблему компонувальника. Зовнішній компонувальник розв'язує шляхи об'єктних файлів відносно каталогу виконуваного результата, а не дерева джерел, тож відносна директива включення об'єкта працює лише тоді, коли каталог результату випадково збігається з робочим каталогом під час компіляції. Бібліотека не може цього припускати щодо проєкту споживача, і це саме собі причина надати приєднувану бібліотеку, а не розсипані об'єкти
Чому інший компілятор C++ не допомагає
Очевидна наступна ідея — перезібрати сторону C++ компілятором, чиї об'єкти Free Pascal може читати. Це теж не працює, і причина фундаментальна, а не в перемикачах. Мінімальна одиниця трансляції C++ із шаблоном, зібрана з усіма вимкненими можливостями генерації коду, все одно випромінює слабкі зовнішні символи, бо інстанціювання шаблонів та inline дає їх за побудовою. Free Pascal відкидає той клас символів беззастережно. Зворотний напрям також зазнає невдачі: мейнстрімний компонувальник C++ не може споживати об'єкти від іншого компілятора через ту саму обробку секцій COMDAT
Тож код C++ не можна доставити Free Pascal як об'єкти жодним доступним маршрутом. Його можна доставити як DLL, що й сталося: кодувальник та його залежність обробки зображень зібрано в одну бібліотеку з двома пласкими точками входу C, а модуль бекенда Free Pascal приєднує їх динамічно і реєструє себе точно так, як статичний бекенд. Шлях Delphi та C++Builder не чіпали взагалі, і це правильний результат; проблема переносності на одному інструментарії не має збурювати інструментарій, що вже працював
Полярність — це те, що вас вкусить
Між дворівневою бітовою картою Windows і кодувальником JBIG2 є розбіжність угод, яку не зловить жодна система типів. Рядок розгортки двобітового апаратно-незалежного растру трактує встановлений біт як білий. Кодувальник трактує встановлений біт як чорний. Передайте рядки розгортки без змін — і отримаєте цілком правильний потік JBIG2 фотографічного негативу вашої сторінки
// Однобітовий DIB: встановлений біт означає білий. Кодувальник
// JBIG2: встановлений біт означає чорний. Інвертуйте кожен байт
// на вході
for I := 0 to RowBytes - 1 do
Row[I] := Row[I] xor $FF;
Метод перевірки важить не менше за виправлення. Порівняння довжин стиснутих потоків не говорить нічого, бо негативне зображення стискається до подібного розміру. Погляд на сторінку доводить лише те, що вона не очевидно інвертована. Надійна перевірка — відрендерити результат обох шляхів кодування, власного Pascal і зовнішнього, у PNG і порівняти байт у байт: обидва кодувальники безвтратні на тому самому вихідному зображенні, тож будь-що інше за точний збіг — вада одного з них. Те порівняння тепер постійний регресійний тест, і це той тип твердження, який варто будувати щоразу, коли дві реалізації мають збігатися точно
Який бекенд використовувати
Для загального дворівневого вмісту — розмитих напівтонів, лінійної графіки, змішаних ілюстрацій — власний кодувальник Pascal MMR достатній і не має вартості розгортання. Для сканованого тексту, заради якого JBIG2 і проєктувався, зменшення розміру живе в зовнішньому кодувальнику словника символів, бо він виносить повторювані форми гліфів у словник замість повторного кодування кожного входження. Якщо ви створюєте архіви сканованих документів, та різниця достатньо велика, щоб змінити планування сховища
Вихідне питання — як дворівневе зображення взагалі створюється — важить для розміру результату не менше; регіональний монохромний рендеринг охоплено в статті про монохромний рендеринг регіонів, а стратегію розміру документа в цілому — у оптимізації розміру PDF та субсетуванні шрифтів. Для наборів сканів із повторюваними сторінками дедуплікація часто перемагає краще стискання, що є темою перцептивної дедуплікації зображень. Доступність інструментарію та бекендів за платформами наведена на сторінці продукту losLab PDF Developer Library