1use crate::Gray;
2use crate::strips::fill_rows;
3
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub enum Shrink {
8 #[default]
11 Average,
12 Subsample,
14}
15
16pub fn shrink2(gray: &Gray, shrink: Shrink) -> Gray {
20 let width = gray.width() / 2;
21 let height = gray.height() / 2;
22 let source = gray.as_slice();
23 let stride = gray.width();
24
25 let mut shrunk = vec![0; width * height];
26
27 fill_rows(&mut shrunk, width, |y, row| {
28 let top = 2 * y * stride;
29 let bottom = top + stride;
30
31 for (x, sample) in row.iter_mut().enumerate() {
32 let left = 2 * x;
33 *sample = match shrink {
34 Shrink::Average => {
35 let total = source[top + left] as u16
37 + source[top + left + 1] as u16
38 + source[bottom + left] as u16
39 + source[bottom + left + 1] as u16;
40 ((total + 2) / 4) as u8
41 }
42 Shrink::Subsample => source[top + left],
43 };
44 }
45 });
46
47 Gray::from_vec(shrunk, width, height)
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use proptest::prelude::*;
54
55 fn counting(width: usize, height: usize) -> Gray {
56 Gray::from_vec((0..(width * height) as u8).collect(), width, height)
57 }
58
59 #[test]
60 fn averaging_takes_the_mean_of_each_block() {
61 let shrunk = shrink2(&counting(4, 4), Shrink::Average);
62
63 assert_eq!((shrunk.width(), shrunk.height()), (2, 2));
64 assert_eq!(shrunk.as_slice(), &[3, 5, 11, 13]);
65 }
66
67 #[test]
69 fn averaging_rounds_to_nearest() {
70 let block = Gray::from_vec(vec![1, 2, 3, 4], 2, 2);
71
72 assert_eq!(shrink2(&block, Shrink::Average).as_slice(), &[3]);
73 }
74
75 #[test]
76 fn subsampling_takes_the_top_left_of_each_block() {
77 let shrunk = shrink2(&counting(4, 4), Shrink::Subsample);
78
79 assert_eq!((shrunk.width(), shrunk.height()), (2, 2));
80 assert_eq!(shrunk.as_slice(), &[0, 2, 8, 10]);
81 }
82
83 #[test]
84 fn an_odd_final_row_or_column_is_dropped() {
85 let source = counting(5, 3);
86
87 let averaged = shrink2(&source, Shrink::Average);
88 assert_eq!((averaged.width(), averaged.height()), (2, 1));
89 assert_eq!(averaged.as_slice(), &[3, 5]);
90
91 let subsampled = shrink2(&source, Shrink::Subsample);
92 assert_eq!((subsampled.width(), subsampled.height()), (2, 1));
93 assert_eq!(subsampled.as_slice(), &[0, 2]);
94 }
95
96 #[test]
97 fn a_saturated_block_averages_to_saturation() {
98 let block = Gray::from_vec(vec![255; 4], 2, 2);
99
100 assert_eq!(shrink2(&block, Shrink::Average).as_slice(), &[255]);
101 }
102
103 #[test]
104 fn a_plane_too_small_to_halve_shrinks_away() {
105 let shrunk = shrink2(&counting(1, 1), Shrink::Average);
106
107 assert_eq!((shrunk.width(), shrunk.height()), (0, 0));
108 assert!(shrunk.as_slice().is_empty());
109 }
110
111 proptest! {
112 #[test]
114 fn both_modes_halve_to_the_same_size(width in 0usize..40, height in 0usize..40) {
115 let source = Gray::from_vec(vec![9; width * height], width, height);
116
117 let averaged = shrink2(&source, Shrink::Average);
118 let subsampled = shrink2(&source, Shrink::Subsample);
119
120 prop_assert_eq!(averaged.width(), width / 2);
121 prop_assert_eq!(averaged.height(), height / 2);
122 prop_assert_eq!(subsampled.width(), width / 2);
123 prop_assert_eq!(subsampled.height(), height / 2);
124 }
125
126 #[test]
127 fn a_constant_plane_survives_either_mode(
128 value in 0u8..=255,
129 width in 2usize..40,
130 height in 2usize..40,
131 ) {
132 let source = Gray::from_vec(vec![value; width * height], width, height);
133
134 for mode in [Shrink::Average, Shrink::Subsample] {
135 for &sample in shrink2(&source, mode).as_slice() {
136 prop_assert_eq!(sample, value);
137 }
138 }
139 }
140 }
141}