PDFlibPas può codificare immagini bilevel come JBIG2 attraverso due backend diversi. Uno è un encoder MMR nativo in Object Pascal sempre presente. L'altro è un encoder esterno a symbol dictionary che produce output sostanzialmente più piccolo sul testo scannerizzato, ed è opzionale: un progetto deve collegare l'unità del backend perché esista affatto. Questa distinzione è la fonte della sorpresa più comune con questa funzione, quindi vale la pena dirlo subito: DefaultJBIG2EncodeOptions richiede l'encoder esterno per impostazione predefinita, e quando l'unità del backend non è collegata la richiesta ricade silenziosamente sul percorso MMR in Pascal
Su Delphi e C++Builder il backend esterno è un insieme di oggetti statici precompilati. Su Free Pascal ha dovuto diventare una DLL, e la strada verso questa conclusione è una storia di linker utile a chiunque abbia provato a collegare oggetti C++ in un programma Free Pascal
La registrazione è il contratto
L'unità del backend si registra dalla propria sezione di initialisation chiamando RegisterJBIG2EncoderBackend. I chiamanti la richiedono sia attraverso il bit delle opzioni, PDF_JBIG2_OPTION_EXTERNAL_ENCODER, che ha valore 4, sia attraverso il parametro UseExternalEncoder dei punti di ingresso estesi per le immagini. L'unità ombrello della libreria non include deliberatamente l'unità del backend, perché trasportare un grande insieme di oggetti deve essere una decisione di ogni singolo progetto; nell'albero C++Builder, per esempio, viene inclusa esplicitamente dai progetti che la vogliono
La conseguenza per i chiamanti è che richiedere l'encoder esterno è una preferenza, non una garanzia, e una build che dimentica l'unità produce file più grandi anziché un errore. Se la dimensione dell'output conta abbastanza da chiedere l'encoder migliore, conta abbastanza da verificare di averlo ottenuto
uses
PDFlibrary,
{$IFDEF FPC}
PDFlibJBIG2EncDLL; // backend dinamico per Free Pascal
{$ELSE}
PDFlibJBIG2EncC; // insieme di oggetti statici per 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;
Compilare l'unità è costato due righe. I simboli sono stati il vero lavoro
Far compilare l'unità del backend sotto Free Pascal è costato esattamente due modifiche: impostare il dialetto assembler e sostituire un costruttore di impostazioni formato basato su record con la variabile default globale. È un riflesso fedele di quanto il Pascal semplice sia portabile tra i due compilatori
Il fronte dei simboli è stato il vero lavoro. L'insieme di oggetti riferisce 176 simboli C. Di questi, 128 avevano già implementazioni Pascal dentro l'unità e avevano bisogno solo dei nomi di export agganciati, perché Delphi usa il nome della funzione come nome del simbolo mentre Free Pascal richiede una dichiarazione esplicita del nome pubblico. Ventisette erano condivisi con il codec JPEG 2000 e dovevano essere esportati da esattamente un punto, poiché definirli due volte rompe qualsiasi programma che li colleghi entrambi. I restanti 21 erano voci della piattaforma e del runtime C, sedici funzioni file Win32 più una manciata di chiamate della libreria standard, e sono finiti in una nuova unità di compatibilità
Nulla di ciò è concettualmente difficile, e tutto è necessario prima che il linker provi anche solo a lavorare. Il linker è dove si è fermato tutto
Tre vie di collegamento, tre vicoli ciechi
Il linker interno di Free Pascal non può leggere i file oggetto, perché sono stati prodotti da un compilatore che emette sezioni COMDAT associative e il linker interno segnala di non supportarle. È un rifiuto secco, non un avvertimento
Passare a un linker esterno sembrava la risposta. Il linker binutils incluso in Free Pascal va in crash frontalmente mentre applica la garbage collection delle sezioni a questo archivio, e quel flag fa parte del set fisso di parametri che Free Pascal passa per il target Windows a 64 bit, quindi non può essere rimosso dalla riga di comando; gli switch documentati per sopprimerlo vengono ignorati su questo percorso. Fornire un binutils molto più recente fallisce in modo diverso: non riesce a elaborare affatto lo script di link di Free Pascal, producendo un output vuoto senza lo script e un muro di errori di relocation con esso
Un confine scoperto lungo la strada merita di essere conosciuto anche se non si incontra mai il problema del linker. Il linker esterno risolve i percorsi dei file oggetto relativamente alla directory di output dell'eseguibile anziché all'albero dei sorgenti, quindi una direttiva include-object relativa funziona solo quando la directory di output capita coincidere con la directory di lavoro in fase di compilazione. Una libreria non può fare quest'ipotesi sul progetto di un consumatore, il che è di per sé un motivo per preferire una libreria collegata a oggetti sparsi
Perché un compilatore C++ diverso non aiuta
L'idea successiva ovvia è ricompilare il lato C++ con un compilatore i cui oggetti Free Pascal sa leggere. Non funziona neanche, e la ragione è fondamentale più che una questione di switch. Una minima translation unit C++ contenente un template, compilata con ogni funzione di generazione codice disattivata, emette comunque simboli esterni weak, perché l'istanziazione di template e inline li produce per costruzione. Free Pascal rifiuta categoricamente quella classe di simboli. Fallisce anche la direzione opposta: un linker C++ mainstream non può consumare oggetti dell'altro compilatore a causa della medesima gestione delle sezioni COMDAT
Quindi il codice C++ non può essere consegnato a Free Pascal come oggetti per nessuna via disponibile. Può essere consegnato come DLL, che è quanto accaduto: l'encoder e la sua dipendenza di elaborazione immagini sono costruiti in un'unica libreria che espone due punti di ingresso C flat, e l'unità del backend Free Pascal li collega dinamicamente e si registra esattamente come fa il backend statico. La via Delphi e C++Builder non è stata toccata affatto, che è l'esito giusto; un problema di portabilità su una toolchain non deve perturbare la toolchain che già funzionava
La polarità è la cosa che morde davvero
Tra una bitmap bilevel di Windows e un encoder JBIG2 c'è un disallineamento di convenzioni che nessun type system coglierà. Una scanline di device-independent bitmap a un bit per pixel tratta un bit a 1 come bianco. L'encoder tratta un bit a 1 come nero. Consegnare le scanline invariate significa ottenere uno stream JBIG2 perfettamente valido del negativo fotografico della pagina
// DIB a un bit: bit a 1 significa bianco. Encoder JBIG2: bit a 1
// significa nero. Invertire ogni byte all'ingresso
for I := 0 to RowBytes - 1 do
Row[I] := Row[I] xor $FF;
Il metodo di verifica conta quanto la correzione. Confrontare le lunghezze degli stream compressi non dice nulla, perché un'immagine negativa comprime a una dimensione simile. Guardare la pagina prova solo che non è evidentemente invertita. Il controllo affidabile è renderizzare l'output di entrambe le vie di codifica, Pascal nativa ed esterna, in PNG e confrontarli byte per byte: entrambi gli encoder sono lossless sulla stessa immagine sorgente, quindi qualsiasi cosa diversa da una corrispondenza esatta è un bug di uno dei due. Quel confronto è ora un test di regressione permanente, ed è il tipo di asserzione che vale la pena costruire ogni volta che due implementazioni devono coincidere esattamente
Quale backend usare
Per contenuti bilevel generali, mezze tinte con dithering, line art, grafica mista, l'encoder MMR nativo in Pascal è adeguato e non ha costi di deployment. Per il testo scannerizzato, che è il caso per cui JBIG2 è stato progettato, la riduzione di dimensione vive nell'encoder esterno a symbol dictionary, perché fattorizza le forme ripetute dei glifi in un dizionario invece di ricodificare ogni occorrenza. Se Lei produce archivi di documenti scannerizzati, quella differenza è abbastanza grande da cambiare la pianificazione dello storage
La questione a monte, come viene prodotta in primo luogo l'immagine bilevel, conta quanto la dimensione dell'output; il rendering monocromatico basato su regioni è trattato nell'articolo sul rendering di regioni monocromatiche, e la strategia di dimensione a livello di documento in ottimizzazione della dimensione dei file PDF e subsetting dei font. Per set di scansioni con pagine ripetute, la deduplicazione batte spesso una compressione migliore, che è l'oggetto della deduplicazione percettiva delle immagini. La disponibilità di toolchain e backend per piattaforma è elencata nella pagina di prodotto di losLab PDF Developer Library