use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_core::pixelcolor::RgbColor;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PaletteMode {
Off,
Rgb332,
}
impl PaletteMode {
#[must_use]
#[inline]
pub fn apply(self, color: Rgb565) -> Rgb565 {
match self {
PaletteMode::Off => color,
PaletteMode::Rgb332 => {
let r3 = ((color.r() as u16 * 7 + 15) / 31) as u8;
let g3 = ((color.g() as u16 * 7 + 31) / 63) as u8;
let b2 = ((color.b() as u16 * 3 + 15) / 31) as u8;
let r = (r3 as u16 * 31 / 7) as u8;
let g = (g3 as u16 * 63 / 7) as u8;
let b = (b2 as u16 * 31 / 3) as u8;
Rgb565::new(r, g, b)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AnimatedPalette<const N: usize> {
pub colors: [Rgb565; N],
pub cycle_start: usize,
pub cycle_end: usize,
pub offset: usize,
}
impl<const N: usize> AnimatedPalette<N> {
#[must_use]
pub const fn new(colors: [Rgb565; N]) -> Self {
Self {
colors,
cycle_start: 0,
cycle_end: N,
offset: 0,
}
}
#[must_use]
pub const fn with_cycle_range(mut self, start: usize, end: usize) -> Self {
self.cycle_start = start;
self.cycle_end = if end <= N { end } else { N };
self
}
pub fn step(&mut self, steps: usize) {
let range_len = self.cycle_end.saturating_sub(self.cycle_start);
if range_len > 1 {
self.offset = (self.offset + steps) % range_len;
}
}
#[must_use]
#[inline]
pub fn get_color(&self, index: usize) -> Rgb565 {
if index >= N {
return Rgb565::BLACK;
}
if index >= self.cycle_start && index < self.cycle_end {
let range_len = self.cycle_end - self.cycle_start;
let cycled_idx =
self.cycle_start + (index - self.cycle_start + self.offset) % range_len;
self.colors[cycled_idx]
} else {
self.colors[index]
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CycleDirection {
#[default]
Forward,
Reverse,
PingPong,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PaletteSlice {
pub start: usize,
pub end: usize,
pub rate_hz: f32,
pub direction: CycleDirection,
pub timer: f32,
pub current_step: usize,
pub ping_pong_forward: bool,
}
impl PaletteSlice {
#[must_use]
pub const fn new(start: usize, end: usize, rate_hz: f32, direction: CycleDirection) -> Self {
Self {
start,
end,
rate_hz,
direction,
timer: 0.0,
current_step: 0,
ping_pong_forward: true,
}
}
#[must_use]
pub fn advance(&mut self, dt: f32) -> bool {
let span = self.end.saturating_sub(self.start);
if span <= 1 || self.rate_hz <= 0.0 {
return false;
}
self.timer += dt;
let period = 1.0 / self.rate_hz;
let mut changed = false;
while self.timer >= period {
self.timer -= period;
changed = true;
match self.direction {
CycleDirection::Forward => {
self.current_step = (self.current_step + 1) % span;
}
CycleDirection::Reverse => {
self.current_step = if self.current_step == 0 {
span - 1
} else {
self.current_step - 1
};
}
CycleDirection::PingPong => {
if self.ping_pong_forward {
if self.current_step + 1 >= span {
self.ping_pong_forward = false;
self.current_step = self.current_step.saturating_sub(1);
} else {
self.current_step += 1;
}
} else if self.current_step == 0 {
self.ping_pong_forward = true;
self.current_step = 1.min(span - 1);
} else {
self.current_step -= 1;
}
}
}
}
changed
}
#[must_use]
#[inline]
pub fn map_index(&self, index: usize) -> usize {
let span = self.end.saturating_sub(self.start);
if span <= 1 || index < self.start || index >= self.end {
return index;
}
self.start + (index - self.start + self.current_step) % span
}
}
#[derive(Debug, Clone, Copy)]
pub struct PaletteCycler<const MAX_SLICES: usize = 4> {
pub slices: [Option<PaletteSlice>; MAX_SLICES],
pub slice_count: usize,
}
impl<const MAX_SLICES: usize> Default for PaletteCycler<MAX_SLICES> {
fn default() -> Self {
Self::new()
}
}
impl<const MAX_SLICES: usize> PaletteCycler<MAX_SLICES> {
#[must_use]
pub const fn new() -> Self {
Self {
slices: [None; MAX_SLICES],
slice_count: 0,
}
}
pub fn add_slice(&mut self, slice: PaletteSlice) -> bool {
if self.slice_count < MAX_SLICES {
self.slices[self.slice_count] = Some(slice);
self.slice_count += 1;
true
} else {
false
}
}
pub fn add_range(
&mut self,
start: usize,
end: usize,
rate_hz: f32,
direction: CycleDirection,
) -> bool {
self.add_slice(PaletteSlice::new(start, end, rate_hz, direction))
}
#[must_use]
pub fn advance(&mut self, dt: f32) -> bool {
let mut any_changed = false;
for slice in self.slices.iter_mut().take(self.slice_count).flatten() {
if slice.advance(dt) {
any_changed = true;
}
}
any_changed
}
#[must_use]
#[inline]
pub fn map_index(&self, index: usize) -> usize {
let mut mapped = index;
for slice in self.slices.iter().take(self.slice_count).flatten() {
if index >= slice.start && index < slice.end {
mapped = slice.map_index(index);
break;
}
}
mapped
}
pub fn cycle_palette<const N: usize>(&self, base: &[Rgb565; N], dest: &mut [Rgb565; N]) {
for i in 0..N {
dest[i] = base[self.map_index(i)];
}
}
pub fn cycle_slice(&self, base: &[Rgb565], dest: &mut [Rgb565]) {
let count = base.len().min(dest.len());
for i in 0..count {
dest[i] = base[self.map_index(i)];
}
}
}