5. Espacio de nombres visual:: — imágenes y comparación visual
← struct:: · Índice · Siguiente: prepress:: →
16 funciones sobre las imágenes del documento y sobre el aspecto renderizado de las páginas.
Las funciones de comparación y de calidad renderizan la página en escala de grises. Cada página se renderiza una sola vez y el resultado queda en caché.
5.1 Inventario de imágenes
visual::detect_images() y visual::count_images()
check "Imágenes del documento" {
require visual::detect_images()
print("total de imágenes:", visual::count_images())
// Un catálogo sin imágenes seguramente esté mal
assert visual::count_images() >= 10,
"catálogo con solo #{visual::count_images()} imagen(es)"
}
visual::get_image_resolution(n)
DPI efectivo de la enésima imagen (1-based).
check "Resolución de la imagen de portada" {
dpi = visual::get_image_resolution(1)
assert dpi >= 300, "imagen de portada con #{round(dpi)} DPI (mínimo 300)"
}
visual::get_image_size(n)
Dimensiones en píxeles: [anchura, altura].
check "Tamaño de la imagen" {
tamano = visual::get_image_size(1)
print("primera imagen:", tamano.first(), "x", tamano.last(), "píxeles")
require tamano.first() >= 1000
}
visual::detect_image_color_space([n])
Sin argumento: lista de los espacios de color presentes en el documento.
Con n: el espacio de la enésima imagen.
check "Espacios de color usados" {
espacios = visual::detect_image_color_space()
print("espacios presentes:", espacios.join(", "))
// Para offset, todo debe ser CMYK
assert !espacios.contains("DeviceRGB"),
"hay imágenes RGB — convertirlas a CMYK antes de imprimir"
// Comprobando una imagen concreta
require visual::detect_image_color_space(1) == "DeviceCMYK"
}
visual::detect_low_resolution([dpi_minimo])
Verdadero si alguna imagen está por debajo del mínimo (300 por defecto).
check "Resolución general" {
assert !visual::detect_low_resolution(300),
"hay imágenes por debajo de 300 DPI"
// Para impresión de gran formato el límite es otro
assert !visual::detect_low_resolution(150),
"hay imágenes por debajo de 150 DPI (mínimo para una lona)"
}
Para saber qué imágenes están mal (y no solo si las hay), recorre
doc.images; consulta el capítulo 2:doc.images.each { |img| assert img.dpi >= 300, "imagen en la página #{img.page_number}: #{img.dpi} DPI" }
5.2 Comparación visual entre archivos
Estas funciones comparan una página de este documento con una página de otro archivo. La firma general es:
funcion(pagina_de_aqui, "otro.pdf" [, pagina_de_alli])
Si se omite la página del otro archivo, se usa el mismo número. Las páginas de tamaños distintos se remuestrean antes de la comparación.
visual::measure_ssim(pagina, "otro.pdf" [, pagina_b])
Similitud estructural, de 0.0 a 1.0. Es la medida que más se acerca a la
percepción humana de «son la misma página».
check "Prueba aprobada frente al archivo final" {
aprobado = "aprobados/revista_v1.pdf"
doc.pages.each { |page|
ssim = visual::measure_ssim(page.number, aprobado)
assert ssim > 0.99,
"la página #{page.number} cambió visualmente (SSIM #{ssim})"
}
}
visual::compare_images(...) y visual::diff_pages(...)
La misma comparación, en escala de 0 a 100. Son sinónimos: usa el nombre que tenga más sentido en tu script.
check "Similitud en porcentaje" {
nota = visual::diff_pages(1, "version_anterior.pdf")
assert nota > 95, "la portada cambió un #{round(100 - nota)}% desde la última versión"
}
visual::pixel_diff(pagina, "otro.pdf" [, pagina_b, tolerancia])
Porcentaje de píxeles que difieren. La tolerancia (10 por defecto, de 0 a 255) ignora las variaciones mínimas de renderizado.
check "Cuánto cambió la página" {
porcentaje = visual::pixel_diff(4, "anterior.pdf")
print("píxeles modificados en la página 4:", porcentaje, "%")
// Tolerancia mayor para ignorar el antialiasing
suave = visual::pixel_diff(4, "anterior.pdf", 4, 30)
assert suave < 1.0, "cambio significativo en la página 4"
}
visual::calculate_perceptual_hash([pagina])
Huella visual de la página: 64 bits en hexadecimal. Las páginas parecidas tienen huellas parecidas.
check "Huella de las páginas" {
doc.pages.each { |page|
print("página", page.number, "->", visual::calculate_perceptual_hash(page.number))
}
}
visual::detect_image_replacement(pagina, "otro.pdf" [, pagina_b, distancia])
Verdadero si la página cambió visualmente más de lo tolerado. Compara las
huellas perceptuales; distancia es cuántos bits pueden diferir (10 de 64 por
defecto).
check "Ninguna imagen cambiada entre versiones" {
anterior = "aprobados/catalogo_v1.pdf"
doc.pages.each { |page|
assert !visual::detect_image_replacement(page.number, anterior),
"página #{page.number}: contenido visual sustituido"
}
}
5.3 Calidad de la imagen
visual::detect_image_artifacts([pagina])
Verdadero si la página presenta el bloqueo típico de un JPEG muy comprimido.
check "Sin artefactos de compresión" {
doc.pages.each { |page|
assert !visual::detect_image_artifacts(page.number),
"la página #{page.number} tiene bloqueo de compresión visible"
}
}
visual::estimate_image_quality([pagina])
Nota de 0 a 100 derivada del bloqueo detectado.
check "Nota de calidad" {
doc.pages.each { |page|
nota = visual::estimate_image_quality(page.number)
assert nota >= 70,
"la página #{page.number} tiene calidad #{nota}/100 — ¿recomprimida de más?"
}
}
visual::detect_posterization([pagina])
Verdadero si hay pocos niveles tonales en una página con un rango tonal amplio, señal de una imagen guardada con profundidad de color insuficiente.
check "Degradados íntegros" {
doc.pages.each { |page|
assert !visual::detect_posterization(page.number),
"página #{page.number}: posible posterización (pocos tonos)"
}
}
visual::detect_banding([pagina])
Verdadero si hay un degradado en escalones visibles en lugar de una transición suave.
La detección exige una progresión monótona con mesetas anchas: las páginas de texto normal, que tienen transiciones abruptas, no disparan la alarma.
check "Degradados sin bandas" {
// Los fondos con degradado son donde aparece el banding
doc.pages.each { |page|
assert !visual::detect_banding(page.number),
"la página #{page.number} tiene banding en el degradado"
}
}
5.4 Ejemplo completo
// aprobacion_visual.pdfl — compara el archivo con la versión aprobada
// Uso: pdfl run aprobacion_visual.pdfl nueva_version.pdf
profile "aprobacion-visual" {
const APROBADO = "aprobados/catalogo_v1.pdf"
const DPI_MINIMO = 300
check "Inventario" tags: ["imagenes"] {
require visual::detect_images()
print("imágenes:", visual::count_images())
print("espacios de color:", visual::detect_image_color_space().join(", "))
}
check "Resolución" tags: ["imagenes", "prepress"] {
doc.images.each { |img|
assert img.dpi >= DPI_MINIMO,
"imagen en la página #{img.page_number}: #{round(img.dpi)} DPI (mínimo #{DPI_MINIMO})"
}
}
check "Calidad" tags: ["imagenes"] {
doc.pages.each { |page|
assert !visual::detect_image_artifacts(page.number),
"la página #{page.number} tiene artefactos de compresión"
assert !visual::detect_banding(page.number),
"la página #{page.number} tiene banding"
}
}
check "Fidelidad a la versión aprobada" tags: ["aprobacion"] {
doc.pages.each { |page|
ssim = visual::measure_ssim(page.number, APROBADO)
assert ssim > 0.99,
"la página #{page.number} difiere de la aprobada (SSIM #{ssim}, #{visual::pixel_diff(page.number, APROBADO)}% de los píxeles)"
}
}
}