From 5314b3ed15d5839449d3f57f8a6cef000397eda0 Mon Sep 17 00:00:00 2001 From: "Cliff L. Biffle" Date: Wed, 20 Mar 2024 09:46:54 -0700 Subject: [PATCH 1/2] fl16-inputmodules: generate gamma table during build This costs ~256 bytes of flash (once referenced). I've tried to keep the generated code readable. --- fl16-inputmodules/build.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 fl16-inputmodules/build.rs diff --git a/fl16-inputmodules/build.rs b/fl16-inputmodules/build.rs new file mode 100644 index 0000000..597775c --- /dev/null +++ b/fl16-inputmodules/build.rs @@ -0,0 +1,29 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let gamma_path = Path::new(&out_dir).join("gamma.rs"); + let mut f = File::create(gamma_path).unwrap(); + + // Determined empirically for a PVT panel. May need to become conditional + // on features, TBD. + const GAMMA: f32 = 3.2; + + let corrected: [f32; 256] = + std::array::from_fn(|i| f32::powf((i as f32) / 255., GAMMA) * 255. + 0.5); + + writeln!(f, "const GAMMA: [u8; 256] = [").unwrap(); + + const LINE_LEN: usize = 8; + for line in corrected.chunks(LINE_LEN) { + write!(f, " ").unwrap(); + for element in line { + write!(f, " {:>3},", *element as u8).unwrap(); + } + writeln!(f).unwrap(); + } + writeln!(f, "];").unwrap(); +} From 0e2480bf2ccc9c36f01e51495bab267fc40e85bf Mon Sep 17 00:00:00 2001 From: "Cliff L. Biffle" Date: Wed, 20 Mar 2024 10:17:26 -0700 Subject: [PATCH 2/2] fl16-inputmodules: add gamma correction. The brightness values sent to the LED controller actually control a PWM duty cycle. LEDs emit light roughly in proportion to the PWM duty cycle, but human vision perceives brightness on an exponential curve -- generally it takes 2x the physical luminous flux for the eye to perceive something as one step brighter. As a result, brightness ramps (like the one generated by --all-brightnesses) were rapidly brightening to what appeared to be max, and then flattening. This change adds gamma correction, which maps the linear input brightness values to a non-linear function of PWM duty cycles, causing the ramp to look far more ramp-y. The gamma value I've chosen here is arguably a preference, I messed with output on a PVT panel until I found something that looked approximately right. --- fl16-inputmodules/src/fl16.rs | 2 ++ fl16-inputmodules/src/patterns.rs | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/fl16-inputmodules/src/fl16.rs b/fl16-inputmodules/src/fl16.rs index 8b9f042..d7048bf 100644 --- a/fl16-inputmodules/src/fl16.rs +++ b/fl16-inputmodules/src/fl16.rs @@ -678,6 +678,8 @@ where Ok(()) } + /// Fills the matrix with a _raw_ brightness value, i.e. without gamma + /// correction, to show the native PWM values. pub fn fill_brightness(&mut self, brightness: u8) -> Result<(), Error> { for x in 0..self.device.width { for y in 0..self.device.height { diff --git a/fl16-inputmodules/src/patterns.rs b/fl16-inputmodules/src/patterns.rs index 37f239b..bdf0041 100644 --- a/fl16-inputmodules/src/patterns.rs +++ b/fl16-inputmodules/src/patterns.rs @@ -310,7 +310,8 @@ pub fn double_gradient() -> Grid { pub fn _fill_grid(grid: &Grid, matrix: &mut Foo) { for y in 0..HEIGHT { for x in 0..WIDTH { - matrix.device.pixel(x as u8, y as u8, grid.0[x][y]).unwrap(); + let p = gamma::correct(grid.0[x][y]); + matrix.device.pixel(x as u8, y as u8, p).unwrap(); } } } @@ -327,9 +328,11 @@ pub fn fill_grid_pixels(state: &LedmatrixState, matrix: &mut Foo) { for y in 0..HEIGHT { for x in 0..WIDTH { let (register, page) = (matrix.device.calc_pixel)(x as u8, y as u8); - brightnesses[(page as usize) * 0xB4 + (register as usize)] = + let uncorrected = ((state.grid.0[x][y] as u64) * (state.brightness as u64) / (BRIGHTNESS_LEVELS as u64)) as u8; + brightnesses[(page as usize) * 0xB4 + (register as usize)] = + gamma::correct(uncorrected); } } matrix.device.fill_matrix(&brightnesses).unwrap(); @@ -384,3 +387,11 @@ pub fn every_nth_col(n: usize) -> Grid { grid } + +pub mod gamma { + pub const fn correct(value: u8) -> u8 { + GAMMA[value as usize] + } + + include!(concat!(env!("OUT_DIR"), "/gamma.rs")); +}