rustial_engine/
image_compare.rs1pub fn compute_rmse(a: &[u8], b: &[u8]) -> f64 {
23 assert_eq!(a.len(), b.len(), "image buffers must have the same length");
24 assert!(!a.is_empty(), "image buffers must not be empty");
25
26 let sum_sq: f64 = a
27 .iter()
28 .zip(b.iter())
29 .map(|(&va, &vb)| {
30 let diff = va as f64 - vb as f64;
31 diff * diff
32 })
33 .sum();
34
35 (sum_sq / a.len() as f64).sqrt()
36}
37
38pub fn count_differing_pixels(a: &[u8], b: &[u8], threshold: u8) -> usize {
47 assert_eq!(a.len(), b.len(), "image buffers must have the same length");
48 assert_eq!(a.len() % 4, 0, "image buffer length must be a multiple of 4");
49
50 a.chunks_exact(4)
51 .zip(b.chunks_exact(4))
52 .filter(|(pa, pb)| {
53 pa.iter()
54 .zip(pb.iter())
55 .any(|(&ca, &cb)| (ca as i16 - cb as i16).unsigned_abs() > threshold as u16)
56 })
57 .count()
58}
59
60pub fn differing_pixel_fraction(a: &[u8], b: &[u8], threshold: u8) -> f64 {
64 let total_pixels = a.len() / 4;
65 if total_pixels == 0 {
66 return 0.0;
67 }
68 count_differing_pixels(a, b, threshold) as f64 / total_pixels as f64
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn identical_images_have_zero_rmse() {
77 let img = vec![128u8; 64 * 4];
78 assert!((compute_rmse(&img, &img) - 0.0).abs() < f64::EPSILON);
79 }
80
81 #[test]
82 fn different_images_have_positive_rmse() {
83 let a = vec![0u8; 64 * 4];
84 let b = vec![255u8; 64 * 4];
85 let rmse = compute_rmse(&a, &b);
86 assert!(rmse > 200.0, "rmse was {rmse}");
87 }
88
89 #[test]
90 fn count_differing_pixels_exact_threshold() {
91 let a = vec![100u8; 4 * 4];
92 let mut b = a.clone();
93 b[0] = 110;
95 assert_eq!(count_differing_pixels(&a, &b, 9), 1);
96 assert_eq!(count_differing_pixels(&a, &b, 10), 0);
97 }
98
99 #[test]
100 fn differing_fraction_returns_correct_ratio() {
101 let a = vec![0u8; 8 * 4]; let mut b = a.clone();
103 b[0] = 255; b[4] = 255; let frac = differing_pixel_fraction(&a, &b, 0);
106 assert!((frac - 0.25).abs() < 1e-9, "frac was {frac}");
107 }
108}