HotPDF expone tres kernels de downsampling de imágenes a través de la propiedad ImageDownsampleKernel y una pasada separada de Floyd-Steinberg vía RenderOutputDither. La primera controla cómo se ven las fotografías después de que usted las achica para cumplir un presupuesto de tamaño; la segunda controla cómo se ven después de que una página se reduce a blanco y negro. Ninguna va activa por defecto, y ambas son opt-in por la misma razón: cuestan tiempo real
La presión que lleva a la gente aquí es conocida. Un contrato escaneado de 60 MB tiene que salir por una pasarela de correo que rechaza cualquier cosa de más de 10 MB, o un lote de estados de cuenta tiene que aterrizar en un dispositivo monocromo estilo fax que renderiza cada píxel gris como papel o tóner. Ambos problemas son problemas de resampling, y ambos tienen una respuesta rápida que se ve mal y una respuesta lenta que se ve bien
En qué se diferencian realmente los tres kernels
THPDFResampleKernel tiene tres valores, y viven en puntos genuinamente distintos de la curva de velocidad y calidad. rkHalftone delega en la histórica ruta GDI StretchBlt con el modo HALFTONE, que a pesar del nombre es filtrado de clase bilinear: rápido, adecuado para line art y screenshots, y proclive a esos bordes dentados que uno reconoce al instante en fotografías reescaladas. rkBicubic corre un kernel separable Catmull-Rom, y rkLanczos3 corre un sinc con ventana separable de soporte de tres lóbulos
Ambos kernels separables corren como dos pasadas, horizontal y luego vertical, con 6 a 12 taps por píxel de destino en Pascal puro. Eso es aproximadamente un orden de magnitud más lento que la ruta GDI, que es justamente por lo que rkHalftone sigue siendo el default. En un lote nocturno de miles de páginas la diferencia es una decisión de scheduling, no una preferencia. En un documento único que un usuario está esperando, Lanczos3 es casi gratis y visiblemente mejor
Dos propiedades de implementación valen la pena conocer porque determinan lo que la salida puede y no puede hacer. Los bordes hacen clamp por replicación de borde en lugar de envolver o desvanecer, y los pesos se normalizan por píxel de destino. Juntas, esas dos cosas significan que el resultado nunca suena por debajo del negro ni por encima del blanco, así que el clásico halo de overshoot de Lanczos alrededor de un borde duro no aparece como artefactos recortados en la imagen codificada
var
Pdf: THotPDF;
Info: THPDFLoadedResourceOptimizationInfo;
Changed: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('scanned-contract.pdf');
Pdf.ImageDownsampleKernel := rkLanczos3; // fijar antes de la llamada
Changed := Pdf.DownsampleLoadedImages(150, 82, 4096, Info);
if Changed > 0 then
begin
Writeln('resampled images: ', Info.DownsampledImageCount);
Writeln('kept calibrated : ', Info.PreservedCalibratedImageCount);
Pdf.SaveToFile('scanned-contract-150dpi.pdf');
end;
finally
Pdf.Free;
end;
end;
El argumento MinimumSavingsBytes, 4096 arriba, es el guard que mantiene honesta la operación. Recodificar una imagen que ya estaba eficientemente comprimida puede producir un stream más grande que el original, y un downsampler que reemplaza ciegamente cada imagen ocasionalmente agranda el archivo que le pidieron achicar. El umbral dice: solo comprometa el reemplazo cuando ahorre al menos esta cantidad de bytes. PreservedCalibratedImageCount reporta la otra decisión conservadora, las imágenes dejadas intactas porque cargan un espacio de color calibrado que el resampling comprometería
¿Por qué un coeficiente polinomial equivocado es tan difícil de ver?
Porque un kernel de interpolación roto no truena ni lanza excepciones, simplemente produce una imagen que se ve sutilmente mal de una manera que nadie puede atribuir. El kernel Catmull-Rom es cúbico por tramos, y su rama exterior en forma de Horner anidada es ((-0.5t + 2.5)t - 4)t + 2. Escriba ese coeficiente del medio como -5 en lugar de -4 y la función igual evalúa, igual devuelve números en un rango plausible, e igual produce una imagen
El daño aparece como W(1) evaluando a -1 donde debe ser 0. Los pesos negativos se acumulan, la suma hace clip en cero, y el síntoma visible es un gradiente cuyo extremo izquierdo va a negro y un borde de paso que pierde sus tonos intermedios. Nada en la falla apunta a un polinomio. La comprobación que lo caza en segundos es aritmética, no visual: un kernel interpolante debe satisfacer W(0) = 1 y W(±1) = W(±2) = 0, y cualquier kernel que falle esos tres puntos tiene un error de coeficiente, punto. Afirme esos tres valores en un unit test y toda la clase de defectos por dedoazo desaparece
Dithering Floyd-Steinberg, y dónde vive en el pipeline
La pasada de dithering es un problema distinto del resampling y vive en otro punto del pipeline. RenderOutputDither aplica difusión de error Floyd-Steinberg después de la composición de página, que es la única ubicación con sentido para una vista previa de impresión monocroma o una exportación estilo fax: la operación va de reducir un raster terminado a un bit por píxel, no de cómo se escalaron las imágenes individuales en el camino de entrada
El algoritmo en sí es corto. La luminancia se umbraliza al 50 por ciento, y el error de cuantización se difunde a cuatro vecinos con los clásicos pesos 7/16, 3/16, 5/16 y 1/16, hacia la derecha, abajo a la izquierda, abajo y abajo a la derecha. El píxel de salida es 0 o 255 en todos los canales. Lo que le da la alternativa ingenua, un umbral duro sin difusión, es convertir una fotografía en una silueta y perder cada tono medio que cargaba el contenido
// Dithering al renderizar para un dispositivo de vista previa monocromo
Pdf.RenderOutputDither := True;
// O aplique la misma pasada a un bitmap que ya posee. El bitmap debe
// ser pf24bit; la función devuelve False en lugar de adivinar
if not HPDFFloydSteinbergDitherBitmap(Preview) then
raise Exception.Create('dither expects a 24-bit bitmap');
// Acceso directo al kernel cuando reescala fuera del pipeline del documento
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
Small.SaveToFile('thumb.bmp');
finally
Small.Free;
end;
Hay un detalle de implementación en la difusión de error que muerde a todos una vez. El buffer de error de fila a fila tiene que acumular. Cada píxel de la siguiente fila recibe contribuciones de tres píxeles distintos de la fila actual, los taps 3/16, 5/16 y 1/16, y si el código asigna en lugar de sumar, cada escritura descarta la contribución previa y solo el último tap sobrevive. La imagen igual se ve dithered, que es lo que hace difícil notarlo, pero la textura está mal y la reproducción tonal se desvía. El test que lo caza es cuantitativo: ditheree un campo uniforme de gris medio y exija que la cobertura interior caiga entre el 40 y el 60 por ciento
¿Qué combinación debe usar un pipeline de reducción de tamaño?
Empareje el kernel con lo que las imágenes realmente son, y trate el dithering como un asunto del dispositivo, no de la compresión. Para escaneos fotográficos que deben sobrevivir a un presupuesto de tamaño, rkLanczos3 a 150 o 200 DPI conserva el detalle que la gente nota mientras corta el conteo de píxeles por un factor de cuatro o más. Para screenshots, diagramas y line art, rkHalftone genuinamente está bien y es mucho más rápido, porque esas imágenes tienen pocos gradientes tonales que preservar. Para un lote mixto donde no puede inspeccionar cada imagen, rkBicubic es el punto medio razonable: mejor que bilinear, con aproximadamente la mitad de los taps de Lanczos3
El downsampling es una palanca entre varias, y no siempre la más grande. Los escaneos bilevel suelen responder mucho mejor al encoder cubierto en compresión bilevel JBIG2 nativa en Delphi, donde la ganancia viene de diccionarios de símbolos y no de conteos de píxeles. Antes de decidir, ayuda saber qué hay realmente en el archivo, que es para lo que sirve extraer imágenes y sus decode filters: un inventario de objetos de imagen y su compresión existente le dice si el resampling tiene algo que ganar
Si está construyendo la superficie de vista previa que muestra el resultado, la misma ruta de render documentada en renderizar una página PDF a un bitmap es donde RenderOutputDither hace efecto, así que la vista previa dithered y la salida dithered vienen de una sola ruta de código en lugar de dos implementaciones que se separan
El principio amplio detrás de ambas funciones es que los ajustes de calidad deben ser explícitos y reversibles. HotPDF conserva el comportamiento histórico como default para que una aplicación existente actualice sin un cambio sorpresa en salida ni tiempos, y deja las rutas de mejor apariencia y más lentas a una asignación de propiedad de distancia. Ambas son parte del HotPDF Delphi PDF component, junto con la maquinaria de optimización de recursos y renderizado sobre la que se construyen