1use crate::util::open;
5use crate::{protocol, Ctx, Progress};
6use anyhow::{Context, Result};
7use wall::{Canvas, Frame};
8use sources::{Fit, FrameSource};
9use std::time::Duration;
10
11pub fn load_canvas(ctx: &Ctx, layout: Option<&str>) -> Result<Canvas> {
13 match layout {
14 Some(path) => {
15 let text = std::fs::read_to_string(path).with_context(|| format!("read {path}"))?;
16 serde_json::from_str(&text).with_context(|| format!("parse {path}"))
17 }
18 None => Ok(Canvas::single(u32::from(ctx.width), u32::from(ctx.height))),
19 }
20}
21
22fn env_or<T: std::str::FromStr>(name: &str, default: T) -> T {
24 std::env::var(name)
25 .ok()
26 .and_then(|v| v.parse().ok())
27 .unwrap_or(default)
28}
29
30pub fn wall_settings(ctx: &Ctx) -> driver::Settings {
33 let t = driver::Timing::default();
34 let micros = |name, d: Duration| Duration::from_micros(env_or(name, d.as_micros() as u64));
35 driver::Settings {
36 brightness: ctx.brightness,
37 color_order: ctx.order,
38 announce_layout: false,
39 timing: driver::Timing {
40 latches: env_or("RXP_LATCHES", t.latches),
41 latch_gap: micros("RXP_LATCH_GAP_US", t.latch_gap),
42 row_gap: micros("RXP_ROW_GAP_US", t.row_gap),
43 },
44 }
45}
46
47fn frame_period() -> Duration {
49 Duration::from_millis(env_or("RXP_FRAME_MS", 33))
50}
51
52pub fn brightness(ctx: &Ctx, value: u8) -> Result<()> {
61 let timing = wall_settings(ctx).timing;
62 let mut dev = open(ctx)?;
63 dev.send(&protocol::brightness(value))?;
64 std::thread::sleep(timing.latch_gap);
65 for _ in 0..timing.latches {
66 dev.send(&protocol::sync(value))?;
67 }
68 Ok(())
69}
70
71
72pub fn play(
74 ctx: &Ctx,
75 input: &str,
76 fps: u32,
77 fit: &str,
78 looping: bool,
79 layout: Option<&str>,
80 p: &mut dyn Progress,
81) -> Result<()> {
82 let canvas = load_canvas(ctx, layout)?;
83 let fit: Fit = fit.parse()?;
84 play_on(ctx, canvas, input, fps, fit, looping, p)
85}
86
87#[allow(clippy::too_many_arguments)]
90pub fn play_on(
91 ctx: &Ctx,
92 canvas: Canvas,
93 input: &str,
94 fps: u32,
95 fit: Fit,
96 looping: bool,
97 p: &mut dyn Progress,
98) -> Result<()> {
99 let mut source =
100 sources::VideoSource::open(input, canvas.width, canvas.height, fps, fit, looping)?;
101 let mut frame = Frame::black(canvas.width, canvas.height);
102 let mut wall = driver::Wall::open(&ctx.iface, canvas, wall_settings(ctx))?;
103 let mut pacer = driver::Pacer::new(fps);
104
105 while !p.cancelled() && source.next_frame(&mut frame)? {
106 wall.show(&frame)?;
107 pacer.wait();
108 if wall.frames_sent().is_multiple_of(60) {
109 p.transient(&format!(
110 "{} frames, {:.1} fps",
111 wall.frames_sent(),
112 pacer.achieved_fps()
113 ));
114 }
115 }
116 p.clear_transient();
117 p.out(&format!(
118 "{} frames, {:.1} fps",
119 wall.frames_sent(),
120 pacer.achieved_fps()
121 ));
122 Ok(())
123}
124
125pub fn show_frame(
127 ctx: &Ctx,
128 canvas: Canvas,
129 frame: &Frame,
130 hold: bool,
131 p: &mut dyn Progress,
132) -> Result<()> {
133 let mut wall = driver::Wall::open(&ctx.iface, canvas, wall_settings(ctx))?;
134 let period = frame_period();
135 if hold {
136 while !p.cancelled() {
137 wall.show(frame)?;
138 std::thread::sleep(period);
139 }
140 return Ok(());
141 }
142 for _ in 0..3 {
144 wall.show(frame)?;
145 std::thread::sleep(period);
146 }
147 Ok(())
148}
149
150pub fn show_pattern(
152 ctx: &Ctx,
153 name: &str,
154 hold: bool,
155 layout: Option<&str>,
156 p: &mut dyn Progress,
157) -> Result<()> {
158 let canvas = load_canvas(ctx, layout)?;
159 show_pattern_on(ctx, canvas, name, hold, p)
160}
161
162pub fn show_pattern_on(
164 ctx: &Ctx,
165 canvas: Canvas,
166 name: &str,
167 hold: bool,
168 p: &mut dyn Progress,
169) -> Result<()> {
170 let pattern: sources::Pattern = name.parse()?;
171 let frame = sources::pattern(pattern, canvas.width, canvas.height);
172 show_frame(ctx, canvas, &frame, hold, p)
173}
174
175pub fn show_solid(ctx: &Ctx, rgb: [u8; 3], hold: bool, p: &mut dyn Progress) -> Result<()> {
177 let canvas = load_canvas(ctx, None)?;
178 show_solid_on(ctx, canvas, rgb, hold, p)
179}
180
181pub fn show_solid_on(
183 ctx: &Ctx,
184 canvas: Canvas,
185 rgb: [u8; 3],
186 hold: bool,
187 p: &mut dyn Progress,
188) -> Result<()> {
189 let frame = Frame::from_rgb(
190 canvas.width,
191 canvas.height,
192 rgb.repeat((canvas.width * canvas.height) as usize),
193 )?;
194 show_frame(ctx, canvas, &frame, hold, p)
195}
196
197pub fn show_image(ctx: &Ctx, path: &str, hold: bool, p: &mut dyn Progress) -> Result<()> {
199 let canvas = load_canvas(ctx, None)?;
200 let img = image::open(path).with_context(|| format!("open image {path}"))?;
201 let frame = image_frame(&img, &canvas, Fit::Stretch)?;
202 show_frame(ctx, canvas, &frame, hold, p)
203}
204
205pub fn image_frame(img: &image::DynamicImage, canvas: &Canvas, fit: Fit) -> Result<Frame> {
211 use image::imageops::FilterType::Lanczos3;
212 let (w, h) = (canvas.width, canvas.height);
213 let rgb = match fit {
214 Fit::Stretch => img.resize_exact(w, h, Lanczos3).to_rgb8(),
215 Fit::Cover => img.resize_to_fill(w, h, Lanczos3).to_rgb8(),
216 Fit::Contain => {
217 let scaled = img.resize(w, h, Lanczos3).to_rgb8();
218 let mut out = image::RgbImage::new(w, h);
219 let x = i64::from((w - scaled.width()) / 2);
220 let y = i64::from((h - scaled.height()) / 2);
221 image::imageops::replace(&mut out, &scaled, x, y);
222 out
223 }
224 };
225 Ok(Frame::from_rgb(w, h, rgb.into_raw())?)
226}
227
228pub fn probe(
232 ctx: &Ctx,
233 rows: u16,
234 row_gap_us: u64,
235 sync_after: bool,
236 repeat: u32,
237 rgb: [u8; 3],
238) -> Result<()> {
239 let mut dev = open(ctx)?;
240 let line = vec![rgb; ctx.width as usize];
241 for pass in 0..repeat {
242 for row in 0..rows {
243 dev.send(&protocol::pixel_row(row, 0, &line, ctx.order))?;
244 if row_gap_us > 0 {
245 std::thread::sleep(Duration::from_micros(row_gap_us));
246 }
247 }
248 if sync_after {
249 dev.send(&protocol::sync(ctx.brightness))?;
250 }
251 if repeat > 1 && pass + 1 < repeat {
252 std::thread::sleep(Duration::from_millis(33));
253 }
254 }
255 Ok(())
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 fn frames(value: u8, latches: u32) -> Vec<Vec<u8>> {
264 let mut out = vec![protocol::brightness(value).to_vec()];
265 out.extend(std::iter::repeat_n(protocol::sync(value).to_vec(), latches as usize));
266 out
267 }
268
269 #[test]
270 fn brightness_sends_the_frame_then_the_measured_number_of_latches() {
271 let t = driver::Timing::default();
272 let f = frames(40, t.latches);
273 assert_eq!(f.len(), 1 + t.latches as usize);
274 assert_eq!(f[0][12..14], [0x0a, 40], "brightness frame type carries the value");
275 assert_eq!(f[0][14..17], [40, 40, 0xff], "brightness block");
276 assert_eq!(f[1][12..14], [0x01, 0x07], "latch frame type");
277 assert_eq!(f[1][35], 40, "master brightness");
278 assert_eq!(f[1][38..41], [40, 40, 40], "channel gains");
279 assert!(f[1..].iter().all(|x| *x == f[1]), "every latch identical");
280 }
281}