Artículo técnico

Compresión nativa de imágenes bitonales JBIG2 en PDF con Delphi

Un contrato escaneado tiene unos cientos de puntos por pulgada de tinta negra sobre papel blanco. Almacenado como un mapa de bits de un bit por píxel ya es pequeño, pero cien páginas así aún inflan un PDF más allá de lo que enviaría por correo electrónico. El filtro correcto cambia la aritmética. JBIG2 es la compresión de mayor proporción que define ISO 32000-1 para imágenes bitonales, y en una pila de texto escaneado habitualmente reduce a la mitad lo que produce CCITT Group 4. Este es el filtro a elegir cuando la entrada se envía por fax, se escanea o se reduce a dos colores, y HotPDF puede escribirlo directamente en un PDF

El formato logra esa proporción con dos ideas que un códec de imagen genérico no tiene. Modela cómo se sitúan los trazos negros sobre un fondo blanco y nota que una página escaneada es en su mayor parte las mismas formas de unos cientos de glifos repetidas miles de veces. Entender ambas cosas es lo que le permite elegir las opciones de codificación de forma deliberada en lugar de adivinar

Dónde se sitúa JBIG2 en la especificación PDF

ISO 32000-1 incluye JBIG2Decode entre los filtros de flujo en la sección 7.4.7, disponible desde PDF 1.4 en adelante. Se aplica solo a un lugar: los XObjects de imagen cuyo /BitsPerComponent es 1 y cuyo espacio de color se resuelve en un solo canal. Ese es el objetivo principal. JBIG2 es un códec bitonal, por lo que nunca compite con DCT o JPXDecode en fotografías. Compite con CCITTFaxDecode, los filtros de fax Group 3 y Group 4, exactamente en el tipo de página de dos tonos que produce un escáner de documentos

El decodificador consume la organización JBIG2 incrustada que el estándar llama perfil PDF, donde cada flujo de imagen contiene una secuencia de segmentos en lugar de un flujo de bits simple. Un flujo /JBIG2Globals opcional transporta segmentos compartidos en varias imágenes del mismo documento, que es el mecanismo que permite que el contenido repetido se almacene una vez para todo un archivo en lugar de una vez por página. HotPDF emite el flujo por imagen de forma predeterminada y mantiene libre el canal global a menos que lo solicite un backend

La arquitectura del codificador centrada en el backend

Un codificador JBIG2 completo es una gran pieza de software, y sus partes más agresivas históricamente han estado limitadas por patentes y se han distribuido bajo licencias que no se adaptan a todos los productos. HotPDF resuelve esa tensión separando la interfaz del motor. La unidad HPDFJBIG2 define las llamadas que hace el resto de la biblioteca, e incluye un codificador integrado modesto para que JBIG2 funcione desde el principio. Cuando necesita proporciones de nivel de producción, registra un motor más fuerte y la biblioteca delega en él, sin cambios en su código de llamada

El cambio es una única llamada de registro. Sin un backend registrado, el codificador recurre a su ruta integrada. Registre uno y todas las codificaciones posteriores pasarán a través de él

uses
  HPDFJBIG2;

// Query what is active, then optionally install a stronger engine.
if not IsJBIG2EncoderBackendAvailable then
  // Production backend not present: HotPDF uses its built-in MMR path.
  RegisterJBIG2EncoderBackend(MyVendorJBIG2Encode);

// Later, to return to the built-in behaviour:
// ClearJBIG2Backends;

El mismo enlace existe para la decodificación a través de RegisterJBIG2DecoderBackend, con IsJBIG2DecoderBackendAvailable para comprobarlo. Por esto una biblioteca incluye una pequeña ruta integrada más una costura para el backend en lugar de un codificador monolítico. La ruta integrada mantiene el binario ligero y libre de enredos de licencias, mientras que la costura permite a un equipo que ha obtenido la licencia de un codificador completo conectarlo sin tocar en absoluto la capa de escritura de PDF

Qué intercambian realmente las opciones de codificación

La codificación se configura a través de TJBIG2EncodeOptions, un registro con los campos Lossless, UseGlobalSegments, UseSymbolDictionary y LossyLevel. El contenedor amigable para componentes THPDFJBIG2Options publica Lossless, UseSymbolDictionary y LossyLevel para que puedan configurarse desde el Inspector de objetos, y se convierte al registro internamente. Tres intenciones impulsan las configuraciones

La reconstrucción sin pérdidas (lossless) mantiene cada píxel. Establezca Lossless en True y deje LossyLevel en cero, y el mapa de bits decodificado será idéntico bit a bit a la entrada. Esta es la única opción segura para arte lineal, dibujos técnicos y cualquier página donde un píxel omitido podría cambiar el significado, como una firma o un sello. La codificación de diccionario de símbolos activa la deduplicación sensible al texto y es la opción que separa JBIG2 de los filtros de fax. El nivel de pérdida, un número entero de 0 a 9, permite que un backend capaz intercambie fidelidad por tamaño al tratar marcas casi idénticas como el mismo símbolo. Cero significa sin pérdidas. El codificador integrado solo respeta la ruta sin pérdidas e ignora cualquier nivel de pérdida distinto de cero, por lo que los niveles más altos surten efecto solo una vez que se registra un backend que los implementa

var
  Options: TJBIG2EncodeOptions;
begin
  Options := DefaultJBIG2EncodeOptions;   // Lossless True, symbol dictionary on
  Options.Lossless := True;
  Options.LossyLevel := 0;                // 0 keeps every pixel
  Options.UseSymbolDictionary := True;    // dedupe repeated glyphs
  // Pass Options to a backend, or let THPDFJBIG2Options carry them.
end;

Los diccionarios de símbolos y por qué los escaneos de texto ganan

Una página de texto escaneado no es realmente una imagen de palabras. Es la misma letra e impresa varios cientos de veces, la misma t, la misma coma, cada instancia como una copia ligeramente ruidosa de una forma subyacente. Un diccionario de símbolos captura esa estructura. El codificador recopila las marcas distintas de la página en un diccionario, almacena cada forma una vez y luego registra la página como una lista de posiciones que hacen referencia a entradas del diccionario. Mil apariciones del mismo glifo cuestan un mapa de bits almacenado más mil colocaciones baratas

Aquí es precisamente donde JBIG2 se adelanta a CCITT Group 4. Group 4 codifica cada línea de escaneo frente a la línea superior sin noción de glifo, por lo que paga el costo completo de cada letra cada vez que aparece la letra. JBIG2 paga una vez. Cuando el mismo diccionario se promueve al flujo global a nivel de documento, el ahorro se acumula en un escaneo de varias páginas, porque las formas compartidas página tras página se almacenan una sola vez para todo el archivo. En texto denso la diferencia no es marginal. Es la razón por la que existe JBIG2

Región genérica y MMR para todo lo demás

No todas las imágenes bitonales son texto. Los mapas, esquemas, dibujos de ingeniería y páginas mixtas tienen arte lineal que ningún diccionario puede resumir. Para estos, JBIG2 codifica una región genérica, un rectángulo de píxeles comprimidos directamente sin ningún entrenamiento de símbolos. El estándar permite que una región genérica use MMR, la codificación READ modificada modificada que ya usa el fax Group 4, que modela cada fila de píxeles frente a la fila superior

Esta es la ruta que HotPDF incluye en su codificador integrado. Cuando no hay ningún backend registrado y la solicitud es sin pérdidas, la biblioteca comprime el mapa de bits como una única región genérica MMR y la envuelve en la estructura de segmento JBIG2 que requiere el perfil PDF. No necesita diccionario, pase de entrenamiento ni una segunda imagen de referencia, por lo que es la opción predeterminada confiable para arte lineal y contenido bitonal mixto. No igualará a un codificador completo con diccionario de símbolos en texto puro, pero siempre es correcto, siempre es sin pérdidas y siempre está presente. La superficie del codificador para esto es de una sola llamada

var
  Encoder: THPDFJBIG2Encoder;
  ImageData: TJBIG2ByteArray;
  Scanlines: TJBIG2ScanlineArray;  // one byte array per row, MSB-first
  W, H: Integer;
begin
  // Scanlines, W and H describe a 1-bit page; each row is (W + 7) div 8 bytes.
  Encoder := THPDFJBIG2Encoder.Create;
  try
    if Encoder.EncodeToByteArray(Scanlines, W, H, ImageData) then
      // ImageData now holds a JBIG2 stream ready for a /JBIG2Decode XObject.
      ;
  finally
    Encoder.Free;
  end;
end;

Activándolo cuando compila un documento

Para el uso diario no se toca la clase del codificador directamente. HotPDF expone JBIG2 como una opción de compresión de imagen en el documento. La enumeración THPDFImageCompressionType incluye icJBIG2 junto a las opciones Flate, JPEG y CCITT, y el documento incluye una propiedad JBIG2Options de tipo THPDFJBIG2Options que mantiene las configuraciones utilizadas cuando se selecciona esa compresión. Configure ambas antes de agregar las imágenes bitonales que desea comprimir de esta manera

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.ImageCompressionType := icJBIG2;     // route 1-bit images through JBIG2
    Pdf.JBIG2Options.Lossless := True;        // keep every pixel
    Pdf.JBIG2Options.UseSymbolDictionary := True;
    Pdf.JBIG2Options.LossyLevel := 0;
    // Add pages and place your scanned 1-bit images here.
  finally
    Pdf.Free;
  end;
end;

Una conveniencia que vale la pena notar es el complemento DBGridHotPDFExport, que renderiza un TDBGrid directamente a un PDF. Su salida es en su mayoría reglas y texto bitonal, por lo que un documento configurado para JBIG2 mantiene esas exportaciones compactas sin ningún manejo adicional de su parte. Dos temas relacionados en este blog profundizan en el flujo de trabajo circundante. Para ver cómo se establecen las imágenes y las fuentes cuando compila reportes, consulte salida de reportes con fuentes e imágenes en Delphi. Cuando un documento comprimido debe satisfacer un perfil de archivo, las reglas en validación PDF/A, PDF/X y PDF/UA en Delphi le indican qué filtros acepta un nivel de conformidad dado. JBIG2 se incluye como parte de HotPDF Component para Delphi y C++Builder, junto a las API de carga, edición y cifrado cubiertas en otras partes de este sitio