1use crate::wl::CapturedImage;
9
10pub const DEFAULT_TOLERANCE: u8 = 8;
13
14pub fn changed_fraction(a: &CapturedImage, b: &CapturedImage, tolerance: u8) -> f64 {
18 if a.width != b.width || a.height != b.height {
19 return 1.0;
20 }
21 let total = (a.width as usize) * (a.height as usize);
22 if total == 0 {
23 return 0.0;
24 }
25 let changed = a
26 .rgba
27 .chunks_exact(4)
28 .zip(b.rgba.chunks_exact(4))
29 .filter(|(pa, pb)| {
30 pa[0].abs_diff(pb[0]) > tolerance
31 || pa[1].abs_diff(pb[1]) > tolerance
32 || pa[2].abs_diff(pb[2]) > tolerance
33 })
34 .count();
35 changed as f64 / total as f64
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 fn img(w: u32, h: u32, fill: [u8; 4]) -> CapturedImage {
43 CapturedImage {
44 width: w,
45 height: h,
46 rgba: fill.repeat((w * h) as usize),
47 }
48 }
49
50 #[test]
51 fn identical_is_zero() {
52 let a = img(4, 4, [10, 20, 30, 255]);
53 let b = img(4, 4, [10, 20, 30, 255]);
54 assert_eq!(changed_fraction(&a, &b, DEFAULT_TOLERANCE), 0.0);
55 }
56
57 #[test]
58 fn within_tolerance_is_zero() {
59 let a = img(2, 2, [100, 100, 100, 255]);
60 let b = img(2, 2, [105, 100, 100, 255]); assert_eq!(changed_fraction(&a, &b, DEFAULT_TOLERANCE), 0.0);
62 }
63
64 #[test]
65 fn counts_changed_pixels() {
66 let mut a = img(2, 1, [0, 0, 0, 255]);
67 let b = img(2, 1, [0, 0, 0, 255]);
68 a.rgba[0] = 200; assert_eq!(changed_fraction(&a, &b, DEFAULT_TOLERANCE), 0.5);
70 }
71
72 #[test]
73 fn alpha_is_ignored() {
74 let a = img(1, 1, [0, 0, 0, 255]);
75 let b = img(1, 1, [0, 0, 0, 0]); assert_eq!(changed_fraction(&a, &b, DEFAULT_TOLERANCE), 0.0);
77 }
78
79 #[test]
80 fn size_mismatch_is_full() {
81 let a = img(2, 2, [0, 0, 0, 255]);
82 let b = img(3, 2, [0, 0, 0, 255]);
83 assert_eq!(changed_fraction(&a, &b, DEFAULT_TOLERANCE), 1.0);
84 }
85}