5. Namespace `visual::` — imagens e comparação visual
16 funções sobre as imagens do documento e sobre a aparência renderizada das páginas.
As funções de comparação e qualidade renderizam a página em escala de cinza. Cada página é renderizada uma única vez e o resultado fica em cache.
5.1 Inventário de imagens
Seção intitulada “5.1 Inventário de imagens”visual::detect_images() e visual::count_images()
Seção intitulada “visual::detect_images() e visual::count_images()”check "Imagens no documento" { require visual::detect_images() print("total de imagens:", visual::count_images())
// Um catálogo sem imagem provavelmente está errado assert visual::count_images() >= 10, "catálogo com apenas #{visual::count_images()} imagem(ns)"}visual::get_image_resolution(n)
Seção intitulada “visual::get_image_resolution(n)”DPI efetivo da n-ésima imagem (1-based).
check "Resolução da imagem de capa" { dpi = visual::get_image_resolution(1) assert dpi >= 300, "imagem de capa com #{round(dpi)} DPI (mínimo 300)"}visual::get_image_size(n)
Seção intitulada “visual::get_image_size(n)”Dimensões em pixels: [largura, altura].
check "Tamanho da imagem" { tamanho = visual::get_image_size(1) print("primeira imagem:", tamanho.first(), "x", tamanho.last(), "pixels") require tamanho.first() >= 1000}visual::detect_image_color_space([n])
Seção intitulada “visual::detect_image_color_space([n])”Sem argumento: lista dos espaços de cor presentes no documento.
Com n: o espaço da n-ésima imagem.
check "Espaços de cor usados" { espacos = visual::detect_image_color_space() print("espaços presentes:", espacos.join(", "))
// Para offset, tudo deve ser CMYK assert !espacos.contains("DeviceRGB"), "há imagens RGB — converter para CMYK antes de imprimir"
// Conferindo uma imagem específica require visual::detect_image_color_space(1) == "DeviceCMYK"}visual::detect_low_resolution([dpi_minimo])
Seção intitulada “visual::detect_low_resolution([dpi_minimo])”Verdadeiro se alguma imagem está abaixo do mínimo (padrão 300).
check "Resolução geral" { assert !visual::detect_low_resolution(300), "há imagens abaixo de 300 DPI"
// Para impressão de grande formato o limite é outro assert !visual::detect_low_resolution(150), "há imagens abaixo de 150 DPI (mínimo para banner)"}Para saber quais imagens estão ruins (e não apenas se existem), percorra
doc.images— veja o capítulo 2:doc.images.each { |img|assert img.dpi >= 300,"imagem na página #{img.page_number}: #{img.dpi} DPI"}
5.2 Comparação visual entre arquivos
Seção intitulada “5.2 Comparação visual entre arquivos”Estas funções comparam uma página deste documento com uma página de outro arquivo. A assinatura geral é:
funcao(pagina_daqui, "outro.pdf" [, pagina_de_la])Se a página do outro arquivo for omitida, usa o mesmo número. Páginas de tamanhos diferentes são reamostradas antes da comparação.
visual::measure_ssim(pagina, "outro.pdf" [, pagina_b])
Seção intitulada “visual::measure_ssim(pagina, "outro.pdf" [, pagina_b])”Similaridade estrutural, de 0.0 a 1.0. É a medida que mais se aproxima da
percepção humana de “são a mesma página”.
check "Prova aprovada x arquivo final" { aprovado = "aprovados/revista_v1.pdf"
doc.pages.each { |page| ssim = visual::measure_ssim(page.number, aprovado) assert ssim > 0.99, "página #{page.number} mudou visualmente (SSIM #{ssim})" }}visual::compare_images(...) e visual::diff_pages(...)
Seção intitulada “visual::compare_images(...) e visual::diff_pages(...)”A mesma comparação, em escala de 0 a 100. São sinônimos — use o nome que fizer mais sentido no seu script.
check "Similaridade em porcentagem" { nota = visual::diff_pages(1, "versao_anterior.pdf") assert nota > 95, "capa mudou #{round(100 - nota)}% desde a última versão"}visual::pixel_diff(pagina, "outro.pdf" [, pagina_b, tolerancia])
Seção intitulada “visual::pixel_diff(pagina, "outro.pdf" [, pagina_b, tolerancia])”Porcentagem de pixels que diferem. A tolerância (padrão 10, de 0 a 255) ignora variações mínimas de renderização.
check "Quanto da página mudou" { percentual = visual::pixel_diff(4, "anterior.pdf") print("pixels alterados na página 4:", percentual, "%")
// Tolerância maior para ignorar antialiasing suave = visual::pixel_diff(4, "anterior.pdf", 4, 30) assert suave < 1.0, "mudança significativa na página 4"}visual::calculate_perceptual_hash([pagina])
Seção intitulada “visual::calculate_perceptual_hash([pagina])”Impressão digital visual da página: 64 bits em hexadecimal. Páginas parecidas têm hashes parecidos.
check "Impressão digital das páginas" { doc.pages.each { |page| print("página", page.number, "->", visual::calculate_perceptual_hash(page.number)) }}visual::detect_image_replacement(pagina, "outro.pdf" [, pagina_b, distancia])
Seção intitulada “visual::detect_image_replacement(pagina, "outro.pdf" [, pagina_b, distancia])”Verdadeiro se a página mudou visualmente além do tolerado. Compara os hashes
perceptuais; distancia é quantos bits podem diferir (padrão 10 de 64).
check "Nenhuma imagem trocada entre versões" { anterior = "aprovados/catalogo_v1.pdf"
doc.pages.each { |page| assert !visual::detect_image_replacement(page.number, anterior), "página #{page.number}: conteúdo visual substituído" }}5.3 Qualidade da imagem
Seção intitulada “5.3 Qualidade da imagem”visual::detect_image_artifacts([pagina])
Seção intitulada “visual::detect_image_artifacts([pagina])”Verdadeiro se a página apresenta blocagem típica de JPEG muito comprimido.
check "Sem artefatos de compressão" { doc.pages.each { |page| assert !visual::detect_image_artifacts(page.number), "página #{page.number} com blocagem de compressão visível" }}visual::estimate_image_quality([pagina])
Seção intitulada “visual::estimate_image_quality([pagina])”Nota de 0 a 100 derivada da blocagem detectada.
check "Nota de qualidade" { doc.pages.each { |page| nota = visual::estimate_image_quality(page.number) assert nota >= 70, "página #{page.number} com qualidade #{nota}/100 — recomprimida demais?" }}visual::detect_posterization([pagina])
Seção intitulada “visual::detect_posterization([pagina])”Verdadeiro se há poucos níveis de tom numa página com faixa tonal ampla — sinal de imagem salva com profundidade de cor insuficiente.
check "Degradês íntegros" { doc.pages.each { |page| assert !visual::detect_posterization(page.number), "página #{page.number}: possível posterização (poucos tons)" }}visual::detect_banding([pagina])
Seção intitulada “visual::detect_banding([pagina])”Verdadeiro se há degradê em degraus visíveis em vez de transição suave.
A detecção exige progressão monotônica com platôs largos — páginas de texto comum, que têm transições abruptas, não disparam alarme.
check "Degradês sem faixas" { // Fundos com degradê são onde o banding aparece doc.pages.each { |page| assert !visual::detect_banding(page.number), "página #{page.number} com banding no degradê" }}5.4 Exemplo completo
Seção intitulada “5.4 Exemplo completo”// aprovacao_visual.pdfl — compara o arquivo com a versão aprovada// Uso: pdfl run aprovacao_visual.pdfl nova_versao.pdfprofile "aprovacao-visual" {
const APROVADO = "aprovados/catalogo_v1.pdf" const DPI_MINIMO = 300
check "Inventário" tags: ["imagens"] { require visual::detect_images() print("imagens:", visual::count_images()) print("espaços de cor:", visual::detect_image_color_space().join(", ")) }
check "Resolução" tags: ["imagens", "prepress"] { doc.images.each { |img| assert img.dpi >= DPI_MINIMO, "imagem na página #{img.page_number}: #{round(img.dpi)} DPI (mínimo #{DPI_MINIMO})" } }
check "Qualidade" tags: ["imagens"] { doc.pages.each { |page| assert !visual::detect_image_artifacts(page.number), "página #{page.number} com artefatos de compressão" assert !visual::detect_banding(page.number), "página #{page.number} com banding" } }
check "Fidelidade à versão aprovada" tags: ["aprovacao"] { doc.pages.each { |page| ssim = visual::measure_ssim(page.number, APROVADO) assert ssim > 0.99, "página #{page.number} diferente da aprovada (SSIM #{ssim}, #{visual::pixel_diff(page.number, APROVADO)}% dos pixels)" } }}