1#[derive(Debug, thiserror::Error, PartialEq, Eq)]
30pub enum Error {
31 #[error("CFA split needs even dimensions, got {width}×{height}")]
32 OddDimensions { width: u32, height: u32 },
33 #[error("expected {expected} bytes of mosaic data, got {actual}")]
34 WrongLength { expected: usize, actual: usize },
35}
36
37pub type Result<T> = std::result::Result<T, Error>;
38
39pub const PLANE_COUNT: usize = 4;
41
42#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct Planes {
46 pub width: u32,
48 pub height: u32,
50 pub bytes_per_sample: usize,
51 pub planes: [Vec<u8>; PLANE_COUNT],
52}
53
54pub fn split(mosaic: &[u8], width: u32, height: u32, bytes_per_sample: usize) -> Result<Planes> {
56 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
57 return Err(Error::OddDimensions { width, height });
58 }
59 let bps = bytes_per_sample;
60 let expected = width as usize * height as usize * bps;
61 if mosaic.len() != expected {
62 return Err(Error::WrongLength {
63 expected,
64 actual: mosaic.len(),
65 });
66 }
67
68 let (w, h) = (width as usize, height as usize);
69 let (hw, hh) = (w / 2, h / 2);
70 let mut planes: [Vec<u8>; PLANE_COUNT] =
71 std::array::from_fn(|_| Vec::with_capacity(hw * hh * bps));
72
73 for y in 0..h {
74 let row = &mosaic[y * w * bps..(y + 1) * w * bps];
75 let pair = (y % 2) * 2;
77 for x in 0..w {
78 planes[pair + (x % 2)].extend_from_slice(&row[x * bps..(x + 1) * bps]);
79 }
80 }
81
82 Ok(Planes {
83 width: hw as u32,
84 height: hh as u32,
85 bytes_per_sample: bps,
86 planes,
87 })
88}
89
90pub fn merge(planes: &Planes) -> Vec<u8> {
92 let bps = planes.bytes_per_sample;
93 let (hw, hh) = (planes.width as usize, planes.height as usize);
94 let (w, h) = (hw * 2, hh * 2);
95 let mut out = vec![0u8; w * h * bps];
96
97 for y in 0..h {
98 let plane_row = (y / 2) * hw * bps;
99 let pair = (y % 2) * 2;
100 for x in 0..w {
101 let src = &planes.planes[pair + (x % 2)];
102 let s = plane_row + (x / 2) * bps;
103 let d = (y * w + x) * bps;
104 out[d..d + bps].copy_from_slice(&src[s..s + bps]);
105 }
106 }
107 out
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn noise(n: usize, seed: u64) -> Vec<u8> {
116 let mut s = seed | 1;
117 (0..n)
118 .map(|_| {
119 s ^= s << 13;
120 s ^= s >> 7;
121 s ^= s << 17;
122 (s >> 24) as u8
123 })
124 .collect()
125 }
126
127 #[test]
128 fn round_trip_is_byte_exact() {
129 for (w, h) in [(8u32, 6u32), (64, 32), (2, 2), (1024, 8)] {
130 let src = noise(w as usize * h as usize * 2, u64::from(w * h));
131 let p = split(&src, w, h, 2).unwrap();
132 assert_eq!(src, merge(&p), "round trip failed for {w}×{h}");
133 }
134 }
135
136 #[test]
137 fn planes_are_quarter_size_and_partition_the_mosaic() {
138 let (w, h) = (8u32, 6u32);
139 let src = noise(w as usize * h as usize * 2, 7);
140 let p = split(&src, w, h, 2).unwrap();
141 assert_eq!((p.width, p.height), (4, 3));
142 let total: usize = p.planes.iter().map(|v| v.len()).sum();
143 assert_eq!(total, (w * h) as usize * 2);
144 }
145
146 #[test]
147 fn split_routes_positions_correctly() {
148 let src: Vec<u8> = [1u16, 2, 3, 4]
150 .iter()
151 .flat_map(|v| v.to_le_bytes())
152 .collect();
153 let p = split(&src, 2, 2, 2).unwrap();
154 assert_eq!(p.planes[0], vec![1, 0]); assert_eq!(p.planes[1], vec![2, 0]); assert_eq!(p.planes[2], vec![3, 0]); assert_eq!(p.planes[3], vec![4, 0]); }
159
160 #[test]
163 fn byte_order_is_irrelevant() {
164 let src = noise(16 * 16 * 2, 3);
165 let a = split(&src, 16, 16, 2).unwrap();
166 let mut swapped = src.clone();
167 for c in swapped.chunks_exact_mut(2) {
168 c.swap(0, 1);
169 }
170 let b = split(&swapped, 16, 16, 2).unwrap();
171 for i in 0..PLANE_COUNT {
172 let unswapped: Vec<u8> = b.planes[i]
173 .chunks_exact(2)
174 .flat_map(|c| [c[1], c[0]])
175 .collect();
176 assert_eq!(a.planes[i], unswapped);
177 }
178 }
179
180 #[test]
181 fn odd_dimensions_rejected() {
182 let src = vec![0u8; 3 * 4 * 2];
183 assert_eq!(
184 split(&src, 3, 4, 2),
185 Err(Error::OddDimensions {
186 width: 3,
187 height: 4
188 })
189 );
190 }
191
192 #[test]
193 fn wrong_length_rejected() {
194 assert!(matches!(
195 split(&[0u8; 10], 4, 4, 2),
196 Err(Error::WrongLength { .. })
197 ));
198 }
199}