Articolo tecnico

Compressione nativa di immagini JBIG2 nei PDF Delphi

Un contratto scansionato consiste in poche centinaia di punti per pollice di inchiostro nero su carta bianca. Salvato come bitmap a un bit per pixel è già piccolo, ma cento di queste pagine ingrandiscono comunque un PDF oltre le dimensioni adatte per l'invio via email. Il filtro giusto cambia questa aritmetica. JBIG2 è la compressione con il rapporto più elevato che lo standard ISO 32000-1 definisce per le immagini a due livelli e, su una pila di testo scansionato, dimezza regolarmente ciò che produce CCITT Group 4. Questo è il filtro da utilizzare quando l'input è inviato via fax, scansionato o altrimenti ridotto a due colori, e HotPDF può scriverlo direttamente in un PDF

Il formato ottiene questo rapporto grazie a due idee che un codec immagine generico non possiede. Modella il modo in cui i tratti neri si posizionano su uno sfondo bianco e nota che una pagina scansionata è per lo più costituita dalle stesse poche centinaia di forme di glifi ripetute migliaia di volte. Comprendere entrambe le cose è ciò che consente di scegliere consapevolmente le opzioni di codifica invece di tirare a indovinare

Dove si colloca JBIG2 nelle specifiche PDF

Lo standard ISO 32000-1 elenca JBIG2Decode tra i filtri di flusso nella sezione 7.4.7, disponibile a partire da PDF 1.4. Si applica a un solo elemento: gli XObject immagine in cui /BitsPerComponent è 1 e il cui spazio colore si risolve in un singolo canale. Questo è il punto fondamentale. JBIG2 è un codec a due livelli, quindi non compete mai con DCT o JPXDecode sulle fotografie. Compete invece con CCITTFaxDecode, i filtri fax Group 3 e Group 4, esattamente sul tipo di pagina a due toni prodotta da uno scanner di documenti

Il decoder consuma l'organizzazione JBIG2 incorporata che lo standard chiama profilo PDF, in cui ogni flusso di immagine contiene una sequenza di segmenti anziché un semplice flusso di bit. Un flusso opzionale /JBIG2Globals trasporta segmenti condivisi tra più immagini nello stesso documento, che è il meccanismo che consente di memorizzare il contenuto ripetuto una sola volta per un intero file anziché una volta per pagina. HotPDF emette il flusso per singola immagine per impostazione predefinita e mantiene libero il canale globale a meno che un backend non lo richieda

L'architettura dell'encoder incentrata sui backend

Un encoder JBIG2 completo è un componente software di grandi dimensioni e le sue parti più aggressive sono state storicamente gravate da brevetti e distribuite con licenze non adatte a ogni prodotto. HotPDF risolve questa tensione separando l'interfaccia dal motore. L'unità HPDFJBIG2 definisce le chiamate effettuate dal resto della libreria e include un modesto encoder integrato in modo che JBIG2 funzioni fin dall'inizio. Quando si necessita di rapporti di compressione di livello produttivo, si registra un motore più potente e la libreria gli delega il lavoro, senza alcuna modifica al codice chiamante

Il passaggio è una singola chiamata di registrazione. Senza alcun backend registrato, l'encoder ripiega sul suo percorso integrato. Registrandone uno, ogni codifica successiva viene eseguita tramite esso

Diagramma della giuntura del codificatore JBIG2 di HotPDF in HPDFJBIG2, che affida la codifica a un backend registrato con licenza che rispetta LossyLevel da 0 a 9 quando è installato, e altrimenti usa la regione generica MMR lossless incorporata, prima di incapsulare l'output in un image XObject JBIG2Decode con JBIG2Globals opzionale
Così com'è, il percorso MMR integrato risponde a ogni chiamata mentre un motore con licenza prende il controllo nell'istante in cui si registra attraverso la seam
uses
  HPDFJBIG2;

// Chiedi cosa è attivo, poi eventualmente installa un engine più potente
if not IsJBIG2EncoderBackendAvailable then
  // Backend di produzione assente: HotPDF usa il suo percorso MMR interno
  RegisterJBIG2EncoderBackend(MyVendorJBIG2Encode);

// Più avanti, per tornare al comportamento interno:
// ClearJBIG2Backends;

Lo stesso hook esiste per la decodifica tramite RegisterJBIG2DecoderBackend, con IsJBIG2DecoderBackendAvailable per verificarlo. Questo è il motivo per cui una libreria viene distribuita con un piccolo percorso integrato unito a un punto di giunzione per i backend, invece di un unico encoder monolitico. Il percorso integrato mantiene il file binario leggero e libero da complicazioni di licenza, mentre il punto di giunzione consente a un team che ha concesso in licenza un encoder completo di inserirlo senza toccare minimamente il livello di scrittura del PDF

Cosa comportano realmente le opzioni di codifica

La codifica viene configurata tramite TJBIG2EncodeOptions, un record con i campi Lossless, UseGlobalSegments, UseSymbolDictionary e LossyLevel. Il wrapper compatibile con i componenti THPDFJBIG2Options pubblica Lossless, UseSymbolDictionary e LossyLevel in modo che possano essere impostati dall'Object Inspector e si converte internamente nel record. Tre intenti guidano le impostazioni

La ricostruzione senza perdita di dati (lossless) conserva ogni pixel. Impostando Lossless a True e lasciando LossyLevel a zero, la bitmap decodificata risulta identica bit per bit all'input. Questa è l'unica scelta sicura per disegni al tratto, disegni tecnici e qualsiasi pagina in cui un pixel mancante potrebbe alterare il significato, come una firma o un timbro. La codifica con dizionario dei simboli attiva la deduplicazione sensibile al testo ed è l'opzione che distingue JBIG2 dai filtri fax. Il livello di perdita (lossy level), un numero intero da 0 a 9, permette a un backend capace di scambiare la fedeltà con le dimensioni trattando segni quasi identici come lo stesso simbolo. Zero significa senza perdita di dati. L'encoder integrato onora solo il percorso lossless e ignora qualsiasi livello lossy diverso da zero, quindi i livelli più alti hanno effetto solo una volta registrato un backend che li implementa

var
  Options: TJBIG2EncodeOptions;
begin
  Options := DefaultJBIG2EncodeOptions;   // Lossless True, symbol dictionary attivo
  Options.Lossless := True;
  Options.LossyLevel := 0;                // 0 conserva tutti i pixel
  Options.UseSymbolDictionary := True;    // deduplica i glifi ripetuti
  // Passa Options a un backend, oppure lascia che le porti THPDFJBIG2Options
end;

I dizionari dei simboli e perché le scansioni di testo vincono

Una pagina di testo scansionato non è in realtà un'immagine di parole. È la stessa lettera e stampata diverse centinaia di volte, la stessa t, la stessa virgola, dove ogni istanza è una copia leggermente rumorosa di un'unica forma sottostante. Un dizionario dei simboli cattura questa struttura. L'encoder raccoglie i segni distinti sulla pagina in un dizionario, memorizza ogni forma una volta, e poi registra la pagina come un elenco di posizioni che fanno riferimento alle voci del dizionario. Mille occorrenze dello stesso glifo costano una bitmap memorizzata più mille posizionamenti economici

È proprio qui che JBIG2 supera CCITT Group 4. Group 4 codifica ogni linea di scansione rispetto a quella superiore senza alcuna nozione di glifo, quindi paga l'intero costo di ogni lettera ogni volta che questa appare. JBIG2 paga una sola volta. Quando lo stesso dizionario viene promosso al flusso globale a livello di documento, il risparmio si moltiplica per una scansione multipagina, poiché le forme condivise da una pagina all'altra vengono memorizzate una sola volta per l'intero file. Su testi densi la differenza non è marginale. È il motivo per cui esiste JBIG2

Diagramma HotPDF che confronta come CCITT Group 4 fattura ogni occorrenza di glifo separatamente, mentre JBIG2 memorizza ogni forma distinta una volta in un dizionario di simboli e la colloca con un riferimento economico, promuovendo le forme condivise allo stream JBIG2Globals a livello di documento così le scansioni multipagina vengono memorizzate una volta per forma
I glifi ripetuti si assestano in un dizionario così la pagina addebita una bitmap per forma più posizionamenti economici, e i globals estendono il risparmio attraverso le pagine

Regione generica e MMR per tutto il resto

Non tutte le immagini a due livelli sono testo. Mappe, schemi, disegni ingegneristici e pagine miste contengono disegni al tratto che nessun dizionario può riassumere. Per questi casi, JBIG2 codifica una regione generica, un rettangolo di pixel compresso direttamente senza alcun addestramento sui simboli. Lo standard consente a una regione generica di utilizzare MMR, la codifica READ modificata modificata che il fax Group 4 già utilizza, la quale modella ogni riga di pixel rispetto alla riga superiore

Questo è il percorso che HotPDF fornisce nel suo encoder integrato. Quando non è registrato alcun backend e la richiesta è lossless, la libreria comprime la bitmap come una singola regione generica MMR e la avvolge nella struttura del segmento JBIG2 richiesta dal profilo PDF. Non ha bisogno di alcun dizionario, di alcun passaggio di addestramento e di nessuna seconda immagine a cui fare riferimento, quindi è l'impostazione predefinita affidabile per i disegni al tratto e i contenuti a due livelli misti. Non eguaglierà un encoder completo con dizionario dei simboli sul testo puro, ma è sempre corretto, sempre senza perdita di dati e sempre presente. La superficie dell'encoder per questo richiede una sola chiamata

var
  Encoder: THPDFJBIG2Encoder;
  ImageData: TJBIG2ByteArray;
  Scanlines: TJBIG2ScanlineArray;  // un array di byte per riga, MSB per primo
  W, H: Integer;
begin
  // Scanlines, W e H descrivono una pagina a 1 bit; ogni riga è (W + 7) div 8 byte
  Encoder := THPDFJBIG2Encoder.Create;
  try
    if Encoder.EncodeToByteArray(Scanlines, W, H, ImageData) then
      // ImageData ora contiene uno stream JBIG2 pronto per un XObject /JBIG2Decode
      ;
  finally
    Encoder.Free;
  end;
end;

Attivazione durante la creazione di un documento

Per l'uso quotidiano non si interagisce direttamente con la classe dell'encoder. HotPDF espone JBIG2 come scelta per la compressione delle immagini nel documento. L'enumerazione THPDFImageCompressionType include icJBIG2 insieme alle opzioni Flate, JPEG e CCITT, e il documento possiede una proprietà JBIG2Options di tipo THPDFJBIG2Options che contiene le impostazioni utilizzate quando viene selezionata tale compressione. Configurarle entrambe prima di aggiungere le immagini a due livelli che si desidera comprimere in questo modo

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.ImageCompressionType := icJBIG2;     // manda le immagini a 1 bit su JBIG2
    Pdf.JBIG2Options.Lossless := True;        // conserva tutti i pixel
    Pdf.JBIG2Options.UseSymbolDictionary := True;
    Pdf.JBIG2Options.LossyLevel := 0;
    // Aggiungi le pagine e piazza qui le tue scansioni a 1 bit
  finally
    Pdf.Free;
  end;
end;

Una comodità degna di nota è il componente aggiuntivo DBGridHotPDFExport, che esegue il rendering di un TDBGrid direttamente in un PDF. Il suo output è in gran parte costituito da regole e testo a due livelli, quindi un documento configurato per JBIG2 mantiene tali esportazioni compatte senza alcuna gestione aggiuntiva da parte dell'utente. Due argomenti correlati su questo blog approfondiscono il flusso di lavoro circostante. Per scoprire come vengono inseriti immagini e font durante la creazione di report, si veda l'output di report con font e immagini in Delphi. Quando un documento compresso deve soddisfare un profilo di archiviazione, le regole descritte in validazione PDF/A, PDF/X e PDF/UA in Delphi indicano quali filtri sono accettati per un determinato livello di conformità. JBIG2 viene fornito come parte di HotPDF Delphi Component per Delphi e C++Builder, insieme alle API di caricamento, modifica e crittografia trattate altrove in questa sede