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 cambio è una singola chiamata di registrazione. Senza nessun backend registrato, l'encoder ripiega sul suo percorso integrato. Registrane uno e ogni codifica successiva passa per esso

Diagramma della giuntura dell'encoder 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 integrata prima di avvolgere l'output in un image XObject JBIG2Decode con JBIG2Globals opzionali
Fin dalla scatola il percorso MMR integrato risponde a ogni chiamata, mentre un motore con licenza subentra nel momento in cui si registra attraverso la giuntura
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

È esattamente qui che JBIG2 prende il sopravvento su CCITT Group 4. Group 4 codifica ogni riga di scansione contro la riga sopra di essa senza alcuna nozione di glifo, quindi paga il pieno costo di ogni lettera ogni volta che la lettera compare. JBIG2 paga una volta sola. Quando lo stesso dizionario viene promosso al flusso globals a livello di documento, il risparmio si accumula su una scansione multipagina, perché le forme condivise da pagina a pagina vengono memorizzate una sola volta per l'intero file. Su testo denso la differenza non è marginale. È il motivo per cui JBIG2 esiste

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 piazza tramite riferimento economico, promuovendo le forme condivise al flusso JBIG2Globals a livello di documento così le scansioni multipagina sono memorizzate una volta per forma
I glifi ripetuti finiscono in un dizionario così la pagina fattura una bitmap per forma più piazzamenti economici, e i globals allungano il risparmio sulle 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. Un encoder completo con dizionario dei simboli comprime ulteriormente il testo puro, mentre il percorso MMR è 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