1#[derive(Clone, Debug, PartialEq)]
6pub struct Frame {
7 pub width: usize,
8 pub height: usize,
9 pub rgba: Vec<u8>,
10}
11
12impl Frame {
13 pub fn pixel(&self, x: usize, y: usize) -> [u8; 4] {
15 let i = (y * self.width + x) * 4;
16 [self.rgba[i], self.rgba[i + 1], self.rgba[i + 2], self.rgba[i + 3]]
17 }
18
19 pub fn mean(&self) -> [f32; 3] {
21 let n = (self.width * self.height).max(1) as f32;
22 let mut sum = [0.0f32; 3];
23 for px in self.rgba.as_chunks::<4>().0 {
24 for c in 0..3 {
25 sum[c] += px[c] as f32;
26 }
27 }
28 sum.map(|s| s / n)
29 }
30
31 pub fn is_black(&self) -> bool {
34 let [r, g, b] = self.mean();
35 if r.max(g).max(b) > BLACK_MEAN {
36 return false;
37 }
38 let bright = self.rgba.as_chunks::<4>().0.iter().filter(|p| p[0].max(p[1]).max(p[2]) > BLACK_PIXEL).count();
40 bright * 100 < self.width * self.height
41 }
42
43 pub fn turned(self, turns: u8) -> Frame {
45 let (w, h) = (self.width, self.height);
46 if turns.is_multiple_of(4) || w == 0 || h == 0 {
47 return self;
48 }
49 let (ow, oh) = if turns % 2 == 1 { (h, w) } else { (w, h) };
50 let mut rgba = vec![0u8; ow * oh * 4];
51 for oy in 0..oh {
52 for ox in 0..ow {
53 let (sx, sy) = match turns % 4 {
54 1 => (oy, h - 1 - ox),
55 2 => (w - 1 - ox, h - 1 - oy),
56 _ => (w - 1 - oy, ox),
57 };
58 let (s, d) = ((sy * w + sx) * 4, (oy * ow + ox) * 4);
59 rgba[d..d + 4].copy_from_slice(&self.rgba[s..s + 4]);
60 }
61 }
62 Frame { width: ow, height: oh, rgba }
63 }
64}
65
66const BLACK_MEAN: f32 = 12.0;
68const BLACK_PIXEL: u8 = 40;
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 fn solid(w: usize, h: usize, v: u8) -> Frame {
76 Frame { width: w, height: h, rgba: [v, v, v, 255].repeat(w * h) }
77 }
78
79 #[test]
80 fn black_frames_are_noticed() {
81 assert!(solid(8, 8, 0).is_black());
82 assert!(solid(8, 8, 10).is_black());
83 assert!(!solid(8, 8, 60).is_black());
84 let mut stars = solid(10, 10, 0);
86 for i in [0, 9, 44, 77, 98] {
87 stars.rgba[i * 4..i * 4 + 3].copy_from_slice(&[255, 255, 255]);
88 }
89 assert!(!stars.is_black());
90 }
91
92 #[test]
93 fn turns_rotate_clockwise() {
94 let f = Frame { width: 3, height: 2, rgba: (0..6u8).flat_map(|n| [n, 0, 0, 255]).collect() };
98 let reds = |f: &Frame| f.rgba.as_chunks::<4>().0.iter().map(|p| p[0]).collect::<Vec<_>>();
99 let once = f.clone().turned(1);
100 assert_eq!((once.width, once.height), (2, 3));
101 assert_eq!(reds(&once), vec![3, 0, 4, 1, 5, 2]);
102 assert_eq!(reds(&f.clone().turned(2)), vec![5, 4, 3, 2, 1, 0]);
103 assert_eq!(reds(&f.clone().turned(3)), vec![2, 5, 1, 4, 0, 3]);
104 assert_eq!(f.clone().turned(4), f);
105 }
106}