5. Espace de noms visual:: — images et comparaison visuelle

struct:: · Sommaire · Suivant : prepress::

16 fonctions sur les images du document et sur l'aspect rendu des pages.

Les fonctions de comparaison et de qualité rendent la page en niveaux de gris. Chaque page n'est rendue qu'une fois, puis mise en cache.


5.1 Inventaire des images

Fonction Rôle
visual::detect_images() Vrai s'il y a des images
visual::count_images() Nombre total d'images
visual::get_image_resolution(n) DPI effectif de la n-ième image (à partir de 1)
visual::get_image_size(n) Dimensions en pixels [largeur, hauteur]
visual::detect_image_color_space([n]) Liste des espaces, ou celui de la n-ième image
visual::detect_low_resolution([dpi_min]) Vrai s'il existe une image sous le seuil (300 par défaut)
check "Image inventory" {
  require visual::detect_images()
  print("total images:", visual::count_images())
  print("spaces present:", visual::detect_image_color_space().join(", "))

  // L'offset exige du CMJN partout
  assert !visual::detect_image_color_space().contains("DeviceRGB"),
    "there are RGB images — convert to CMYK before printing"

  assert !visual::detect_low_resolution(300),
    "there are images below 300 DPI"
}

Pour savoir lesquelles posent problème, parcourez doc.images — voir le chapitre 2 :

doc.images.each { |img|
  assert img.dpi >= 300, "image on page #{img.page_number}: #{img.dpi} DPI"
}

5.2 Comparaison visuelle entre fichiers

Ces fonctions comparent une page de ce document à une page d'un autre fichier. Signature commune :

fonction(page_ici, "autre.pdf" [, page_là])

Sans le numéro de l'autre page, la même page est utilisée. Des pages de tailles différentes sont rééchantillonnées avant comparaison.

Fonction Rôle
visual::measure_ssim(page, "autre.pdf" [, page_b]) Similarité structurelle (0.0 à 1.0)
visual::compare_images(...) / visual::diff_pages(...) Même comparaison, sur 0 à 100
visual::pixel_diff(page, "autre.pdf" [, page_b, tolérance]) Pourcentage de pixels différents
visual::calculate_perceptual_hash([page]) pHash 64 bits (hexadécimal)
visual::detect_image_replacement(page, "autre.pdf" [, page_b, distance]) Vrai si le changement dépasse la tolérance
check "Approved proof vs final file" {
  approuve = "approved/magazine_v1.pdf"

  doc.pages.each { |page|
    ssim = visual::measure_ssim(page.number, approuve)
    assert ssim > 0.99,
      "page #{page.number} changed visually (SSIM #{ssim})"

    // Une tolérance élevée ignore les écarts d'anticrénelage
    doux = visual::pixel_diff(page.number, approuve, page.number, 30)
    assert doux < 1.0, "significant change on page #{page.number}"

    assert !visual::detect_image_replacement(page.number, approuve),
      "page #{page.number}: visual content was replaced"
  }
}

5.3 Qualité des images

Fonction Rôle
visual::detect_image_artifacts([page]) Vrai s'il y a des blocs JPEG visibles
visual::estimate_image_quality([page]) Note de 0 à 100 déduite de l'effet de bloc
visual::detect_posterization([page]) Vrai si les niveaux de tons sont trop peu nombreux
visual::detect_banding([page]) Vrai si un dégradé montre des marches

La détection de banding exige une progression monotone avec de larges plateaux : une page de texte, très contrastée, ne déclenche pas de fausse alerte.

check "Image quality" {
  doc.pages.each { |page|
    assert !visual::detect_image_artifacts(page.number),
      "page #{page.number} shows visible compression blockiness"

    note = visual::estimate_image_quality(page.number)
    assert note >= 70,
      "page #{page.number} scores #{note}/100 — recompressed too hard?"

    assert !visual::detect_posterization(page.number),
      "page #{page.number}: possible posterization (too few tones)"
    assert !visual::detect_banding(page.number),
      "page #{page.number} shows banding in a gradient"
  }
}

5.4 Exemple complet

// approbation_visuelle.pdfl — comparaison avec la version approuvée
// Usage : pdfl run approbation_visuelle.pdfl nouvelle_version.pdf
profile "visual-approval" {

  const APPROUVE = "approved/catalogue_v1.pdf"
  const MIN_DPI = 300

  check "Inventory" tags: ["images"] {
    require visual::detect_images()
    print("images:", visual::count_images())
    print("color spaces:", visual::detect_image_color_space().join(", "))
  }

  check "Resolution" tags: ["images", "prepress"] {
    doc.images.each { |img|
      assert img.dpi >= MIN_DPI,
        "image on page #{img.page_number}: #{round(img.dpi)} DPI (minimum #{MIN_DPI})"
    }
  }

  check "Quality" tags: ["images"] {
    doc.pages.each { |page|
      assert !visual::detect_image_artifacts(page.number),
        "page #{page.number} has compression artifacts"
      assert !visual::detect_banding(page.number), "page #{page.number} shows banding"
    }
  }

  check "Fidelity to the approved version" tags: ["approval"] {
    doc.pages.each { |page|
      ssim = visual::measure_ssim(page.number, APPROUVE)
      assert ssim > 0.99,
        "page #{page.number} differs from the approved one (SSIM #{ssim}, #{visual::pixel_diff(page.number, APPROUVE)}% of pixels)"
    }
  }
}

struct:: · Sommaire · Suivant : prepress::