Technický článek

JBIG2 backendy enkodéru a linker Free Pascalu

PDFlibPas dokáže kódovat dvouúrovňové (bilevel) obrázky jako JBIG2 dvěma různými backendy. Jedním je nativní MMR enkodér v Object Pascalu, který je přítomen vždy. Druhým je externí enkodér symbolových slovníků, který na skenovaném textu produkuje podstatně menší výstup, a je volitelný: projekt musí unitu backendu přilinkovat, aby vůbec existoval. Toto rozlišení je zdrojem nejčastějšího překvapení u této funkce, takže stojí za zmínku hned na úvod: DefaultJBIG2EncodeOptions žádá externí enkodér ve výchozím nastavení a když unita backendu není přilinkována, požadavek potichu spadne na Pascal cestu MMR

V Delphi a C++Builder je externí backend sada předkompilovaných statických objektů. Ve Free Pascalu se z něj musela stát DLL a cesta k tomuto závěru je příběh o linkeru, užitečný pro každého, kdo se někdy pokusil linkovat C++ objekty do programu ve Free Pascalu

Registrace je smlouva

Unita backendu registruje sama sebe ze sekce inicializace voláním RegisterJBIG2EncoderBackend. Volající si ji vyžádají buď přes bit v options, PDF_JBIG2_OPTION_EXTERNAL_ENCODER, který má hodnotu 4, nebo přes parametr UseExternalEncoder rozšířených vstupních bodů pro obrázky. Zastřešující unita knihovny záměrně backendovou unitu nezatahuje, protože nést velkou sadu objektů by mělo být rozhodnutím každého projektu; ve stromu C++Builder je například zahrnuta explicitně projekty, které ji chtějí

Důsledek pro volající je ten, že žádost o externí enkodér je preference, nikoli záruka, a sestavení, které unitu zapomene, produkuje větší soubory místo chyby. Pokud velikost výstupu stojí za to, abyste žádali lepší enkodér, stojí za to také ověřit, že jste jej dostali

Tok žádosti o JBIG2 kódování v PDFlibPas, kde preference externího enkodéru potichu spadne na nativní Pascal cestu MMR bez unity backendu
Žádost o externí enkodér symbolových slovníků je preference: přilinkováno, výstup se zmenší; nepřilinkováno, Pascal cesta MMR běží potichu s většími soubory
uses
  PDFlibrary,
{$IFDEF FPC}
  PDFlibJBIG2EncDLL;    // dynamický backend pro Free Pascal
{$ELSE}
  PDFlibJBIG2EncC;      // statická sada objektů pro 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;

Zkompilovat unitu byly dva řádky. Prací byly symboly

Donutit samotnou unitu backendu ke kompilaci pod Free Pascalem stálo přesně dvě změny: nastavit dialekt assembleru a nahradit konstruktor nastavení formátu založený na záznamu globální výchozí proměnnou. To je věrný obraz toho, jak je přímočarý Pascal přenositelný mezi oběma kompilátory

Symbolová strana byla skutečná práce. Sada objektů odkazuje na 176 C symbolů. Z toho 128 již mělo implementace v Pascalu uvnitř unity a potřebovalo pouze připojené exportní názvy, protože Delphi používá název funkce jako název symbolu, zatímco Free Pascal vyžaduje explicitní deklaraci public name. Dvacet sedm bylo sdíleno s kodekem JPEG 2000 a muselo být exportováno z přesně jednoho místa, protože dvojí definice rozbije jakýkoli program, který linkuje obojí. Zbylých 21 byly položky platformy a C runtime, šestnáct funkcí souborů Win32 plus hrst volání standardní knihovny, a ty putovaly do nové kompatibilní unity

Nic z toho není koncepčně těžké a všechno je nutné, než se linker vůbec pokusí. Na linkeru to skončilo

Tři linkovací cesty, tři slepé uličky

Interní linker Free Pascalu nedokáže číst objektové soubory, protože je vyprodukoval kompilátor emitující asociativní sekce COMDAT a interní linker hlásí, že je nepodporuje. To je plošné odmítnutí, nikoli varování

Přechod na externí linker vypadal jako odpověď. Linker binutils dodávaný s Free Pascalem krachuje úplně při aplikaci garbage kolekce sekcí na tento archiv a tento příznak je součástí pevné sady parametrů, které Free Pascal předává pro 64bitový cíl Windows, takže jej nelze z příkazové řádky odstranit; zdokumentované přepínače k jeho potlačení se na této cestě ignorují. Dodání mnohem novějších binutils selhává jinak: nedokáže zpracovat linkovací skript Free Pascalu vůbec, čímž vzniká prázdný výstup bez skriptu a stěna relokačních chyb se skriptem

Hranice odhalená po cestě stojí za znalost, i když na problém linkeru nikdy nenarazíte. Externí linker rozlišuje cesty objektových souborů relativně k výstupnímu adresáři spustitelného souboru, nikoli ke stromu zdrojů, takže relativní direktiva include-object funguje jen tehdy, když výstupní adresář náhodou odpovídá pracovnímu adresáři při kompilaci. Knihovna to o projektu konzumenta předpokládat nemůže, což je samo o sobě důvod dát přednost linkované knihovně před volnými objekty

Tři neúspěšné linkovací cesty pro C++ objekty JBIG2 enkodéru pod Free Pascalem a DLL vystavující dva ploché C vstupní body, které je vyřešily
Sekce COMDAT porazí interní linker a oba externí linkery selžou, takže C++ enkodér putuje ven jako jedna DLL dynamicky vázaná unitou backendu

Proč jiný C++ kompilátor nepomůže

Zjevná další myšlenka je přeložit C++ stranu kompilátorem, jehož objekty Free Pascal číst dokáže. Nefunguje to ani tak a důvod je fundamentální, nikoli otázka přepínačů. Minimální C++ překladová jednotka obsahující šablonu, přeložená se všemi funkcemi generování kódu vypnutými, stále emituje slabé externí symboly, protože instanciace šablon a inline je produkuje ze své podstaty. Free Pascal tuto třídu symbolů odmítá úplně. Opačný směr selhává také: hlavní proud C++ linkerů nedokáže konzumovat objekty od druhého kompilátoru kvůli stejné obsluze sekcí COMDAT

C++ kód tedy nelze dodat Free Pascalu jako objekty žádnou dostupnou cestou. Lze jej dodat jako DLL, což se také stalo: enkodér a jeho závislost na zpracování obrazu jsou postaveny do jediné knihovny vystavující dva ploché C vstupní body a unita backendu Free Pascalu je váže dynamicky a registruje se přesně jako statický backend. Cesta Delphi a C++Builder nebyla vůbec dotčena, což je správný výsledek; problém přenositelnosti na jednom toolchainu by neměl narušit toolchain, který už fungoval

Polarita je ta jediná věc, která vás kousne

Mezi Windows bitmapou o dvou úrovních a JBIG2 enkodérem je nesoulad konvencí, který žádný typový systém neodhalí. Scanline device-independent bitmapy o jednom bitu na pixel chápe nastavený bit jako bílý. Enkodér chápe nastavený bit jako černý. Předáte-li scanlines beze změny, dostanete dokonale platný JBIG2 stream fotografického negativu své stránky

Konvence polarity jednobitové DIB a JBIG2, kde nastavený bit je v scanline bílý a v enkodéru černý, opraveno invertováním každého bajtu
Tytéž bajty, opačný význam: bez invertování každého bajtu enkodér produkuje platný JBIG2 stream fotografického negativu
// Jednobitová DIB: nastavený bit znamená bílý. JBIG2 enkodér:
// nastavený bit znamená černý. Invertujte každý bajt na vstupu
for I := 0 to RowBytes - 1 do
  Row[I] := Row[I] xor $FF;

Způsob ověření je stejně důležitý jako oprava. Porovnávání délek komprimovaných streamů neříká nic, protože negativní obraz se komprimuje na podobnou velikost. Pohled na stránku dokazuje jen to, že není zjevně invertovaná. Spolehlivá kontrola je vykreslit výstup obou kódovacích cest, nativní Pascal i externí, do PNG a porovnat je bajt po bajtu: oba enkodéry jsou na témže zdrojovém obrázku bezztrátové, takže cokoli jiného než přesná shoda je chyba jednoho z nich. Toto porovnání je nyní permanentním regresním testem a je to ten druh aserce, kterou stojí za to stavět, kdykoli mají dvě implementace souhlasit přesně

Který backend použít

Pro obecný dvouúrovňový obsah — ditherované polotóny, linkovou grafiku, smíšenou grafiku — je nativní Pascal MMR enkodér dostatečný a nemá žádné náklady na nasazení. Pro skenovaný text, pro který byl JBIG2 navržen, je místem zmenšení velikosti externí enkodér symbolových slovníků, protože opakované tvary glyfů faktorizuje do slovníku namísto opětovného kódování každého výskytu. Pokud produkujete archivy skenovaných dokumentů, je tento rozdíl dost velký na to, aby změnil plánování úložiště

Upstream otázka, jak je dvouúrovňový obrázek vůbec vyprodukován, má na velikost výstupu stejný vliv; regionové monochromatické vykreslování pokrývá článek o monochromatickém vykreslování regionů a strategie velikosti celého dokumentu optimalizace velikosti PDF a subsetting písem. Pro sady skenů s opakovanými stránkami často vyjde lépe deduplikace než lepší komprese, čímž se zabývá perceptuální deduplikace obrázků. Dostupnost toolchainu a backendů na jednotlivých platformách uvádí stránka produktu losLab PDF Developer Library