PDFlibPas poate codifica imagini bilevel ca JBIG2 prin două backend-uri diferite. Unul este un encoder MMR nativ în Object Pascal, mereu prezent. Celălalt este un encoder extern cu dicționar de simboluri, care produce rezultate substanțial mai mici pe text scanat, și este opțional: un proiect trebuie să lege unitatea backend pentru ca el să existe deloc. Această distincție este sursa celei mai comune surprize legate de această funcție, deci merită enunțată mai întâi: DefaultJBIG2EncodeOptions cere encoderul extern implicit, iar când unitatea backend nu este legată, cererea revine în tăcere pe calea MMR din Pascal
Pe Delphi și C++Builder backend-ul extern este un set de obiecte statice precompilate. Pe Free Pascal a trebuit să devină un DLL, iar drumul către această concluzie este o poveste de linker utilă oricui a încercat să lege obiecte C++ într-un program Free Pascal
Înregistrarea este contractul
Unitatea backend se înregistrează din secțiunea ei de inițializare apelând RegisterJBIG2EncoderBackend. Apelanții o cer fie prin bitul de opțiuni PDF_JBIG2_OPTION_EXTERNAL_ENCODER, care are valoarea 4, fie prin parametrul UseExternalEncoder al punctelor de intrare extinse pentru imagini. Unitatea-parasol a bibliotecii nu include în mod deliberat unitatea backend, deoarece transportarea unui set mare de obiecte ar trebui să fie decizia fiecărui proiect; în arborele C++Builder, de exemplu, este inclusă explicit de proiectele care o doresc
Consecința pentru apelanți este că solicitarea encoderului extern este o preferință, nu o garanție, iar un build care uită unitatea produce fișiere mai mari, nu o eroare. Dacă mărimea rezultatului contează suflet cât să cereți encoderul mai bun, contează suflet cât să verificați că l-ați primit
uses
PDFlibrary,
{$IFDEF FPC}
PDFlibJBIG2EncDLL; // backend dinamic pentru Free Pascal
{$ELSE}
PDFlibJBIG2EncC; // set de obiecte statice pentru 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;
Compilarea unității a fost două linii. Simbolurile au fost munca
A duce unitatea backend la compilare sub Free Pascal a luat exact două schimbări: stabilirea dialectului de assembler și înlocuirea unui constructor de setări de format bazat pe record cu variabila globală implicită. Aceasta este o reflectare corectă a cât de portabil este Pascalul direct între cele două compilatoare
Partea de simboluri a fost adevărata muncă. Setul de obiecte referențiază 176 de simboluri C. Dintre acestea, 128 aveau deja implementări Pascal în interiorul unității și au avut nevoie doar de nume de export atașate, deoarece Delphi folosește numele funcției ca nume de simbol, în timp ce Free Pascal cere o declarație explicită de nume public. Douăzeci și șapte erau partajate cu codec-ul JPEG 2000 și au trebuit exportate exact dintr-un singur loc, deoarece definirea lor de două ori strică orice program care leagă pe amândouă. Celelalte 21 erau intrări de platformă și de runtime C, șaisprezece funcții de fișier Win32 plus câteva apeluri de bibliotecă standard, și au intrat într-o unitate nouă de compatibilitate
Nimic dintre acestea nu este conceptual greu, iar totul este necesar înainte ca linkerul să încerce măcar. La linker s-a oprit totul
Trei rute de legare, trei fundături
Linkerul intern Free Pascal nu poate citi fișierele obiect, deoarece ele au fost produse de un compilator care emite secțiuni COMDAT associative, iar linkerul intern raportează că nu le suportă. Este un refuz categoric, nu un avertisment
Trecerea la un linker extern părea răspunsul. Linkerul binutils livrat cu Free Pascal crapă direct în timp ce aplică garbage collection de secțiuni acestei arhive, iar acel fanion face parte din setul fix de parametri pe care Free Pascal îl transmite pentru ținta Windows pe 64 de biți, deci nu poate fi eliminat din linia de comandă; comutatoarele documentate pentru suprimarea lui sunt ignorate pe acest drum. Furnizarea unui binutils mult mai nou eșuează altfel: nu poate procesa deloc scriptul de legare Free Pascal, producând un rezultat gol fără script și un zid de erori de relocare cu el
O limită descoperită pe drum merită cunoscută chiar dacă nu întâlniți niciodată problema linkerului. Linkerul extern rezolvă căile fișierelor obiect relativ la directorul de ieșire al executabilului, nu la arborele de surse, deci o directivă relativă de includere de obiecte funcționează doar când directorul de ieșire coincide întâmplător cu directorul de lucru la compilare. O bibliotecă nu poate presupune asta despre proiectul unui consumator, ceea ce reprezintă singur un motiv de a prefera o bibliotecă legată în locul obiectelor libere
De ce un alt compilator C++ nu ajută
Următoarea idee evidentă este să reconstruiți partea C++ cu un compilator ale cărui obiecte le poate citi Free Pascal. Nu funcționează nici așa, iar motivul este fundamental, nu o chestiune de comutatoare. O unitate de traducere C++ minimală care conține un template, compilată cu toate funcțiile de generare de cod dezactivate, emite tot simboluri externe slabe, deoarece instanțierea de template și inline le produce prin construcție. Free Pascal respinge categoric acea clasă de simboluri. Sensul invers eșuează la fel: un linker C++ de principală răspândire nu poate consuma obiecte de la celălalt compilator din cauza aceleiași gestionări a secțiunilor COMDAT
Deci codul C++ nu poate fi livrat ca obiecte către Free Pascal pe nicio rută disponibilă. Poate fi livrat ca DLL, iar asta s-a întâmplat: encoderul și dependența lui de procesare de imagini sunt construite într-o singură bibliotecă care expune două puncte de intrare C plate, iar unitatea backend Free Pascal se leagă dinamic de ele și se înregistrează exact ca backend-ul static. Calea Delphi și C++Builder nu a fost atinsă deloc, ceea ce este rezultatul corect; o problemă de portabilitate pe un toolchain nu ar trebui să tulbure toolchain-ul care funcționa deja
Polaritatea este singurul lucru care vă va mușca
Între un bitmap bilevel Windows și un encoder JBIG2 există o nepotrivire de convenție pe care niciun sistem de tipuri nu o va prinde. O linie de scanare bitmap independentă de dispozitiv cu un bit pe pixel tratează un bit setat ca alb. Encoderul tratează un bit setat ca negru. Dați liniile de scanare neschimbate și obțineți un stream JBIG2 perfect valid cu negativul fotografic al paginii dumneavoastră
// DIB pe un bit: bit setat înseamnă alb. Encoder JBIG2: bit setat
// înseamnă negru. Inversați fiecare octet la intrare
for I := 0 to RowBytes - 1 do
Row[I] := Row[I] xor $FF;
Metoda de verificare contează la fel de mult ca remedierea. Compararea lungimilor de stream comprimat nu vă spune nimic, deoarece o imagine negativă se comprimă la o mărime similară. Privirea asupra paginii dovedește doar că nu este evident inversată. Verificarea de încredere constă în randarea rezultatului ambelor căi de codificare, Pascal nativ și extern, în PNG și compararea lor octet cu octet: ambele encodere sunt lossless pe aceeași imagine sursă, deci orice altceva decât o potrivire exactă este un bug în unul dintre ele. Acea comparație este acum un test de regresie permanent, iar acesta este genul de aserțiune care merită construită ori de câte ori două implementări ar trebui să coincidă exact
Ce backend să folosiți
Pentru conținut bilevel general, half tones cu dithering, line art, grafică mixtă, encoderul MMR nativ din Pascal este adecvat și nu are cost de implementare. Pentru text scanat, care este cazul pentru care JBIG2 a fost proiectat, encoderul extern cu dicționar de simboluri este locul unde trăiește reducerea de mărime, deoarece factorizează formele de glife repetate într-un dicționar în loc să re-codifice fiecare apariție. Dacă produceți arhive de documente scanate, diferența este suficient de mare încât să schimbe planificarea de stocare
Întrebarea din amonte, cum se produce imaginea bilevel de la bun început, contează la fel de mult pentru mărimea rezultatului; randarea monochrome pe regiuni este tratată în articolul despre randarea monochrome pe regiuni, iar strategia de mărime la nivel de document în optimizarea mărimii fișierelor PDF și subsetting-ul de fonturi. Pentru seturi de scanări cu pagini repetate, deduplicarea bate adesea o compresie mai bună, subiect care face obiectul deduplicării perceptive de imagini. Disponibilitatea de toolchain și backend pe platformă este listată pe pagina de produs losLab PDF Developer Library