1use super::{write_header, HEADER_LEN};
10
11pub const MAX_PIXELS_PER_PACKET: usize = 497;
13
14#[must_use]
20pub fn sync(brightness: u8) -> [u8; 112] {
21 sync_gains(brightness, [brightness; 3])
24}
25
26#[must_use]
29pub fn sync_gains(brightness: u8, gains: [u8; 3]) -> [u8; 112] {
30 let mut f = [0u8; 112];
31 write_header(&mut f, [0x01, 0x07]);
32 f[35] = brightness;
33 f[36] = 0x05;
34 f[38..41].copy_from_slice(&gains);
35 f
36}
37
38#[must_use]
40pub fn brightness(b: u8) -> [u8; 77] {
41 let mut f = [0u8; 77];
42 write_header(&mut f, [0x0a, b]);
43 f[14] = b;
44 f[15] = b;
45 f[16] = 0xff;
46 f
47}
48
49#[derive(Clone, Copy, PartialEq, Eq, Debug)]
50pub enum ColorOrder {
51 Rgb,
52 Bgr,
53 Grb,
54}
55
56impl ColorOrder {
57 const NAMES: [(&'static str, Self); 3] = [("rgb", Self::Rgb), ("bgr", Self::Bgr), ("grb", Self::Grb)];
58
59 const fn permutation(self) -> [usize; 3] {
61 match self {
62 Self::Rgb => [0, 1, 2],
63 Self::Bgr => [2, 1, 0],
64 Self::Grb => [1, 0, 2],
65 }
66 }
67}
68
69impl std::str::FromStr for ColorOrder {
70 type Err = String;
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 Self::NAMES
73 .iter()
74 .find(|(name, _)| name.eq_ignore_ascii_case(s))
75 .map(|&(_, order)| order)
76 .ok_or_else(|| format!("unknown color order {s:?} (rgb|bgr|grb)"))
77 }
78}
79
80const ROW_PIXELS_AT: usize = HEADER_LEN + 7;
81
82#[must_use]
85pub fn pixel_row(row: u16, pixel_offset: u16, rgb: &[[u8; 3]], order: ColorOrder) -> Vec<u8> {
86 let mut f = Vec::new();
87 pixel_row_into(&mut f, row, pixel_offset, rgb, order);
88 f
89}
90
91pub fn pixel_row_into(buf: &mut Vec<u8>, row: u16, pixel_offset: u16, rgb: &[[u8; 3]], order: ColorOrder) {
94 let count = rgb.len() as u16;
95 buf.clear();
96 buf.resize(ROW_PIXELS_AT + rgb.len() * 3, 0);
97 write_header(buf, [0x55, (row >> 8) as u8]);
98 buf[14] = (row & 0xff) as u8;
99 buf[15..17].copy_from_slice(&pixel_offset.to_be_bytes());
100 buf[17..19].copy_from_slice(&count.to_be_bytes());
101 buf[19] = 0x08;
102 buf[20] = 0x88;
103 let [a, b, c] = order.permutation();
104 let (dst_px, _) = buf[ROW_PIXELS_AT..].as_chunks_mut::<3>();
105 for (dst, px) in dst_px.iter_mut().zip(rgb) {
106 dst[0] = px[a];
107 dst[1] = px[b];
108 dst[2] = px[c];
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn pixel_rows_follow_the_fpp_layout() {
118 let px = [[1u8, 2, 3], [4, 5, 6]];
119 let f = pixel_row(0x0102, 5, &px, ColorOrder::Rgb);
120 assert_eq!(f[12], 0x55, "type byte");
121 assert_eq!(&f[13..15], &[0x01, 0x02], "row u16 BE starting at offset 13");
122 assert_eq!(&f[15..17], &5u16.to_be_bytes());
123 assert_eq!(&f[17..19], &2u16.to_be_bytes());
124 assert_eq!(&f[19..21], &[0x08, 0x88]);
125 assert_eq!(&f[21..27], &[1, 2, 3, 4, 5, 6]);
126 }
127
128 #[test]
129 fn colour_order_reorders_the_channels() {
130 let px = [[1u8, 2, 3]];
131 let bgr = pixel_row(0, 0, &px, ColorOrder::Bgr);
132 assert_eq!(&bgr[21..24], &[3, 2, 1]);
133 let grb = pixel_row(0, 0, &px, ColorOrder::Grb);
134 assert_eq!(&grb[21..24], &[2, 1, 3]);
135 }
136
137 #[test]
138 fn a_reused_buffer_gives_the_same_frame() {
139 let px: Vec<[u8; 3]> = (0..MAX_PIXELS_PER_PACKET as u16)
140 .map(|i| [i as u8, (i >> 3) as u8, !(i as u8)])
141 .collect();
142 let mut buf = vec![0xeeu8; 4096];
143 for order in [ColorOrder::Rgb, ColorOrder::Bgr, ColorOrder::Grb] {
144 pixel_row_into(&mut buf, 0x0203, 497, &px, order);
145 assert_eq!(buf, pixel_row(0x0203, 497, &px, order));
146 assert_eq!(buf.len(), 21 + 3 * MAX_PIXELS_PER_PACKET);
147 }
148 }
149
150 #[test]
151 fn colour_order_parses_case_insensitively() {
152 assert_eq!("BGR".parse::<ColorOrder>(), Ok(ColorOrder::Bgr));
153 assert_eq!("rgb".parse::<ColorOrder>(), Ok(ColorOrder::Rgb));
154 assert_eq!("grb".parse::<ColorOrder>(), Ok(ColorOrder::Grb));
155 assert!("rbg".parse::<ColorOrder>().is_err());
156 }
157
158 #[test]
159 fn sync_frame_matches_fpp_byte_for_byte() {
160 let f = sync(0x7f);
162 assert_eq!(f.len(), 112);
163 assert_eq!(&f[12..14], &[0x01, 0x07]);
164 assert_eq!(f[35], 0x7f);
165 assert_eq!(f[36], 0x05);
166 assert_eq!(&f[38..41], &[0x7f; 3]);
167 }
168
169 #[test]
170 fn sync_gains_differ_from_sync_only_at_the_gain_bytes() {
171 let f = sync_gains(0x7f, [10, 20, 30]);
172 let plain = sync(0x7f);
173 assert_eq!(&f[38..41], &[10, 20, 30]);
174 assert_eq!(&f[..38], &plain[..38]);
175 assert_eq!(&f[41..], &plain[41..]);
176 assert_eq!(sync_gains(0x40, [0x40; 3]), sync(0x40));
177 }
178
179 #[test]
180 fn brightness_frame_matches_fpp() {
181 let f = brightness(0x40);
182 assert_eq!(f.len(), 77);
183 assert_eq!(f[12], 0x0a);
184 assert_eq!(&f[13..17], &[0x40, 0x40, 0x40, 0xff]);
185 }
186}