HotPDF, el componente Delphi PDF nativo, decodifica imágenes CCITTFaxDecode con soporte completo de la extensión de modo sin compresión T.4/T.6 y de la recuperación de filas dañadas manejada por DamagedRowsBeforeError. El modo sin compresión se entra únicamente por el código de extensión que termina en 111, y las filas dañadas se reparan solo para datos Group 3 con EndOfLine activado. Todo lo demás falla de forma limpia en lugar de producir una página torcida
Las imágenes con codificación fax son la cola larga de la decodificación de imágenes PDF. La mayoría de los streams CCITT que uno se encuentra salieron de un driver de escáner o de un pipeline de captura de documentos que solo emite Group 4 plano, y un decodificador que maneja el caso común pasa todas las pruebas que le lance. Luego llega un archivo de un servidor de fax de quince años atrás, una página se renderiza como ruido diagonal desde la fila 400 hacia abajo, y usted descubre que las dos partes de las recomendaciones ITU-T que nadie implementa son exactamente las dos partes que ese archivo usa. Este artículo recorre esas dos partes tal como las maneja HotPDF, y los lugares donde un decodificador puede equivocarse mientras sigue pareciendo correcto
¿Qué parámetros CCITTFaxDecode cambian realmente el bit stream?
Cuatro entradas del diccionario DecodeParms cambian cómo el decodificador enmarca el bit stream: K, EndOfLine, EncodedByteAlign y DamagedRowsBeforeError. ISO 32000-1 §7.4.6 (Tabla 11) las define, y equivocarse con cualquiera de ellas desplaza cada fila posterior al primer desajuste. BlackIs1, Columns, Rows y EndOfBlock también importan, pero afectan la polaridad, la geometría y dónde se detiene la decodificación, no cómo se encuentra un límite de fila
Kelige el esquema de codificación: negativo significa Group 4 puramente bidimensional (T.6), cero significa Modified Huffman unidimensional (Group 3 1D), y positivo significa Group 3 2D mixto, donde un bit de etiqueta después de cada EOL dice si la siguiente fila es 1D o 2DEndOfLinedice que cada fila Group 3 va precedida por un código EOL, once bits en cero seguidos de un unoEncodedByteAligndice que cada fila codificada arranca en un límite de byte, así que el decodificador debe descartar los bits de relleno antes de leerlaDamagedRowsBeforeErrores cuántas filas corruptas puede tolerar el decodificador antes de rendirse, con un valor predeterminado de 0
HotPDF lee todos estos valores del diccionario de la imagen cuando usted llama THotPDF.ExtractLoadedImage o THotPDF.DecodeLoadedImageStream. Una desviación deliberada vale conocerla: cuando Columns falta, HotPDF cae de vuelta al Width de la imagen en lugar del valor predeterminado de la especificación, 1728, y cuando Rows falta usa Height. Un productor que omite Columns en un escaneo A4 de 2480 píxeles está mal, pero es lo bastante común como para que confiar en la geometría de la imagen dé una página utilizable donde el valor literal daría basura
¿Por qué 000000 no puede tratarse como el fin del bloque?
En filas bidimensionales, seis bits en cero al inicio son ambiguos, y un decodificador que los lea como el inicio de EOFB malinterpretará una entrada válida de modo sin compresión como el fin de la imagen. Group 4 no tiene EOLs entre filas; los únicos codes cargados de ceros son el EOFB, que son dos EOLs espalda con espalda, y los códigos de extensión de ITU-T T.4 y T.6. Ambos comienzan con 000000, y el séptimo bit es donde divergen
El decodificador CCITT de HotPDF resuelve el prefijo bit a bit. Un séptimo bit de 1 significa que los siguientes tres bits son un código de extensión, siendo el patrón completo 0000001xxx. Solo xxx = 111 entra al modo sin compresión; cualquier otro valor de extensión se trata como malformado, ya que un filtro fax de PDF no tiene uso para los demás. Un séptimo bit de 0 solo puede continuar un EOL, así que el decodificador entonces exige cuatro bits en cero más y un 1 de cierre, once ceros y un uno en total, antes de reportar EOFB. Cualquier cosa más corta o más larga es un error, no un fin de imagen indulgente
La versión indulgente es tentadora porque hace que toda una clase de archivos rotos se vea bien: la decodificación simplemente se detiene temprano y usted recibe una imagen corta sin excepción. También es exactamente el bug que trunca faxes legítimos en el primer tramo de datos sin compresión, que en un formulario escaneado suele ser una firma densa o un sello. HotPDF elige la lectura estricta, así que una imagen corta significa que el encoder de verdad terminó ahí
¿Cómo codifica píxeles el modo sin compresión?
El modo sin compresión reemplaza la codificación por longitudes de corrida con patrones literales de píxeles, cada code word cargando a lo sumo cinco píxeles, hasta que un código de salida regresa a lo normal. Las filas unidimensionales entran con el código de 12 bits 000000001111; las bidimensionales usan la extensión de 10 bits 0000001111 descrita arriba. Los encoders cambian a este modo para regiones tipo halftone donde corridas de uno o dos píxeles costarían más que los píxeles mismos
Dentro del modo, los code words se cuentan por sus ceros iniciales. Cero a cuatro ceros seguidos de un 1 significan esa cantidad de píxeles blancos y luego un píxel negro. Cinco ceros seguidos de un 1 significan cinco píxeles blancos y nada más. Seis a diez ceros seguidos de un 1 son códigos de salida: los ceros más allá de seis emiten hasta cuatro píxeles blancos más, y el bit único después del código de salida, el bit de etiqueta, fija el color de la siguiente corrida cuando la codificación por corridas ordinaria se reanuda
{ Palabras de código del modo sin compresión (ITU-T T.4 / T.6)
W = píxel blanco, B = píxel negro, T = bit de etiqueta
1 B
01 W B
001 W W B
0001 W W W B
00001 W W W W B
000001 W W W W W
0000001T salida, luego retoma corridas con color T
00000001T W, salida
000000001T W W, salida
0000000001T W W W, salida
00000000001T W W W W, salida
más de diez ceros: malformado }
El bit de etiqueta es la parte sutil. La codificación por corridas tiene estado: una fila 1D alterna corridas blancas y negras, y una fila 2D rastrea el color del elemento cambiante actual a0. Después del código de salida, el decodificador tiene que reanudar con el color que la etiqueta nombre, que puede coincidir o no con el último píxel literal. El decodificador de HotPDF registra un cambio de color en la posición actual cuando la etiqueta difiere del color que venía rastreando, así la siguiente corrida arranca del color correcto. Ignore la etiqueta y el resto de la fila sale con los colores invertidos, y en modo 2D cada fila siguiente hereda el daño a través de su línea de referencia
¿Qué recupera DamagedRowsBeforeError y qué no?
DamagedRowsBeforeError permite a HotPDF reemplazar una fila corrupta y continuar, pero solo cuando K es 0 o positivo y EndOfLine es true. Esas dos condiciones no son arbitrarias. La recuperación necesita una forma de encontrar dónde arranca la siguiente fila después de que los bits dejan de tener sentido, y el único punto de referencia confiable en un stream CCITT es el código EOL. Los datos Group 4 no tienen EOLs, y los datos Group 3 sin EOLs obligatorios no garantizan que exista uno, así que en ambos casos una fila dañada termina la decodificación sin importar el valor del parámetro
Cuando una fila falla, el decodificador de HotPDF escanea hacia adelante hasta el siguiente EOL, una corrida de al menos once bits en cero seguidos de un uno, y sustituye una fila de reemplazo. La primera fila dañada después de una buena se reemplaza con una copia de la fila anterior, que en una página de texto escaneado suele quedar lo bastante cerca como para ser invisible. Una fila dañada que sigue directamente a otra fila dañada se reemplaza con una fila toda blanca, porque copiar una copia embarraría una línea de texto en una barra negra alta. El sustituto se convierte entonces en la línea de referencia de la siguiente fila 2D, que es lo que mantiene decodificable un stream de modo mixto después de la reparación
Dos límites son fáciles de pasar por alto. El presupuesto es acumulativo sobre toda la imagen, no por tanda de filas malas, así que DamagedRowsBeforeError 3 tolera tres filas dañadas en total y la cuarta falla la decodificación. Y una decodificación fallida en HotPDF es todo o nada: THotPDF.ExtractLoadedImage devuelve nil en lugar de un bitmap con el borde inferior ragged. THPDFLoadedImageInfo.Decodable reporta que el filtro está soportado, no que este stream en particular esté intacto, así que siempre revise el bitmap contra nil
Por qué el escaneo debe detenerse al inicio del EOL
El escaneo de resincronización deja al lector en el primer bit en cero del EOL, no después de él. El decodificador tiene un único bucle de enmarcado que consume EOLs, revisa el EOL doble que termina el bloque y lee el bit de etiqueta de modo mixto. Si el escaneo de recuperación se traga el EOL mismo, ese bucle ve una fila sin EOL, la trata como dañada también, y quema una segunda unidad del presupuesto mientras tira una fila buena. Peor aún, un bit de etiqueta saltado significa que una fila 2D se decodifica como 1D o al revés. La misma regla aplica cuando un decodificador de corridas 1D golpea un EOL inesperado a media fila: rebobina a donde el código de corrida empezó, así el marcador sigue ahí para el escaneo de recuperación
Inventariar imágenes CCITT en un PDF cargado
Antes de confiar en un lote de faxes archivados, liste qué imágenes usan CCITTFaxDecode y cuáles fallan al decodificar. THotPDF.GetLoadedImageInfo le da geometría y nombre de filtro sin decodificar nada, y THotPDF.ExtractLoadedImage le dice si el stream sobrevivió. Para el panorama más amplio de cómo el despachador de imágenes enruta cada filtro, vea la guía de extracción de imágenes PDF cargadas y sus filtros de decodificación
var
Pdf: THotPDF;
Info: THPDFLoadedImageInfo;
Bmp: TBitmap;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('fax-archive.pdf', '') <= 0 then
Exit;
for I := 0 to Pdf.GetLoadedImageCount - 1 do
begin
if not Pdf.GetLoadedImageInfo(I, Info) then
Continue;
if Pos('CCITT', UpperCase(String(Info.Filter))) = 0 then
Continue;
Bmp := Pdf.ExtractLoadedImage(I); // nil cuando el stream está demasiado dañado
try
if Bmp = nil then
Writeln(Format('obj %d: %dx%d CCITT failed to decode',
[Info.ObjectNumber, Info.Width, Info.Height]))
else
Bmp.SaveToFile(Format('fax_%d.bmp', [I]));
finally
Bmp.Free; // quien llama es dueño del bitmap
end;
end;
finally
Pdf.Free;
end;
end;
Para las fallas, conserve la evidencia. THotPDF.ExtractLoadedImageToStream con el modo lismEncoded predeterminado escribe los bytes almacenados del stream sin tocar, que es lo que usted quiere entregar a un segundo decodificador, envolver en un contenedor TIFF para inspección o adjuntar a un reporte de bug. Re-codificar un fax dañado solo esconde dónde estuvo el daño
procedure SaveRawFax(Pdf: THotPDF; ImageIndex: Integer; const FileName: string);
var
Fs: TFileStream;
begin
Fs := TFileStream.Create(FileName, fmCreate);
try
if not Pdf.ExtractLoadedImageToStream(ImageIndex, Fs, lismEncoded) then
raise Exception.CreateFmt('Image %d has no stream data', [ImageIndex]);
finally
Fs.Free;
end;
end;
Si usted recorre los streams de imagen por su cuenta vía THotPDF.CollectLoadedImageStreams, pase cada THPDFStreamObject a THotPDF.DecodeLoadedImageStream y obtendrá la misma ruta CCITT, parámetros y reglas de recuperación. Libere la lista devuelta pero no los objetos de stream dentro de ella, que siguen siendo propiedad del documento
¿Debe subir DamagedRowsBeforeError en archivos que no controla?
No, no editando el PDF, y usualmente para nada. El parámetro le corresponde al productor, que es la única parte que sabe si su canal de transmisión tira bits. Un archivo que declara DamagedRowsBeforeError 0 y luego contiene una fila dañada le está diciendo que la copia del archivo está corrupta, y el resultado honesto es una falla de decodificación que usted puede registrar. Parchar filas en silencio sobre un contrato o un expediente médico convierte un defecto detectable en una imagen de apariencia plausible con una línea faltante, que es un desenlace peor para cualquiera que después confíe en ella
El patrón más seguro para archivos no confiables es mantener la decodificación estricta, registrar qué números de objeto fallaron y enrutar esos a un humano o a un decodificador separado. Si el archivo es grande y viene de muchas fuentes, correr la decodificación de imágenes fuera de proceso limita lo que un stream hostil puede hacerle a la aplicación anfitriona; el artículo sobre aislar codecs de imagen al procesar PDF no confiables cubre ese montaje, y la guía de compresión bilevel JBIG2 compara el otro filtro bilevel que encontrará en los mismos archivos escaneados
Preguntas frecuentes
¿HotPDF soporta el modo sin compresión CCITT en imágenes Group 4?
Sí. HotPDF reconoce la extensión de 10 bits 0000001111 en filas bidimensionales y el código de entrada de 12 bits 000000001111 en filas unidimensionales, decodifica los code words literales y honra el bit de etiqueta de salida cuando se reanuda la codificación por corridas
¿Por qué DamagedRowsBeforeError no tiene efecto en mi fax Group 4?
La recuperación exige marcadores EOL para resincronizar. HotPDF aplica DamagedRowsBeforeError solo cuando K es 0 o positivo y EndOfLine es true; los datos Group 4 (K negativo) no tienen EOLs, así que cualquier fila dañada termina la decodificación
¿Qué devuelve ExtractLoadedImage para una imagen CCITT dañada?
THotPDF.ExtractLoadedImage devuelve nil cuando el daño excede lo que DamagedRowsBeforeError permite o no puede recuperarse en absoluto. Nunca devuelve un bitmap parcial, así que pruebe contra nil y use ExtractLoadedImageToStream para conservar los bytes crudos
HotPDF maneja imágenes CCITT, JBIG2, JPEG, JPEG 2000 y Flate en PDFs cargados desde código Delphi y C++Builder plano, sin DLL externa para la ruta fax. Detalles y ediciones están en la página del componente HotPDF Delphi PDF