Техническа статия

JBIG2 енкодер бекендове и линкерът на Free Pascal

PDFlibPas може да кодира bilevel изображения като JBIG2 чрез два различни бекенда; Единият е роден Object Pascal MMR енкодер, който винаги е наличен; Другият е външен symbol-dictionary енкодер, който произвежда съществено по-малък изход върху сканиран текст и е опционален: проект трябва да линкне бекенд модула, за да съществува изобщо; Това разграничение е източникът на най-честото изненадване с тази функция, така че си заслужава да се каже първо: DefaultJBIG2EncodeOptions иска външния енкодер по подразбиране и когато бекенд модулът не е линкнат, заявката мълшиво пада обратно към Pascal MMR пътя

На Delphi и C++Builder външният бекенд е набор от предварително компилирани статични обектни файлове; На Free Pascal той трябваше да стане DLL, а пътят към това заключение е история за линкера, полезна за всеки, който се е опитвал да линкне C++ обекти в Free Pascal програма

Регистрацията е договорът

Бекенд модулът регистрира себе си от своя initialisation секция чрез извикване на RegisterJBIG2EncoderBackend; Извикващите го искат или чрез бита в опциите PDF_JBIG2_OPTION_EXTERNAL_ENCODER, който има стойност 4, или чрез параметъра UseExternalEncoder на разширените image входни точки; Общият umbrella модул на библиотеката нарочно не дърпа бекенд модула, защото носенето на голям набор от обекти трябва да е решение на всеки проект; в дървото на C++Builder например той се включва изрично от проектите, които го искат

Последицата за извикващите е, че искането на външния енкодер е предпочитание, не гаранция, а компилация, която забрави модула, произвежда по-големи файлове вместо грешка; Ако размерът на изхода има достатъчно значение, за да искате по-добрия енкодер, има достатъчно значение, за да проверите, че сте го получили

Поток на JBIG2 заявка за кодиране в PDFlibPas, при която предпочитанието за външен енкодер мълшиво пада към родния Pascal MMR път без бекенд модула
Искането на външния symbol-dictionary енкодер е предпочитание: линкнат, изходът намалява; нелинкнат, Pascal MMR пътят тича мълшиво с по-големи файлове
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 отне точно две промени: задаване на асемблерния диалект и замяна на конструктор за format settings, базиран на запис, с глобалната променлива по подразбиране; Това е справедливо отражение на колко преносим е праволинейният Pascal между двата компилатора

Символната страна беше истинската работа; Наборът от обекти реферира 176 C символа; От тях 128 вече имаха Pascal имплементации вътре в модула и се нуждаеха само от прикрепени export имена, защото Delphi използва името на функцията като име на символа, докато Free Pascal изисква изрична декларация на public име; Двадесет и седем бяха споделени с JPEG 2000 кодека и трябваше да бъдат експортирани от точно едно място, тъй като дефинирането им два пъти чупи всяка програма, която линква и двете; Останалите 21 бяха platform и C runtime записи, шестнайсет Win32 файлови функции плюс шепа стандартни библиотечни извиквания, и отидоха в нов compatibility модул

Нищо от това не е концептуално трудно и всичко е необходимо, преди линкерът изобщо да опита; Линкерът е мястото, където спря

Три маршрута за линкване, три задънения

Вътрешният Free Pascal линкер не може да прочете обектните файлове, защото те са произведени от компилатор, който излъчва асоциативни COMDAT секции, а вътрешният линкер докладва, че не ги поддържа; Това е плосък отказ, не предупреждение

Преминаването към външен линкер изглеждаше като отговорът; Binutils линкерът, комплектован с Free Pascal, се срива напълно, докато прилага section garbage collection върху този архив, а този флаг е част от фиксиран набор параметри, които Free Pascal предава за 64-битовата Windows цел, така че не може да бъде премахнат от командния ред; документираните ключове за потискането му се игнорират на този път; Снабдяването с много по-нов binutils се проваля различно: той изобщо не може да обработи Free Pascal link скрипта, произвеждайки празен изход без скрипта и стена от relocation грешки с него

Едно гранично откритие по пътя си заслужава да се знае, дори никога да не се сблъскате с проблема с линкера; Външният линкер резолва пътищата на обектните файлове спрямо директорията с изпълнимия изход, а не спрямо source дървото, така че относителна include-object директива работи само когато изходната директория случайно съвпада с работната директория по време на компилация; Библиотека не може да предполага това за проекта на консуматора, което само по себе си е причина да се предпочете линкната библиотека пред разхвърляни обекти

Три неуспешни линк маршрута за C++ JBIG2 енкодер обекти под Free Pascal и DLL-ът, излагащ два плоски C входни пункта, който ги разреши
COMDAT секциите надвитяват вътрешния линкер и двата външни линкера се провалят, така че C++ енкодерът се доставя като един DLL, обвързан динамично от бекенд модула

Защо друг C++ компилатор не помага

Очевидната следваща идея е C++ страната да се прекомпилира с компилатор, чиито обекти Free Pascal може да чете; Тя също не работи, а причината е фундаментална, а не въпрос на ключове; Минимална C++ translation единица, съдържаща template, компилирана с всяка функция за генериране на код изключена, все пак излъчва weak external символи, защото template и inline инстанцирането ги произвежда по конструкция; Free Pascal отхвърля този клас символи изцяло; Обратната посока също се проваля: масов C++ линкер не може да консумира обекти от другия компилатор поради същата обработка на COMDAT секции

Така C++ кодът не може да бъде доставен като обекти на Free Pascal по никакъв наличен маршрут; Може да бъде доставен като DLL, което се случи: енкодерът и неговата image-processing зависимост са вградени в една библиотека, излагаща два плоски C входни пункта, а Free Pascal бекенд модулът ги обвързва динамично и се регистрира точно както статичният бекенд; Пътят на Delphi и C++Builder изобщо не беше докоснат, което е правилният резултат; проблем с преносимостта на една toolchain не трябва да смущава toolchain-а, който вече работи

Полярността е единственото нещо, което ще ви ухапе

Между Windows bilevel bitmap и JBIG2 енкодер има несъответствие в конвенциите, което никаква типова система няма да хване; Scanline от еднобитово на пиксел device-independent bitmap третира установен бит като бял; Енкодерът третира установен бит като черен; Подайте scanline-ите непроменени и ще получите напълно валиден JBIG2 поток на фотографския негатив на вашата страница

Конвенции за полярност на еднобитов DIB и JBIG2, при които установен бит е бял в scanline и черен в енкодера, оправени с инвертиране на всеки байт
Същите байтове, противоположно значение: без инвертиране на всеки байт енкодерът произвежда валиден JBIG2 поток на фотографския негатив
// Еднобитов DIB: установен бит значи бяло. JBIG2 енкодер:
// установен бит значи черно. Инвертирайте всеки байт по пътя навътре
for I := 0 to RowBytes - 1 do
  Row[I] := Row[I] xor $FF;

Методът за верификация има също толкова значение, колкото поправката; Сравняването на дължините на компресираните потоци не ви казва нищо, защото негативно изображение се компресира до подобен размер; Поглеждането към страницата доказва само, че не е очевидно инвертирана; Надеждната проверка е да се изобрази изходът и на двата кодиращи пътя, роден Pascal и външен, към PNG и да се сравнят байт по байт: и двата енкодера са lossless върху едно и също source изображение, така че всичко различно от точно съвпадение е дефект в единия от тях; Това сравнение вече е постоянен regression тест и е видът assertion, който си заслужава да се изгради, когато две имплементации трябва да съвпадат точно

Кой бекенд да се използва

За общо bilevel съдържание, dithered halftones, line art, смесена графика, родният Pascal MMR енкодер е достатъчен и няма разход за внедряване; За сканиран текст, който е случаят, за който JBIG2 е проектиран, външният symbol-dictionary енкодер е мястото, където живее намалението на размера, защото факторизира повтарящи се glyph форми в речник вместо да прекодира всяко срещане; Ако произвеждате архиви от сканирани документи, тази разлика е достатъчно голяма, за да промени планирането на съхранението

Въпросът нагоре по веригата, как bilevel изображението се произвежда изобщо, има също толкова значение за размера на изхода; region-based монохромно изобразяване е разгледано в статията за монохромно изобразяване на региони, а стратегията за размер в целия документ — в оптимизация на PDF размера на файл и font subsetting; За сканиращи набори с повтарящи се страници дедупликацията често надминава по-добрата компресия, което е темата на перцептуална дедупликация на изображения; Наличността на toolchain и бекенд за всяка платформа е изброена на продуктовата страница losLab PDF Developer Library