use crate::{Result, VirtualProductionError};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LedProcessorConfig {
pub gamma_correction: bool,
pub gamma: f32,
pub dithering: bool,
pub temporal_optimization: bool,
pub bit_depth: u8,
}
impl Default for LedProcessorConfig {
fn default() -> Self {
Self {
gamma_correction: true,
gamma: 2.2,
dithering: true,
temporal_optimization: true,
bit_depth: 10,
}
}
}
#[derive(Debug, Clone)]
pub struct GammaLut {
lut: Vec<u16>,
#[allow(dead_code)]
gamma: f32,
#[allow(dead_code)]
bit_depth: u8,
}
impl GammaLut {
#[must_use]
pub fn new(gamma: f32, bit_depth: u8) -> Self {
let max_val = (1 << bit_depth) - 1;
let lut_size = 256;
let lut: Vec<u16> = (0..lut_size)
.map(|i| {
let normalized = i as f32 / (lut_size - 1) as f32;
let corrected = normalized.powf(gamma);
(corrected * max_val as f32) as u16
})
.collect();
Self {
lut,
gamma,
bit_depth,
}
}
#[must_use]
pub fn apply(&self, value: u8) -> u16 {
self.lut[value as usize]
}
#[must_use]
pub fn apply_rgb(&self, rgb: [u8; 3]) -> [u16; 3] {
[self.apply(rgb[0]), self.apply(rgb[1]), self.apply(rgb[2])]
}
}
#[derive(Debug, Clone)]
pub struct TemporalDither {
frame_count: u64,
pattern: Vec<Vec<i8>>,
}
impl TemporalDither {
#[must_use]
pub fn new() -> Self {
let pattern = vec![
vec![0, 8, 2, 10],
vec![12, 4, 14, 6],
vec![3, 11, 1, 9],
vec![15, 7, 13, 5],
];
Self {
frame_count: 0,
pattern,
}
}
pub fn apply(&mut self, value: u16, x: usize, y: usize) -> u16 {
let threshold = self.pattern[y % 4][x % 4];
let temporal_offset = (self.frame_count % 4) as i8;
let dither_value = (threshold + temporal_offset * 4) % 16;
value.saturating_add(dither_value as u16 / 16)
}
pub fn next_frame(&mut self) {
self.frame_count += 1;
}
}
impl Default for TemporalDither {
fn default() -> Self {
Self::new()
}
}
pub struct LedProcessor {
config: LedProcessorConfig,
gamma_lut: Option<GammaLut>,
dither: Option<TemporalDither>,
}
impl LedProcessor {
pub fn new(config: LedProcessorConfig) -> Result<Self> {
let gamma_lut = if config.gamma_correction {
Some(GammaLut::new(config.gamma, config.bit_depth))
} else {
None
};
let dither = if config.dithering {
Some(TemporalDither::new())
} else {
None
};
Ok(Self {
config,
gamma_lut,
dither,
})
}
pub fn process(&mut self, input: &[u8], width: usize, height: usize) -> Result<Vec<u16>> {
if input.len() != width * height * 3 {
return Err(VirtualProductionError::LedWall(
"Invalid input size".to_string(),
));
}
let mut output = Vec::with_capacity(width * height * 3);
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) * 3;
let rgb = [input[idx], input[idx + 1], input[idx + 2]];
let mut corrected = if let Some(lut) = &self.gamma_lut {
lut.apply_rgb(rgb)
} else {
[u16::from(rgb[0]), u16::from(rgb[1]), u16::from(rgb[2])]
};
if let Some(dither) = &mut self.dither {
corrected[0] = dither.apply(corrected[0], x, y);
corrected[1] = dither.apply(corrected[1], x, y);
corrected[2] = dither.apply(corrected[2], x, y);
}
output.push(corrected[0]);
output.push(corrected[1]);
output.push(corrected[2]);
}
}
if let Some(dither) = &mut self.dither {
dither.next_frame();
}
Ok(output)
}
#[must_use]
pub fn config(&self) -> &LedProcessorConfig {
&self.config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gamma_lut() {
let lut = GammaLut::new(2.2, 10);
let result = lut.apply(128);
assert!(result > 0);
assert!(result < 1024);
}
#[test]
fn test_gamma_lut_rgb() {
let lut = GammaLut::new(2.2, 10);
let result = lut.apply_rgb([255, 128, 0]);
assert_eq!(result[0], 1023);
assert!(result[1] > 0);
}
#[test]
fn test_temporal_dither() {
let mut dither = TemporalDither::new();
let result = dither.apply(512, 0, 0);
assert!(result >= 512);
}
#[test]
fn test_led_processor() {
let config = LedProcessorConfig::default();
let mut processor = LedProcessor::new(config).expect("should succeed in test");
let input = vec![128u8; 10 * 10 * 3];
let output = processor
.process(&input, 10, 10)
.expect("should succeed in test");
assert_eq!(output.len(), 10 * 10 * 3);
}
#[test]
fn test_led_processor_no_gamma() {
let config = LedProcessorConfig {
gamma_correction: false,
gamma: 2.2,
dithering: false,
temporal_optimization: false,
bit_depth: 10,
};
let mut processor = LedProcessor::new(config).expect("should succeed in test");
let input = vec![128u8; 10 * 10 * 3];
let output = processor
.process(&input, 10, 10)
.expect("should succeed in test");
assert_eq!(output.len(), 10 * 10 * 3);
}
}