1#[derive(Clone)]
3pub struct Gray {
4 data: Vec<u8>,
5 width: usize,
6 height: usize,
7}
8
9impl Gray {
10 pub fn from_vec(data: Vec<u8>, width: usize, height: usize) -> Self {
12 assert_eq!(
13 data.len(),
14 width * height,
15 "{width}x{height} needs {} samples, got {}",
16 width * height,
17 data.len()
18 );
19 Self {
20 data,
21 width,
22 height,
23 }
24 }
25
26 pub fn from_rgb(rgb: &[u8], width: usize, height: usize) -> Self {
31 assert_eq!(
32 rgb.len(),
33 width * height * 3,
34 "{width}x{height} needs {} samples, got {}",
35 width * height * 3,
36 rgb.len()
37 );
38
39 let data = rgb
40 .chunks_exact(3)
41 .map(|pixel| {
42 let (red, green, blue) = (pixel[0] as u32, pixel[1] as u32, pixel[2] as u32);
43 ((54 * red + 183 * green + 19 * blue) / 256) as u8
44 })
45 .collect();
46
47 Self {
48 data,
49 width,
50 height,
51 }
52 }
53
54 pub fn width(&self) -> usize {
55 self.width
56 }
57
58 pub fn height(&self) -> usize {
59 self.height
60 }
61
62 pub fn as_slice(&self) -> &[u8] {
63 &self.data
64 }
65
66 pub fn sample(&self, x: usize, y: usize) -> u8 {
67 self.data[y * self.width + x]
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn from_vec_carries_samples_and_dimensions() {
77 let gray = Gray::from_vec(vec![1, 2, 3, 4, 5, 6], 3, 2);
78
79 assert_eq!(gray.width(), 3);
80 assert_eq!(gray.height(), 2);
81 assert_eq!(gray.as_slice(), &[1, 2, 3, 4, 5, 6]);
82 }
83
84 #[test]
85 #[should_panic(expected = "3x2 needs 6 samples, got 5")]
86 fn from_vec_rejects_a_length_that_contradicts_the_dimensions() {
87 Gray::from_vec(vec![1, 2, 3, 4, 5], 3, 2);
88 }
89
90 #[test]
91 fn sample_reads_row_major() {
92 let gray = Gray::from_vec(vec![1, 2, 3, 4, 5, 6], 3, 2);
93
94 assert_eq!(gray.sample(0, 0), 1);
95 assert_eq!(gray.sample(2, 0), 3);
96 assert_eq!(gray.sample(0, 1), 4);
97 assert_eq!(gray.sample(2, 1), 6);
98 }
99
100 #[test]
101 fn from_rgb_maps_white_to_full_scale_and_black_to_zero() {
102 let gray = Gray::from_rgb(&[255, 255, 255, 0, 0, 0], 2, 1);
103
104 assert_eq!(gray.as_slice(), &[255, 0]);
105 }
106
107 #[test]
108 fn from_rgb_weights_the_channels_as_the_paper_does() {
109 let gray = Gray::from_rgb(&[255, 0, 0, 0, 255, 0, 0, 0, 255], 3, 1);
110
111 assert_eq!(gray.as_slice(), &[53, 182, 18]);
112 }
113
114 #[test]
115 #[should_panic(expected = "2x1 needs 6 samples, got 5")]
116 fn from_rgb_rejects_a_length_that_contradicts_the_dimensions() {
117 Gray::from_rgb(&[1, 2, 3, 4, 5], 2, 1);
118 }
119}