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(reference_dir: PathBuf, pixel_tolerance: u8, max_mismatched_percentage: f64) -> Self {
18 Self {
19 reference_dir,
20 pixel_tolerance,
21 max_mismatched_percentage,
22 }
23 }
24
25 pub fn verify_frame(&self, test_name: &str, captured_png: &[u8]) -> bool {
30 let reference_path = self.reference_dir.join(format!("{}.png", test_name));
31 if !reference_path.exists() {
32 log::info!("Golden reference for '{}' not found. Recording current capture as reference.", test_name);
33 if let Some(parent) = reference_path.parent() {
34 let _ = std::fs::create_dir_all(parent);
35 }
36 if let Err(e) = std::fs::write(&reference_path, captured_png) {
37 log::error!("Failed to write golden image: {}", e);
38 return false;
39 }
40 return true;
41 }
42
43 let ref_img = match image::load_from_memory(&std::fs::read(&reference_path).unwrap_or_default()) {
45 Ok(img) => img.to_rgba8(),
46 Err(e) => {
47 log::error!("Failed to decode reference image: {}", e);
48 return false;
49 }
50 };
51
52 let cap_img = match image::load_from_memory(captured_png) {
54 Ok(img) => img.to_rgba8(),
55 Err(e) => {
56 log::error!("Failed to decode captured image: {}", e);
57 return false;
58 }
59 };
60
61 if ref_img.dimensions() != cap_img.dimensions() {
62 log::warn!("Dimensions mismatch for test '{}': ref {:?}, cap {:?}", test_name, ref_img.dimensions(), cap_img.dimensions());
63 return false;
64 }
65
66 let (width, height) = ref_img.dimensions();
67 let total_pixels = width as f64 * height as f64;
68 let mut mismatched_pixels = 0;
69
70 for (x, y, ref_pixel) in ref_img.enumerate_pixels() {
71 let cap_pixel = cap_img.get_pixel(x, y);
72 let mut pixel_differs = false;
73 for c in 0..4 {
74 let diff = (ref_pixel[c] as i16 - cap_pixel[c] as i16).abs();
75 if diff > self.pixel_tolerance as i16 {
76 pixel_differs = true;
77 break;
78 }
79 }
80 if pixel_differs {
81 mismatched_pixels += 1;
82 }
83 }
84
85 let mismatch_pct = (mismatched_pixels as f64 / total_pixels) * 100.0;
86 if mismatch_pct > self.max_mismatched_percentage {
87 log::warn!("Visual regression detected in test '{}': {:.2}% mismatched pixels (max allowed {:.2}%)",
88 test_name, mismatch_pct, self.max_mismatched_percentage);
89 false
90 } else {
91 true
92 }
93 }
94}