graphics: update to use figments Fract8 type

This commit is contained in:
2026-02-28 16:47:25 +01:00
parent 95ebc7820a
commit 319c812a61
5 changed files with 36 additions and 35 deletions

View File

@@ -1,6 +1,6 @@
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal, watch::{Receiver, Watch}}; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal, watch::{Receiver, Watch}};
use figments::prelude::*; use figments::{liber8tion::interpolate::Fract8, prelude::*};
use core::{fmt::Debug, sync::atomic::{AtomicBool, AtomicU8}}; use core::{fmt::Debug, ops::Mul, sync::atomic::{AtomicBool, AtomicU8}};
use alloc::sync::Arc; use alloc::sync::Arc;
//use super::{Output}; //use super::{Output};
@@ -201,8 +201,8 @@ impl DisplayControls {
self.data.on.load(core::sync::atomic::Ordering::Relaxed) self.data.on.load(core::sync::atomic::Ordering::Relaxed)
} }
pub fn brightness(&self) -> u8 { pub fn brightness(&self) -> Fract8 {
self.data.brightness.load(core::sync::atomic::Ordering::Relaxed) Fract8::from_raw(self.data.brightness.load(core::sync::atomic::Ordering::Relaxed))
} }
pub async fn wait_until_display_is_on(&self) { pub async fn wait_until_display_is_on(&self) {
@@ -228,8 +228,8 @@ impl GammaCorrected for DisplayControls {
} }
impl Brightness for DisplayControls { impl Brightness for DisplayControls {
fn set_brightness(&mut self, brightness: u8) { fn set_brightness(&mut self, brightness: Fract8) {
self.data.brightness.store(brightness, core::sync::atomic::Ordering::Relaxed); self.data.brightness.store(brightness.to_raw(), core::sync::atomic::Ordering::Relaxed);
} }
fn set_on(&mut self, is_on: bool) { fn set_on(&mut self, is_on: bool) {

View File

@@ -145,9 +145,9 @@ impl Screen {
Image::new(&images::BOOT_LOGO, Point::zero()).draw(sampler).unwrap(); Image::new(&images::BOOT_LOGO, Point::zero()).draw(sampler).unwrap();
const SPARKLE_COUNT: i32 = 8; const SPARKLE_COUNT: i32 = 8;
for n in 0..SPARKLE_COUNT { for n in 0..SPARKLE_COUNT {
let sparkle_center = Point::new(128u8.scale8(cos8(state.frame.wrapping_mul(n as usize))) as i32, 64u8.scale8(sin8(state.frame.wrapping_mul(n as usize) as u8)) as i32); let sparkle_center = Point::new((128u8 * state.frame.wrapping_mul(n as usize).cos8()) as i32, (64u8 * state.frame.wrapping_mul(n as usize).sin8()) as i32);
let offset = (state.frame / 2 % 32) as i32 - 16; let offset = (state.frame / 2 % 32) as i32 - 16;
let rotation = PI * 2.0 * (sin8(state.frame) as f32 / 255.0); let rotation = PI * 2.0 * state.frame.sin8();
let normal = Point::new((rotation.cos() * offset as f32) as i32, (rotation.sin() * offset as f32) as i32); let normal = Point::new((rotation.cos() * offset as f32) as i32, (rotation.sin() * offset as f32) as i32);
let cross_normal = Point::new((rotation.sin() * offset as f32) as i32, (rotation.cos() * offset as f32) as i32); let cross_normal = Point::new((rotation.sin() * offset as f32) as i32, (rotation.cos() * offset as f32) as i32);
// Draw horizontal // Draw horizontal

View File

@@ -1,6 +1,7 @@
use core::cmp::max; use core::cmp::max;
use figments::{liber8tion::{interpolate::{ease_in_out_quad}, noise::inoise8, trig::{cos8, sin8}}, prelude::*}; use figments::{liber8tion::{interpolate::{Fract8, ease_in_out_quad}, noise::inoise8, trig::Trig8}, prelude::*};
use num_traits::WrappingAdd;
use rgb::Rgba; use rgb::Rgba;
use crate::graphics::display::{SegmentSpace, Uniforms}; use crate::graphics::display::{SegmentSpace, Uniforms};
@@ -25,8 +26,8 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Movement {
} else { } else {
uniforms.frame.wrapping_sub(surface_coords.x) uniforms.frame.wrapping_sub(surface_coords.x)
}; };
let idx = sin8(offset).wrapping_add(uniforms.primary_color.hue); let idx = offset.sin8().wrapping_add(&Fract8::from_raw(uniforms.primary_color.hue));
Rgba::new(idx, idx.wrapping_mul(2), idx.wrapping_div(2), 128) Rgba::new(idx.to_raw(), idx.to_raw().wrapping_mul(2), idx.to_raw() / 2, 128)
} }
} }
@@ -45,12 +46,12 @@ impl Background {
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Background { impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Background {
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> { fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
let noise_x = sin8(uniforms.frame % 255) as i16; let noise_x = (uniforms.frame % 255).sin8().to_raw() as i16;
let noise_y = cos8(uniforms.frame % 255) as i16; let noise_y = (uniforms.frame % 255).cos8().to_raw() as i16;
let brightness = inoise8(noise_x.wrapping_add(coords.x as i16), noise_y.wrapping_add(coords.y as i16)); let brightness = inoise8(noise_x.wrapping_add(coords.x as i16), noise_y.wrapping_add(coords.y as i16));
let saturation = inoise8(noise_y.wrapping_add(coords.y as i16), noise_x.wrapping_add(coords.x as i16)); let saturation = inoise8(noise_y.wrapping_add(coords.y as i16), noise_x.wrapping_add(coords.x as i16));
let rgb: Rgb<u8> = match self.color { let rgb: Rgb<u8> = match self.color {
None => Hsv::new(uniforms.primary_color.hue, max(128, saturation), brightness).into(), None => Hsv::new(uniforms.primary_color.hue, max(128, saturation.to_raw()), brightness.to_raw()).into(),
Some(c) => c Some(c) => c
}; };
@@ -62,9 +63,9 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Background {
pub struct Tail {} pub struct Tail {}
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Tail { impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Tail {
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> { fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
let hue_offset: u8 = 32.scale8(sin8(uniforms.frame.wrapping_sub(coords.x))); let hue_offset: u8 = 32u8 * Fract8::from_raw(uniforms.frame.wrapping_sub(coords.x) as u8);
let value = max(30, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16)); let value = max(30, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16).to_raw());
Hsv::new(uniforms.primary_color.hue.wrapping_sub(hue_offset), max(210, sin8(uniforms.frame.wrapping_add(coords.x))), value).into() Hsv::new(uniforms.primary_color.hue.wrapping_sub(hue_offset), max(210, uniforms.frame.wrapping_add(coords.x).sin8().to_raw()), value).into()
} }
} }
@@ -96,10 +97,10 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Brakelight {
let distance_from_end = Self::end() - coords.x; let distance_from_end = Self::end() - coords.x;
if distance_from_end < Self::safety_length() { if distance_from_end < Self::safety_length() {
Rgba::new(max(128, sin8(uniforms.frame.wrapping_sub(coords.x))), 0, 0, 255) Rgba::new(max(128, uniforms.frame.wrapping_sub(coords.x).sin8().to_raw()), 0, 0, 255)
} else { } else {
let pct = (distance_from_end as f32 / Self::length() as f32) * 255f32; let pct = (distance_from_end as f32 / Self::length() as f32) * 255f32;
Rgba::new(max(100, ease_in_out_quad((pct as u8).wrapping_add(uniforms.frame as u8))), 0, 0, ease_in_out_quad(255 - pct as u8)) Rgba::new(max(100, ease_in_out_quad(Fract8::from_raw(pct as u8).wrapping_add(&Fract8::from_raw(uniforms.frame as u8))).to_raw()), 0, 0, ease_in_out_quad(Fract8::from_raw(255 - pct as u8)).to_raw())
} }
} }
} }
@@ -108,7 +109,7 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Brakelight {
pub struct Headlight {} pub struct Headlight {}
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Headlight { impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Headlight {
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> { fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
Hsv::new(0, 0, max(130, ease_in_out_quad(sin8(uniforms.frame.wrapping_sub(coords.x))))).into() Hsv::new(0, 0, max(130, ease_in_out_quad(uniforms.frame.wrapping_sub(coords.x).sin8()).to_raw())).into()
} }
} }
@@ -116,14 +117,14 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Headlight {
pub struct Panel {} pub struct Panel {}
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Panel { impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Panel {
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> { fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
let noise_offset = max(180, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16)); let noise_offset = max(180, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16).to_raw());
let pct = (coords.x as f32 / 18f32) * 255f32; let pct = (coords.x as f32 / 18f32) * 255f32;
let shift = match coords.y { let shift = match coords.y {
1..=2 => 106, // 150 degrees 1..=2 => 106, // 150 degrees
3..=4 => 148, // 210 degrees 3..=4 => 148, // 210 degrees
_ => 0 _ => 0
}; };
Hsv::new(uniforms.primary_color.hue.wrapping_add(shift), noise_offset, max(100, sin8(pct as u8).wrapping_add(uniforms.frame as u8))).into() Hsv::new(uniforms.primary_color.hue.wrapping_add(shift), noise_offset, max(100, pct.sin8().wrapping_add(&Fract8::from_raw(uniforms.frame as u8)).to_raw())).into()
} }
} }
@@ -134,15 +135,15 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Thinking {
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> { fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
//let noise_x = sin8(sin8((frame % 255) as u8).wrapping_add(coords.x)); //let noise_x = sin8(sin8((frame % 255) as u8).wrapping_add(coords.x));
//let noise_y = cos8(cos8((frame % 255) as u8).wrapping_add(coords.y)); //let noise_y = cos8(cos8((frame % 255) as u8).wrapping_add(coords.y));
let offset_x = sin8(uniforms.frame.wrapping_add(coords.x)); let offset_x = uniforms.frame.wrapping_add(coords.x).sin8();
let offset_y = cos8(uniforms.frame.wrapping_add(coords.y)); let offset_y = uniforms.frame.wrapping_add(coords.y).cos8();
let noise_x = offset_x / 2; let noise_x = offset_x / 2;
let noise_y = offset_y / 2; let noise_y = offset_y / 2;
//let noise_x = coords.x.wrapping_add(offset_x); //let noise_x = coords.x.wrapping_add(offset_x);
//let noise_y = coords.y.wrapping_add(offset_y); //let noise_y = coords.y.wrapping_add(offset_y);
Hsv::new( Hsv::new(
inoise8(offset_x as i16, offset_y as i16), inoise8(offset_x.to_raw() as i16, offset_y.to_raw() as i16).to_raw(),
128_u8.saturating_add(inoise8(noise_y.into(), noise_x.into())), 128_u8.saturating_add(inoise8(noise_y.to_raw().into(), noise_x.to_raw().into()).to_raw()),
255 255
).into() ).into()
} }

View File

@@ -4,7 +4,7 @@ use core::{cmp::min, ops::{BitAnd, Shr}};
use display_interface::DisplayError; use display_interface::DisplayError;
use embedded_graphics::prelude::*; use embedded_graphics::prelude::*;
use esp_hal::{gpio::Output, i2c::master::I2c, Async}; use esp_hal::{gpio::Output, i2c::master::I2c, Async};
use figments::{liber8tion::{interpolate::Fract8, noise}, mappings::embedded_graphics::Matrix2DSpace, prelude::*}; use figments::{liber8tion::{interpolate::Fract8}, mappings::embedded_graphics::Matrix2DSpace, prelude::*};
use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::pixelcolor::BinaryColor;
use figments::pixels::AdditivePixelSink; use figments::pixels::AdditivePixelSink;
use figments_render::output::OutputAsync; use figments_render::output::OutputAsync;
@@ -59,8 +59,8 @@ const DITHER_MAP: [u16;15] = [
impl AdditivePixelSink<BinaryColor> for SsdPixel { impl AdditivePixelSink<BinaryColor> for SsdPixel {
fn add(&mut self, pixel: BinaryColor, opacity: Fract8) { fn add(&mut self, pixel: BinaryColor, opacity: Fract8) {
match opacity { match opacity {
0 => (), Fract8::MIN => (),
255 => self.set_pixel(pixel), Fract8::MAX => self.set_pixel(pixel),
_ => { _ => {
let dither_value = DITHER_MAP[opacity as usize / 17]; let dither_value = DITHER_MAP[opacity as usize / 17];
let dither_x = self.coords.x % 4; let dither_x = self.coords.x % 4;
@@ -108,7 +108,7 @@ pub struct SsdOutput {
target: ssd1306::Ssd1306Async<ssd1306::prelude::I2CInterface<esp_hal::i2c::master::I2c<'static, esp_hal::Async>>, ssd1306::prelude::DisplaySize128x64, ssd1306::mode::BasicMode>, target: ssd1306::Ssd1306Async<ssd1306::prelude::I2CInterface<esp_hal::i2c::master::I2c<'static, esp_hal::Async>>, ssd1306::prelude::DisplaySize128x64, ssd1306::mode::BasicMode>,
controls: DisplayControls, controls: DisplayControls,
is_on: bool, is_on: bool,
last_brightness: u8, last_brightness: Fract8,
reset_pin: Output<'static> reset_pin: Output<'static>
} }
@@ -132,7 +132,7 @@ impl SsdOutput {
pixbuf: [0; 128 * 64 / 8], pixbuf: [0; 128 * 64 / 8],
target, target,
controls, controls,
last_brightness: 255, last_brightness: Fract8::MAX,
is_on: true, is_on: true,
reset_pin reset_pin
} }

View File

@@ -2,7 +2,7 @@ use alloc::sync::Arc;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, pubsub::DynSubscriber}; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, pubsub::DynSubscriber};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::pixelcolor::BinaryColor;
use figments::{mappings::embedded_graphics::Matrix2DSpace, prelude::{Coordinates, Rectangle}, render::Shader, surface::{BufferedSurfacePool, NullBufferPool, Surface, SurfaceBuilder, Surfaces}}; use figments::{liber8tion::interpolate::Fract8, mappings::embedded_graphics::Matrix2DSpace, prelude::{Coordinates, Rectangle}, render::Shader, surface::{Surface, SurfaceBuilder, Surfaces}};
use figments_render::output::Brightness; use figments_render::output::Brightness;
use log::*; use log::*;
@@ -37,7 +37,7 @@ impl<S: core::fmt::Debug + Surface<CoordinateSpace = Matrix2DSpace, Pixel = Bina
overlay: SurfaceBuilder::build(surfaces) overlay: SurfaceBuilder::build(surfaces)
.rect(Rectangle::everything()) .rect(Rectangle::everything())
.shader(OverlayShader{}) .shader(OverlayShader{})
.opacity(0) .opacity(Fract8::MIN)
.finish().unwrap(), .finish().unwrap(),
controls, controls,
uniforms uniforms
@@ -45,8 +45,8 @@ impl<S: core::fmt::Debug + Surface<CoordinateSpace = Matrix2DSpace, Pixel = Bina
} }
pub async fn screen_transition(&mut self, next_screen: Screen) { pub async fn screen_transition(&mut self, next_screen: Screen) {
const FADE_IN: Animation = Animation::new().from(0).to(255).duration(Duration::from_millis(300)); const FADE_IN: Animation<Fract8> = Animation::new().from(Fract8::MIN).to(Fract8::MAX).duration(Duration::from_millis(300));
const FADE_OUT: Animation = Animation::new().from(255).to(0).duration(Duration::from_millis(300)); const FADE_OUT: Animation<Fract8> = Animation::new().from(Fract8::MAX).to(Fract8::MIN).duration(Duration::from_millis(300));
info!("Fading in to screen {next_screen:?}"); info!("Fading in to screen {next_screen:?}");
FADE_IN.apply(&mut self.overlay).await; FADE_IN.apply(&mut self.overlay).await;
{ {