use crate::util::open;
use crate::{protocol, Ctx, Progress};
use anyhow::{Context, Result};
use wall::{Canvas, Frame};
use sources::{Fit, FrameSource};
use std::time::Duration;
pub fn load_canvas(ctx: &Ctx, layout: Option<&str>) -> Result<Canvas> {
match layout {
Some(path) => {
let text = std::fs::read_to_string(path).with_context(|| format!("read {path}"))?;
serde_json::from_str(&text).with_context(|| format!("parse {path}"))
}
None => Ok(Canvas::single(u32::from(ctx.width), u32::from(ctx.height))),
}
}
fn env_or<T: std::str::FromStr>(name: &str, default: T) -> T {
std::env::var(name)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
pub fn wall_settings(ctx: &Ctx) -> driver::Settings {
let t = driver::Timing::default();
let micros = |name, d: Duration| Duration::from_micros(env_or(name, d.as_micros() as u64));
driver::Settings {
brightness: ctx.brightness,
color_order: ctx.order,
announce_layout: false,
timing: driver::Timing {
latches: env_or("RXP_LATCHES", t.latches),
latch_gap: micros("RXP_LATCH_GAP_US", t.latch_gap),
row_gap: micros("RXP_ROW_GAP_US", t.row_gap),
},
}
}
fn frame_period() -> Duration {
Duration::from_millis(env_or("RXP_FRAME_MS", 33))
}
pub fn brightness(ctx: &Ctx, value: u8) -> Result<()> {
let timing = wall_settings(ctx).timing;
let mut dev = open(ctx)?;
dev.send(&protocol::brightness(value))?;
std::thread::sleep(timing.latch_gap);
for _ in 0..timing.latches {
dev.send(&protocol::sync(value))?;
}
Ok(())
}
pub fn play(
ctx: &Ctx,
input: &str,
fps: u32,
fit: &str,
looping: bool,
layout: Option<&str>,
p: &mut dyn Progress,
) -> Result<()> {
let canvas = load_canvas(ctx, layout)?;
let fit: Fit = fit.parse()?;
play_on(ctx, canvas, input, fps, fit, looping, p)
}
#[allow(clippy::too_many_arguments)]
pub fn play_on(
ctx: &Ctx,
canvas: Canvas,
input: &str,
fps: u32,
fit: Fit,
looping: bool,
p: &mut dyn Progress,
) -> Result<()> {
let mut source =
sources::VideoSource::open(input, canvas.width, canvas.height, fps, fit, looping)?;
let mut frame = Frame::black(canvas.width, canvas.height);
let mut wall = driver::Wall::open(&ctx.iface, canvas, wall_settings(ctx))?;
let mut pacer = driver::Pacer::new(fps);
while !p.cancelled() && source.next_frame(&mut frame)? {
wall.show(&frame)?;
pacer.wait();
if wall.frames_sent().is_multiple_of(60) {
p.transient(&format!(
"{} frames, {:.1} fps",
wall.frames_sent(),
pacer.achieved_fps()
));
}
}
p.clear_transient();
p.out(&format!(
"{} frames, {:.1} fps",
wall.frames_sent(),
pacer.achieved_fps()
));
Ok(())
}
pub fn show_frame(
ctx: &Ctx,
canvas: Canvas,
frame: &Frame,
hold: bool,
p: &mut dyn Progress,
) -> Result<()> {
let mut wall = driver::Wall::open(&ctx.iface, canvas, wall_settings(ctx))?;
let period = frame_period();
if hold {
while !p.cancelled() {
wall.show(frame)?;
std::thread::sleep(period);
}
return Ok(());
}
for _ in 0..3 {
wall.show(frame)?;
std::thread::sleep(period);
}
Ok(())
}
pub fn show_pattern(
ctx: &Ctx,
name: &str,
hold: bool,
layout: Option<&str>,
p: &mut dyn Progress,
) -> Result<()> {
let canvas = load_canvas(ctx, layout)?;
show_pattern_on(ctx, canvas, name, hold, p)
}
pub fn show_pattern_on(
ctx: &Ctx,
canvas: Canvas,
name: &str,
hold: bool,
p: &mut dyn Progress,
) -> Result<()> {
let pattern: sources::Pattern = name.parse()?;
let frame = sources::pattern(pattern, canvas.width, canvas.height);
show_frame(ctx, canvas, &frame, hold, p)
}
pub fn show_solid(ctx: &Ctx, rgb: [u8; 3], hold: bool, p: &mut dyn Progress) -> Result<()> {
let canvas = load_canvas(ctx, None)?;
show_solid_on(ctx, canvas, rgb, hold, p)
}
pub fn show_solid_on(
ctx: &Ctx,
canvas: Canvas,
rgb: [u8; 3],
hold: bool,
p: &mut dyn Progress,
) -> Result<()> {
let frame = Frame::from_rgb(
canvas.width,
canvas.height,
rgb.repeat((canvas.width * canvas.height) as usize),
)?;
show_frame(ctx, canvas, &frame, hold, p)
}
pub fn show_image(ctx: &Ctx, path: &str, hold: bool, p: &mut dyn Progress) -> Result<()> {
let canvas = load_canvas(ctx, None)?;
let img = image::open(path).with_context(|| format!("open image {path}"))?;
let frame = image_frame(&img, &canvas, Fit::Stretch)?;
show_frame(ctx, canvas, &frame, hold, p)
}
pub fn image_frame(img: &image::DynamicImage, canvas: &Canvas, fit: Fit) -> Result<Frame> {
use image::imageops::FilterType::Lanczos3;
let (w, h) = (canvas.width, canvas.height);
let rgb = match fit {
Fit::Stretch => img.resize_exact(w, h, Lanczos3).to_rgb8(),
Fit::Cover => img.resize_to_fill(w, h, Lanczos3).to_rgb8(),
Fit::Contain => {
let scaled = img.resize(w, h, Lanczos3).to_rgb8();
let mut out = image::RgbImage::new(w, h);
let x = i64::from((w - scaled.width()) / 2);
let y = i64::from((h - scaled.height()) / 2);
image::imageops::replace(&mut out, &scaled, x, y);
out
}
};
Ok(Frame::from_rgb(w, h, rgb.into_raw())?)
}
pub fn probe(
ctx: &Ctx,
rows: u16,
row_gap_us: u64,
sync_after: bool,
repeat: u32,
rgb: [u8; 3],
) -> Result<()> {
let mut dev = open(ctx)?;
let line = vec![rgb; ctx.width as usize];
for pass in 0..repeat {
for row in 0..rows {
dev.send(&protocol::pixel_row(row, 0, &line, ctx.order))?;
if row_gap_us > 0 {
std::thread::sleep(Duration::from_micros(row_gap_us));
}
}
if sync_after {
dev.send(&protocol::sync(ctx.brightness))?;
}
if repeat > 1 && pass + 1 < repeat {
std::thread::sleep(Duration::from_millis(33));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn frames(value: u8, latches: u32) -> Vec<Vec<u8>> {
let mut out = vec![protocol::brightness(value).to_vec()];
out.extend(std::iter::repeat_n(protocol::sync(value).to_vec(), latches as usize));
out
}
#[test]
fn brightness_sends_the_frame_then_the_measured_number_of_latches() {
let t = driver::Timing::default();
let f = frames(40, t.latches);
assert_eq!(f.len(), 1 + t.latches as usize);
assert_eq!(f[0][12..14], [0x0a, 40], "brightness frame type carries the value");
assert_eq!(f[0][14..17], [40, 40, 0xff], "brightness block");
assert_eq!(f[1][12..14], [0x01, 0x07], "latch frame type");
assert_eq!(f[1][35], 40, "master brightness");
assert_eq!(f[1][38..41], [40, 40, 40], "channel gains");
assert!(f[1..].iter().all(|x| *x == f[1]), "every latch identical");
}
}