Una diapositiva médica escaneada, un mosaico de levantamiento aéreo, un fotograma de película archivado con rango dinámico completo. Estas son las imágenes que llegan como JPEG 2000, y llegan así por una razón. El formato mantiene 12 o 16 bits por canal, se comprime con una transformada wavelet en lugar del DCT de bloques que usa JPEG, y puede codificar la misma imagen sin pérdidas o con pérdidas desde un solo flujo de códigos. Cuando un documento creado a partir de esas fuentes tiene que convertirse en un PDF, la imagen tiene que viajar a través de un filtro que la especificación PDF reserva exactamente para este códec
HotPDF v2.228.0 restauró un motor de decodificación JPEG 2000 funcional para esa ruta. Una compilación anterior había incluido la unidad con funciones auxiliares que devolvían nil, por lo que la API existía pero no decodificaba nada. El motor actual vincula OpenJPEG 2.5.4 de forma estática y convierte una fuente JP2 o J2K en píxeles que HotPDF puede colocar en una página
El filtro JPXDecode en PDF
ISO 32000-1 define el filtro JPXDecode en la sección 7.4.9. Un XObject de imagen PDF nombra su compresión en la entrada /Filter del diccionario de flujo, y JPXDecode es el valor que indica que los datos del flujo son un flujo de códigos JPEG 2000 en lugar del JPEG base que transporta /DCTDecode. El filtro es lo que permite que un PDF contenga datos de imagen comprimidos por wavelet con alta profundidad de bits, y admite tanto los modos sin pérdidas como con pérdidas del códec, porque el modo es una propiedad del propio flujo de códigos y no de su contenedor
Vale la pena aferrarse a ese último punto. JPEG 2000 es un algoritmo único con un caso especial sin pérdidas, no dos formatos separados. La wavelet 5/3 reversible reconstruye las muestras originales exactamente; la wavelet 9/7 irreversible intercambia esa exactitud por un archivo más pequeño. Un decodificador trata ambos de la misma manera en el momento de la lectura, por lo que HotPDF necesita solo una ruta de decodificación para aceptar cualquier cosa que le arroje un flujo JPXDecode
Lo que el decodificador hace a los píxeles
Los XObjects de imagen PDF en el caso común esperan 8 bits por componente en DeviceGray o DeviceRGB. JPEG 2000 a menudo supera eso, y su modelo de componentes es más general que un ráster empaquetado, por lo que el decodificador tiene tres tareas que hacer antes de que los datos se puedan usar como una imagen normal
Primero, los componentes de alta profundidad de bits se remuestrean a 8 bits. Una muestra de 12 o 16 bits se reduce a una escala del rango de 0 a 255 para que el resultado sea un ráster ordinario de 8 bits. Los componentes con signo se cambian primero al rango sin signo. El detalle importa porque en sí mismo produce pérdida: un escaneo en escala de grises de 16 bits pierde su profundo rango tonal en el momento en que se convierte en una imagen PDF de 8 bits, que es el intercambio correcto para la salida en pantalla y la impresión, pero no para volver a archivar
Segundo, un espacio de color YCbCr (el códec lo llama SYCC) se convierte a RGB. JPEG 2000 a menudo almacena color en un espacio luma-croma para la eficiencia de compresión, la misma idea que usa JPEG base, y el decodificador aplica la transformada inversa estándar para que la página reciba RGB verdadero
Tercero, los componentes submuestreados se sobremuestrean mediante replicación del vecino más cercano. Los canales croma se almacenan con frecuencia a la mitad de la resolución, por lo que el decodificador lee cada componente en sus propias dimensiones y su propio factor de muestreo, luego replica las muestras para llevar cada canal al tamaño completo de la imagen antes del entrelazado. El vecino más cercano mantiene el paso barato; el croma que está llenando era de baja frecuencia para empezar, por lo que el costo visible es pequeño
Las cajas JP2 frente a un flujo de códigos J2K sin formato
Un archivo JPEG 2000 viene en dos formas, y HotPDF detecta cuál está leyendo a partir de los primeros bytes en lugar de la extensión del archivo. Un archivo JP2 es un contenedor estructurado en cajas: se abre con la caja de firma de doce bytes 00 00 00 0C 6A 50 20 20 y envuelve el flujo de códigos junto con cajas que describen el espacio de color, la resolución y los metadatos. Un flujo de códigos J2K sin formato no incluye ningún contenedor y comienza con el marcador SOC FF 4F FF 51. El decodificador lee esos bytes iniciales, reconoce la firma y selecciona el códec OpenJPEG coincidente para cada caso
Ambas formas se manejan porque ambas ocurren en la práctica. Los dispositivos de captura y archivos que necesitan los metadatos adicionales emiten JP2; las herramientas que buscan la carga útil más pequeña posible emiten el flujo de códigos simple. El tipo de formato se modela como una enumeración, TJpeg2000FileType, con los miembros jtInvalid, jtJP2, jtJ2K y jtJPT. El miembro JPT nombra la variante de flujo JPIP; el detector de firmas de bytes resuelve las dos formas que puede decodificar, JP2 y J2K, y reporta cualquier otra cosa como jtInvalid para que una entrada no admitida falle limpiamente en lugar de producir basura
uses
HPDFJpeg2000;
var
Decoder: THPDFJpeg2000Decoder;
Pixels: TJpeg2000ByteArray;
begin
Decoder := THPDFJpeg2000Decoder.Create;
try
if Decoder.LoadFromStream(Input) then // JP2 or J2K, auto-detected
if Decoder.GetImageData(Pixels) then
// Pixels is 8-bit interleaved, ColorComponents channels wide,
// row-major top to bottom: ready for a DeviceGray/DeviceRGB XObject.
ProcessRaster(Decoder.Width, Decoder.Height,
Decoder.ColorComponents, Pixels);
finally
Decoder.Free;
end;
end;
Sin pérdidas y con pérdidas en el lado de la codificación
El decodificador lee ambos modos sin que se le indique cuál es. La opción solo se convierte en un parámetro cuando se va en la otra dirección y se produce un archivo JPEG 2000, lo que HotPDF también puede hacer a través de la clase TJpeg2000Bitmap, un descendiente de TBitmap que carga y guarda datos ráster como JP2. Dos propiedades gobiernan la salida. LosslessCompression es un booleano que selecciona la wavelet reversible cuando es verdadera; CompressionQuality es un TJpeg2000QualityRange, un entero de 1 a 100 donde 1 es pequeño y feo y 100 es grande y fiel. Los valores predeterminados viven en constantes con nombre: Jpeg2000DefaultLosslessCompression es False y Jpeg2000DefaultLossyQuality es 80
La decisión depende del contenido. Sin pérdidas se adapta a una copia maestra, un escaneo médico o legal, cualquier cosa que pueda recodificarse más adelante y no deba acumular pérdida generacional. Con pérdidas con calidad 80 se adapta a una imagen destinada a la pantalla o la impresión, donde la degradación elegante de la wavelet ofrece un archivo notablemente más pequeño sin ningún artefacto que un lector pueda detectar. Hay una advertencia CMYK que señalar: el mapa de bits expone SetCMYK para marcar datos de cuatro canales como CMYK en lugar de RGBA, lo que es importante para las líneas de trabajo de impresión que mantienen intactas las separaciones
uses
HPDFJpeg2000;
var
Bmp: TJpeg2000Bitmap;
begin
Bmp := TJpeg2000Bitmap.Create;
try
Bmp.LoadFromStream(Source); // decode an existing JP2/J2K
Bmp.LosslessCompression := True; // reversible 5/3 wavelet
// or, for a smaller lossy file:
// Bmp.LosslessCompression := False;
// Bmp.CompressionQuality := 80; // matches the default
Bmp.SaveToStream(Output); // always writes a JP2 file
finally
Bmp.Free;
end;
end;
Por qué no hay una tubería de filtro de decodificación al cargar
Un hecho arquitectónico moldea cómo se usa todo esto, y es fácil asumir lo contrario. HotPDF no tiene un filtro general de imagen de decodificación al cargar. Cuando abre un PDF que ya contiene una imagen JPXDecode, el motor no decodifica ese flujo. Mantiene los bytes de JPEG 2000 exactamente como están, por lo que una copia de página o una fusión de documentos transporta la imagen intacta, byte por byte. El decodificador tiene un único punto de entrada, y se encuentra en el lado de la creación: el método AddImage basado en archivos, que envía por extensión de archivo para manejar fuentes .jp2, .j2k, .jpt y .jpc
Esa división es el diseño correcto en lugar de una limitación. Decodificar un flujo JPX incrustado en la carga, solo para volver a codificarlo al guardar, convertiría una imagen archivada sin pérdidas en una con pérdidas e inflaría cada fusión, todo por una imagen que solo pretendía mover de un PDF a otro. Pasar el flujo textualmente es una operación sin pérdidas y rápida. La decodificación se pospone al único momento en que es genuinamente requerida: cuando le entrega al motor un archivo JPEG 2000 desde el disco y le pide que rasterice esa imagen para colocarla en una página nueva. En ese punto el archivo tiene que convertirse en píxeles y el decodificador se ejecuta
Registro del soporte y colocación de una imagen
El registro de imágenes JPEG 2000 es opcional y depende del interruptor de compilación HPDF_REGISTER_JPEG2000_PICTURE, que está desactivado de forma predeterminada. La razón es un conflicto real, no precaución: registrar los formatos de archivo jp2, j2k y jpc globalmente con TPicture puede interferir con la detección del formato BLOB en la que se basa TppDBImage de ReportBuilder. Defina el interruptor cuando esa integración no esté en juego, y los formatos de archivo se registran para que TPicture los reconozca; déjelo sin definir y el envío de extensiones AddImage seguirá decodificando archivos JPEG 2000 directamente, porque esa ruta no pasa por TPicture en absoluto
Entendido esto, colocar una imagen JPEG 2000 es el mismo ritmo de tres llamadas que cualquier otra imagen de HotPDF. Entregue a AddImage una ruta .jp2 y un tipo de compresión para cómo se debe almacenar la imagen en la salida, luego posicione el índice de imagen devuelto en la página con ShowImage
var
Pdf: THotPDF;
ImgIndex: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.AddPage;
// The .jp2 source is decoded through the OpenJPEG backend, then
// re-embedded with the compression you request here.
ImgIndex := Pdf.AddImage('Scan_16bit.jp2', icJpeg);
// x, y, width, height in points; final 0 is the rotation angle.
Pdf.ShowImage(ImgIndex, 72, 72, 400, 300, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
La compresión que le pasa a AddImage controla cómo se vuelve a almacenar la imagen decodificada, no cómo se leyó. Un archivo JPEG 2000 decodificado a un mapa de bits puede volver a salir como un JPEG DCTDecode, un ráster Flate u otro filtro compatible, el que mejor se adapte al documento. La decodificación desde JP2 o J2K ocurre primero de todos modos, por lo que la misma llamada acepta una fuente comprimida por wavelet y la incrusta en cualquier forma que espere el resto de su línea de trabajo
Para un panorama más amplio de cómo las imágenes y las fuentes llegan a la salida generada, consulte nuestras notas sobre la salida de reportes con fuentes e imágenes. Cuando el documento que está ensamblando reutiliza contenido de archivos PDF existentes, el comportamiento de paso a través descrito aquí se combina con la mecánica de fusión y revisión en flujos de objetos y actualizaciones incrementales. El motor de decodificación JPEG 2000 se incluye como parte de HotPDF Component para Delphi y C++Builder, junto a las API de imágenes, fuentes y documentos cubiertas en otras partes de este blog