cvkg_render_native/
regression.rs1use std::path::PathBuf;
2
3#[derive(Debug, Clone)]
6pub struct VisualRegressionTracker {
7 reference_dir: PathBuf,
9 pixel_tolerance: u8,
11 max_mismatched_percentage: f64,
13}
14
15impl VisualRegressionTracker {
16 pub fn new(
18 reference_dir: PathBuf,
19 pixel_tolerance: u8,
20 max_mismatched_percentage: f64,
21 ) -> Self {
22 Self {
23 reference_dir,
24 pixel_tolerance,
25 max_mismatched_percentage,
26 }
27 }
28
29 pub fn verify_frame(&self, test_name: &str, captured_png: &[u8]) -> bool {
34 let reference_path = self.reference_dir.join(format!("{}.png", test_name));
35 if !reference_path.exists() {
36 tracing::info!(
37 "Golden reference for '{}' not found. Recording current capture as reference.",
38 test_name
39 );
40 if let Some(parent) = reference_path.parent() {
41 let _ = std::fs::create_dir_all(parent);
42 }
43 if let Err(e) = std::fs::write(&reference_path, captured_png) {
44 tracing::error!("Failed to write golden image: {}", e);
45 return false;
46 }
47 return true;
48 }
49
50 let ref_img =
52 match image::load_from_memory(&std::fs::read(&reference_path).unwrap_or_default()) {
53 Ok(img) => img.to_rgba8(),
54 Err(e) => {
55 tracing::error!("Failed to decode reference image: {}", e);
56 return false;
57 }
58 };
59
60 let cap_img = match image::load_from_memory(captured_png) {
62 Ok(img) => img.to_rgba8(),
63 Err(e) => {
64 tracing::error!("Failed to decode captured image: {}", e);
65 return false;
66 }
67 };
68
69 if ref_img.dimensions() != cap_img.dimensions() {
70 tracing::warn!(
71 "Dimensions mismatch for test '{}': ref {:?}, cap {:?}",
72 test_name,
73 ref_img.dimensions(),
74 cap_img.dimensions()
75 );
76 return false;
77 }
78
79 let (width, height) = ref_img.dimensions();
80 let total_pixels = width as f64 * height as f64;
81 let mut mismatched_pixels = 0;
82
83 for (x, y, ref_pixel) in ref_img.enumerate_pixels() {
84 let cap_pixel = cap_img.get_pixel(x, y);
85 let mut pixel_differs = false;
86 for c in 0..4 {
87 let diff = (ref_pixel[c] as i16 - cap_pixel[c] as i16).abs();
88 if diff > self.pixel_tolerance as i16 {
89 pixel_differs = true;
90 break;
91 }
92 }
93 if pixel_differs {
94 mismatched_pixels += 1;
95 }
96 }
97
98 let mismatch_pct = (mismatched_pixels as f64 / total_pixels) * 100.0;
99 if mismatch_pct > self.max_mismatched_percentage {
100 tracing::warn!(
101 "Visual regression detected in test '{}': {:.2}% mismatched pixels (max allowed {:.2}%)",
102 test_name,
103 mismatch_pct,
104 self.max_mismatched_percentage
105 );
106 false
107 } else {
108 true
109 }
110 }
111}