Artículo técnico

Mixed Raster Content en Delphi: PDF escaneado a JPEG + CCITT

losLab PDF Library for Delphi comprime una página escaneada con Mixed Raster Content a través de TPDFlib.CompressPageAsMRC. El método renderiza la página, umbraliza una máscara de primer plano de 1 bit a resolución completa, reduce el resto a un fondo JPEG, y reconstruye la página como dos imágenes: un fondo /DCTDecode y una máscara stencil /CCITTFaxDecode pintada en el color de relleno actual

La página para la que esto existe es la que llena todos los archivos documentales: un escaneo a color a 300 DPI de una factura o un contrato, guardado como un gran JPEG. Codifíquelo con una calidad que mantenga el texto legible y el archivo es grande; baje la calidad y las letras desarrollan halos de ringing y serifas emborronadas, porque lo que JPEG peor hace es un borde negro nítido sobre papel blanco. Mixed Raster Content deja de pedirle a un solo códec que haga dos trabajos

¿Por qué un solo JPEG hace que el texto escaneado sea borroso y grande a la vez?

Un solo JPEG maneja mal el texto escaneado porque el códec DCT gasta sus bits en gradientes suaves y aproxima los bordes duros con coeficientes de alta frecuencia que la cuantización descarta primero. El texto en un escaneo no es casi nada más que bordes duros. Mantener esos bordes limpios obliga a una configuración de calidad alta en toda la página, incluida la textura del papel, el logo y la mancha de café, ninguno de los cuales lo necesitaba

La idea de MRC divide la página por lo que cada región realmente es. La tinta se convierte en una imagen bilevel, donde un códec de fax como CCITT registra secuencias de píxeles negros y blancos sin pérdida y de forma muy compacta. Todo lo demás, el tono del papel, las fotos, los fondos de color, se convierte en una imagen de tono continuo que se puede guardar a una fracción de la resolución, porque nadie lee un fondo a 300 DPI. La capa bilevel mantiene el texto nítido a resolución completa; la capa de fondo mantiene el color a bajo costo

Escaneo JPEG único contra la división en capas de Mixed Raster Content creada por PDFlibPas CompressPageAsMRC en Delphi: el códec DCT emborrona los bordes duros del texto y fuerza calidad alta en toda la página, mientras la tinta se convierte en un stencil CCITT sin pérdida de 1 bit a 300 DPI y el tono del papel pasa a un fondo JPEG DeviceRGB de 75 DPI
Mixed Raster Content deja de pedirle a un solo códec que haga dos trabajos: la capa bilevel mantiene las letras nítidas a resolución completa y el fondo conserva el color a una fracción del costo, así que una calidad de 50 a 70 normalmente es invisible

¿Cómo divide CompressPageAsMRC una página en dos capas?

TPDFlib.CompressPageAsMRC ejecuta un pipeline fijo por página. Renderiza la página al DPI solicitado en un bitmap de 24 bits, clasifica cada píxel con una luminancia ponderada Rec. 601 (299/587/114 por mil) contra Threshold, y escribe el resultado en una máscara de 1 bit a la misma resolución. En paralelo encoge el mismo render por BGScale con un promedio de caja, de modo que un BGScale de 4 convierte cada bloque de píxeles de 4×4 en un píxel de fondo

Flujo de datos de CompressPageAsMRC en PDFlibPas para Delphi: un render de 24 bits a 300 DPI se umbraliza por luminancia Rec. 601 en un stencil ImageMask CCITTFaxDecode K 1 a resolución completa, se promedia por caja con BGScale 4 en un fondo DCTDecode DeviceRGB, y ambas imágenes reemplazan los operadores originales de la página con la máscara arriba
Ambas capas derivan de un solo render: el DPI decide qué tan fino es el trazo que sobrevive al umbralizado, BGScale intercambia detalle de fondo desde 2 para fotografías hasta 8 para texto plano, y la página reconstruida dibuja el stencil en el color de relleno actual

El contenido de la página se reemplaza entonces. Los operadores originales se eliminan, el fondo reducido se incrusta como una imagen /DCTDecode en /DeviceRGB (ISO 32000-1 §7.4.8), la máscara se codifica con el codificador CCITT de la biblioteca como una imagen /CCITTFaxDecode (§7.4.6) marcada con /ImageMask true, y ambas se dibujan a lo largo de toda la caja de página con la máscara arriba. La firma es corta:

function CompressPageAsMRC(Page: Integer; DPI: Double;
  BGScale, JPEGQuality, Threshold, Options: Integer): Integer;
  • Page es base 1 y debe estar dentro de PageCount
  • DPI es la resolución de render del raster de origen, de 50 a 1200
  • BGScale es el factor de reducción del fondo, de 1 a 8
  • JPEGQuality es la calidad JPEG del fondo, de 1 a 100
  • Threshold es el corte de luminancia, de 1 a 255; los píxeles en él o por encima son papel, los más oscuros son tinta
  • Options está reservado y debe ser 0

Un lote típico sobre un archivo escaneado se ve así. Llame a SetFillColor justo antes de cada página, por una razón que la siguiente sección explica

uses
  SysUtils, PDFlibrary;

procedure CompressScannedFile(const SrcFile, DstFile: string);
var
  Lib: TPDFlib;
  P, Replaced: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile(SrcFile, '') <> 1 then
      raise Exception.CreateFmt('Cannot open %s', [SrcFile]);
    for P := 1 to Lib.PageCount do
    begin
      Lib.SetFillColor(0, 0, 0);  // color de tinta de la capa stencil
      // máscara de 300 DPI, fondo de 75 DPI, calidad JPEG 60, corte en luma 160
      Replaced := Lib.CompressPageAsMRC(P, 300, 4, 60, 160, 0);
      if Replaced = 0 then
        raise Exception.CreateFmt('MRC failed on page %d', [P]);
    end;
    if Lib.SaveToFile(DstFile) <> 1 then
      raise Exception.CreateFmt('Cannot save %s', [DstFile]);
  finally
    Lib.Free;
  end;
end;

¿Por qué el primer plano se pinta en el color de relleno actual?

La capa de primer plano es una máscara stencil, y una máscara stencil no tiene color propio. ISO 32000-1 §8.9.6.2 define una imagen con /ImageMask true como una forma de 1 bit: los valores de muestra marcan dónde va la pintura, y la pintura es el color de nonstroking que esté activo en el estado de gráficos. Eso es exactamente lo que hace barata la capa, y también es la razón por la que CompressPageAsMRC no toma parámetro de color

TPDFlib.CompressPageAsMRC pinta la máscara con el color establecido por SetFillColor, así que el orden de las llamadas importa. El pintor emite el color de relleno de forma perezosa: SetFillColor registra el color y lo marca como cambiado, y el dibujo del stencil escribe el operador rg dentro de su propio bloque q/Q solo mientras ese cambio sigue pendiente. Dibuje una forma rellena en medio y el cambio pendiente lo consume esa forma en su lugar. Fijar el color justo antes de cada página, como en el lote de arriba, elimina la adivinanza. Negro es la elección normal; un azul marino oscuro funciona para formularios de tinta azul donde usted quiere que la salida se vea menos como una fotocopia

Secuencia de emisión perezosa del color de relleno para la capa stencil MRC de PDFlibPas en Delphi: SetFillColor registra el color como un cambio pendiente, el dibujo del stencil ImageMask escribe el operador rg dentro de su propio bloque q y Q mientras el cambio sigue pendiente, y una forma rellena intercalada consume el cambio pendiente en su lugar
Una máscara stencil no posee color, así que cada píxel de tinta toma un único color de relleno: un sello rojo, una firma azul y texto negro colapsan juntos, razón por la que SetFillColor debe llamarse justo antes de cada página
// Formulario de bolígrafo azul: renderice la capa de tinta en un azul marino oscuro
Lib.SetFillColor(0.08, 0.12, 0.35);
Replaced := Lib.CompressPageAsMRC(PageNo, 300, 4, 55, 170, 0);

La colocación sigue el origen de coordenadas de la biblioteca. CompressPageAsMRC lee GetOrigin y dibuja ambas imágenes desde el borde superior de la caja de página: coordenada superior 0 cuando el origen es superior izquierda (SetOrigin(1)), coordenada superior PageHeight con el origen por defecto en inferior izquierda. No necesita ajustar nada, pero explica por qué las capas caen correcto cualquiera sea el origen que su pipeline ya estaba usando

Una consecuencia de la semántica stencil vale la pena decir sin rodeos: cada píxel clasificado como tinta toma el mismo color. Un sello rojo, una firma azul y el texto negro del cuerpo acaban en un solo color de relleno en la capa de primer plano. Solo el JPEG de fondo conserva un rastro tenue y de baja resolución de los colores originales debajo

Elegir DPI, BGScale, Threshold y calidad JPEG

El valor de DPI decide la nitidez del texto, y 300 es el valor por defecto sensato para escaneos de oficina. La máscara se mantiene a la resolución completa del render, así que DPI fija qué tan fino es el trazo que sobrevive al umbralizado. Ir más arriba de la resolución del escaneo original no compra nada, porque el renderer solo estaría ampliando la imagen de escaneo incrustada. La memoria es el otro límite: una página US Letter a 300 DPI se renderiza a 2550×3300 píxeles, 25,245,000 bytes de datos de píxeles de 24 bits antes de derivar la máscara y el fondo, y la misma página en el techo de 1200 DPI son 10200×13200 píxeles, unos 404 MB

BGScale cambia detalle de fondo por tamaño. A 300 DPI, un factor de 4 da un fondo de 75 DPI, 638×825 píxeles para Letter, que es de sobra para el tono del papel y las áreas de color plano. Las páginas con fotografías o ilustraciones de semitonos que un lector realmente va a mirar merecen 2; texto plano sobre papel blanco puede tomar 6 u 8, ya que el fondo es apenas más que una tinción

Threshold es el parámetro que cambia el aspecto de la página. Subirlo clasifica más píxeles de gris medio como tinta, lo que engrosa los trazos finos y las marcas de lápiz, pero también arrastra ruido del escáner y sombras del papel como motas. Bajarlo da un papel más limpio y letras más delgadas, a veces rotas. Valores alrededor de 128 a 170 son un rango de partida razonable para texto negro sobre blanco; el balance entre ambos es el mismo que se discute en renderizar páginas PDF a monocromo de 1 bit en Delphi, donde el mismo umbral de luminancia mueve RenderPageToMonochromeFileEx. A diferencia de ese método, CompressPageAsMRC no tiene pasada de despeckle, así que los escaneos ruidosos quedan mejor servidos con un umbral un poco más bajo

JPEGQuality importa menos que en un escaneo de imagen única, porque el fondo ya no carga ningún borde de texto. Una calidad de 50 a 70 normalmente es invisible en un fondo de 75 DPI detrás de una máscara nítida

¿Qué le dice el valor de retorno, y qué no?

TPDFlib.CompressPageAsMRC devuelve el tamaño en bytes del contenido de página que reemplazó, y 0 cuando algo falló. Es un indicador de éxito, no una medida del ahorro. En una página escaneada típica el flujo de contenido original es de unas pocas docenas de bytes, apenas los operadores necesarios para colocar una imagen, así que un número positivo pequeño es el resultado normal incluso cuando la página se reduce por megabytes. Una página cuyo flujo de contenido original estaba vacío reporta 0 aunque fue reconstruida

El comportamiento ante fallas no es atómico, y esa es la parte para diseñar alrededor. Las verificaciones de argumentos, el render y la codificación JPEG corren antes de tocar la página, así que un BGScale fuera de rango, un JPEGQuality de 101, o una falla de render dejan la página exactamente como estaba. Pero una vez que el contenido original fue limpiado, una falla al incrustar una de las capas o al dibujarla también devuelve 0, y en ese punto la página puede estar ya parcialmente reconstruida. Trate cualquier 0 como razón para descartar el documento en memoria en lugar de guardarlo, y nunca escriba el resultado sobre el archivo de origen

Para saber lo que la conversión realmente ahorró, mida el documento. Comparar el largo de SaveToString antes y después es la verificación más rápida; para una respuesta por categoría, la auditoría de tamaño de archivos PDF con AuditDocumentSpace muestra si el peso salió de las imágenes como se esperaba

function MRCSavings(Lib: TPDFlib): Int64;
var
  Before: Int64;
  P: Integer;
begin
  Before := Length(Lib.SaveToString);
  for P := 1 to Lib.PageCount do
  begin
    Lib.SetFillColor(0, 0, 0);
    if Lib.CompressPageAsMRC(P, 300, 4, 60, 160, 0) = 0 then
      raise Exception.CreateFmt('Page %d failed; discard this document', [P]);
  end;
  Result := Before - Length(Lib.SaveToString);
end;

Dónde este MRC de dos capas se queda corto frente a ITU-T T.44

CompressPageAsMRC implementa un MRC deliberadamente simplificado. El modelo completo en ITU-T T.44 usa tres capas por franja: una imagen de fondo, una imagen de primer plano que lleva los colores de la tinta, y una máscara bilevel que selecciona entre ellas píxel por píxel, con espacio para varios pares de máscara y primer plano en una página. En términos de PDF la tercera capa sería una imagen de primer plano a color de baja resolución con máscara explícita (§8.9.6.3) en lugar de un stencil pintado en un color. PDF Library for Delphi omite la capa de color de primer plano, que es lo que hace tan pequeño el resultado y también por qué la tinta de color colapsa en el color de relleno

  • El método sirve para texto negro o de un solo color sobre papel escaneado; los documentos con código de color, el texto resaltado y los sellos de color pierden su color de tinta
  • La página reconstruida es raster puro: cualquier texto real, arte vectorial o caracteres seleccionables de la página original se vuelven píxeles, así que ejecútelo sobre escaneos, no sobre páginas digitales de nacimiento, donde el subsetting de fuentes y la reducción de imágenes son las herramientas correctas
  • La máscara usa el codificador CCITT de la biblioteca en modo bidimensional T.4, escrita como /CCITTFaxDecode con /K 1; el codificador también tiene un modo Group 4 vía AddImageCCITTFromBitmapStream, pero CompressPageAsMRC no lo expone, y no usa JBIG2
  • Las anotaciones y los campos de formulario no se aplanan; se quedan como objetos encima del contenido reconstruido
  • El fondo es /DeviceRGB, así que la salida no sirve para flujos de trabajo PDF/X-1a, y SetFillColor mismo se rechaza en modo PDF/X-1a
  • Un escaneo que ya necesita OCR debe reconocerse antes del MRC, mientras los píxeles originales aún existen a profundidad de color completa

FAQ

¿CompressPageAsMRC mantiene el texto buscable?

No. CompressPageAsMRC reemplaza el contenido de la página con dos imágenes, así que cualquier operador de texto de la página original, incluida una capa de texto OCR invisible, se elimina con ella. Ejecute OCR sobre la página comprimida después, o agregue la capa de texto de vuelta tras la compresión

¿Puedo comprimir solo algunas páginas de un documento?

Sí. CompressPageAsMRC trabaja sobre una página base 1 por llamada, así que usted elige las páginas y puede incluso variar BGScale y Threshold por página, por ejemplo un factor de 2 para un inserto de foto y 6 para páginas de texto plano

¿Qué valor debe tener el parámetro Options?

Pase 0. Options está reservado para uso futuro en la versión actual y no hay bits definidos, así que un valor distinto de cero no carga significado hoy

CompressPageAsMRC llegó en losLab PDF Library for Delphi v3.561.0 junto con los bloques de construcción de render, JPEG, CCITT y máscara stencil con los que se arma, todos disponibles como métodos públicos si necesita una variante del pipeline. La biblioteca, su soporte de C++Builder y la referencia completa de la API están en la página de producto de PDF Library for Delphi