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
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
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
// 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