From 745ae0c98036808d91cea740e6bd8358351bd872 Mon Sep 17 00:00:00 2001 From: Schell Carl Scivally Date: Wed, 3 Jan 2024 10:33:42 +1300 Subject: [PATCH] img-diff takes into account pixel threshold and image threshold --- crates/img-diff/src/lib.rs | 90 ++++++++++++++++++++++++++++++--- crates/renderling/src/skybox.rs | 1 + 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/crates/img-diff/src/lib.rs b/crates/img-diff/src/lib.rs index c6b93f01..98220bf1 100644 --- a/crates/img-diff/src/lib.rs +++ b/crates/img-diff/src/lib.rs @@ -6,6 +6,21 @@ use std::path::Path; const TEST_IMG_DIR: &str = "../../test_img"; const TEST_OUTPUT_DIR: &str = "../../test_output"; +const PIXEL_MAGNITUDE_THRESHOLD: f32 = 0.1; +const IMAGE_DIFF_THRESHOLD: f32 = 0.05; + +fn checkerboard_background_color(x: u32, y: u32) -> Vec4 { + let size = 16; + let x_square_index = x / size; + let x_grey = x_square_index % 2 == 0; + let y_square_index = y / size; + let y_grey = y_square_index % 2 == 0; + if (x_grey && y_grey) || (!x_grey && !y_grey) { + Vec4::from([0.5, 0.5, 0.5, 1.0]) + } else { + Vec4::from([1.0, 1.0, 1.0, 1.0]) + } +} #[derive(Debug, Snafu)] enum ImgDiffError { @@ -13,6 +28,27 @@ enum ImgDiffError { ImageSize, } +pub struct DiffCfg { + /// The threshold for a pixel to be considered different. + /// + /// Difference is measured as the magnitude of vector subtraction + /// between the two pixels. + pub pixel_threshold: f32, + /// The percentage of "different" pixels (as determined using + /// `pixel_threshold`) to "correct" pixels that the image must contain + /// before it is considered an error. + pub image_threshold: f32, +} + +impl Default for DiffCfg { + fn default() -> Self { + Self { + pixel_threshold: PIXEL_MAGNITUDE_THRESHOLD, + image_threshold: IMAGE_DIFF_THRESHOLD, + } + } +} + fn get_results( left_image: &Rgba32FImage, right_image: &Rgba32FImage, @@ -29,11 +65,14 @@ fn get_results( if left_pixel == right_pixel { None } else { + // pre-multiply alpha let left_pixel = Vec4::from(left_pixel.0); + let left_pixel = (left_pixel * left_pixel.w).xyz(); let right_pixel = Vec4::from(right_pixel.0); + let right_pixel = (right_pixel * right_pixel.w).xyz(); let delta = (left_pixel - right_pixel).abs(); if delta.length() > threshold { - Some((x, y, delta)) + Some((x, y, delta.extend(1.0))) } else { None } @@ -47,8 +86,22 @@ fn get_results( } else { let mut output_image = image::ImageBuffer::from_pixel(width, height, Rgba([0.0, 0.0, 0.0, 0.0])); + + for x in 0..width { + for y in 0..height { + output_image.put_pixel(x, y, Rgba(checkerboard_background_color(x, y).into())); + } + } + for (x, y, delta) in results { - let color = delta.xyz().extend(1.0); + let bg = checkerboard_background_color(x, y); + let a = 1.0 - delta.z; + let color = Vec4::new( + bg.x * a + delta.x, + bg.y * a + delta.y, + bg.z * a + delta.z, + 1.0, + ); output_image.put_pixel(x, y, Rgba(color.into())); } Ok(Some((diffs, output_image))) @@ -61,11 +114,28 @@ pub fn save(filename: &str, seen: impl Into) { seen.into().save(path).unwrap(); } -pub fn assert_eq(filename: &str, lhs: impl Into, rhs: impl Into) { +pub fn assert_eq_cfg( + filename: &str, + lhs: impl Into, + rhs: impl Into, + cfg: DiffCfg, +) { let lhs = lhs.into(); let lhs = lhs.into_rgba32f(); let rhs = rhs.into().into_rgba32f(); - if let Some((diffs, diff_image)) = get_results(&lhs, &rhs, 0.5).unwrap() { + let DiffCfg { + pixel_threshold, + image_threshold, + } = cfg; + if let Some((diffs, diff_image)) = get_results(&lhs, &rhs, pixel_threshold).unwrap() { + println!("{filename} has {diffs} pixel differences (threshold={pixel_threshold})"); + let percent_diff = diffs as f32 / (lhs.width() * lhs.height()) as f32; + println!("{filename}'s image is {percent_diff} different (threshold={image_threshold})"); + if percent_diff < image_threshold { + return; + } + + let mut dir = Path::new(TEST_OUTPUT_DIR).join(filename); dir.set_extension(""); std::fs::create_dir_all(&dir).expect("cannot create test output dir"); @@ -93,7 +163,11 @@ pub fn assert_eq(filename: &str, lhs: impl Into, rhs: impl Into) { +pub fn assert_eq(filename: &str, lhs: impl Into, rhs: impl Into) { + assert_eq_cfg(filename, lhs, rhs, DiffCfg::default()) +} + +pub fn assert_img_eq_cfg(filename: &str, seen: impl Into, cfg: DiffCfg) { let cwd = std::env::current_dir().expect("no cwd"); let lhs = image::open(Path::new(TEST_IMG_DIR).join(filename)).unwrap_or_else(|_| { panic!( @@ -101,5 +175,9 @@ pub fn assert_img_eq(filename: &str, seen: impl Into) { cwd.join(filename).display() ) }); - assert_eq(filename, lhs, seen) + assert_eq_cfg(filename, lhs, seen, cfg) +} + +pub fn assert_img_eq(filename: &str, seen: impl Into) { + assert_img_eq_cfg(filename, seen, DiffCfg::default()) } diff --git a/crates/renderling/src/skybox.rs b/crates/renderling/src/skybox.rs index 22d5be60..99976cee 100644 --- a/crates/renderling/src/skybox.rs +++ b/crates/renderling/src/skybox.rs @@ -639,6 +639,7 @@ impl Skybox { mod test { use crabslab::GrowableSlab; use glam::Vec3; + use img_diff::DiffCfg; use super::*; use crate::Renderling;