use crate::led::led_color::LedColor;
use anyhow::{anyhow, Result};
use rand::prelude::SliceRandom;
use rand::Rng;
use rand_distr::Distribution;
use rand_distr::Poisson;
pub struct Pattern;
impl Pattern {
pub fn random_discrete(probs: &[f64]) -> Result<usize> {
let sum: f64 = probs.iter().sum();
const TOLERANCE: f64 = 1e-5;
if (sum - 1.0).abs() > TOLERANCE {
return Err(anyhow!("Probabilities do not sum up to 1.0"));
}
let mut rng = rand::thread_rng();
let mut acc = 0.0;
let r: f64 = rng.gen(); for (ind, &prob) in probs.iter().enumerate() {
acc += prob;
if acc >= r {
return Ok(ind);
}
}
Err(anyhow!("Invalid probability distribution"))
}
pub fn random_poisson(lam: f64) -> Result<usize> {
let poisson = Poisson::new(lam).map_err(|e| anyhow!("Poisson error: {}", e))?;
let mut rng = rand::thread_rng();
let sample = poisson.sample(&mut rng) as u64;
if sample > usize::MAX as u64 {
return Err(anyhow!("Sampled value is too large for usize"));
}
Ok(sample as usize)
}
pub fn dim_color(rgb: (u8, u8, u8), prop: f64) -> (u8, u8, u8) {
let dimmed_r = (rgb.0 as f64 * prop).clamp(0.0, 255.0) as u8;
let dimmed_g = (rgb.1 as f64 * prop).clamp(0.0, 255.0) as u8;
let dimmed_b = (rgb.2 as f64 * prop).clamp(0.0, 255.0) as u8;
(dimmed_r, dimmed_g, dimmed_b)
}
pub fn blend_colors(rgb1: (u8, u8, u8), rgb2: (u8, u8, u8), prop: f64) -> (u8, u8, u8) {
let blend =
|c1, c2| ((c1 as f64 * (1.0 - prop) + c2 as f64 * prop).clamp(0.0, 255.0) as u8);
let blended_r = blend(rgb1.0, rgb2.0);
let blended_g = blend(rgb1.1, rgb2.1);
let blended_b = blend(rgb1.2, rgb2.2);
(blended_r, blended_g, blended_b)
}
pub fn random_color() -> (u8, u8, u8) {
let mut rng = rand::thread_rng();
let r = rng.gen_range(0..=255);
let g = rng.gen_range(0..=255);
let b = rng.gen_range(0..=255);
(r, g, b)
}
#[allow(clippy::type_complexity)]
pub fn random_hsl_color_func<'a>(
hue: Option<(f64, f64)>,
sat: Option<(f64, f64)>,
light: Option<(f64, f64)>,
led_color: &'a LedColor, ) -> Result<Box<dyn Fn() -> Result<(u8, u8, u8)> + 'a>> {
let random_in_range = |range_option: Option<(f64, f64)>| -> f64 {
let mut rng = rand::thread_rng();
match range_option {
Some((start, end)) => rng.gen_range(start..=end),
None => rng.gen(),
}
};
Ok(Box::new(move || {
let h = random_in_range(hue.or(Some((0.0, 1.0))));
let s = random_in_range(sat.or(Some((0.0, 1.0))));
let l = random_in_range(light.or(Some((0.0, 1.0))));
Ok(led_color.hsl_color(h, s, l))
}))
}
pub fn sprinkle_pattern(
&self,
pat: &mut [(u8, u8, u8)],
rgblst: &[(u8, u8, u8)],
freq: f64,
) -> Result<()> {
let n = Pattern::random_poisson(freq)?;
let mut rng = rand::thread_rng();
let leds = (0..pat.len()).collect::<Vec<_>>();
let inds = leds
.choose_multiple(&mut rng, n)
.cloned()
.collect::<Vec<_>>();
for &i in &inds {
let &color = rgblst
.choose(&mut rng)
.ok_or_else(|| anyhow!("Color list is empty"))?;
pat[i] = color;
}
Ok(())
}
pub fn make_alternating_color_pattern(
leds: usize,
rgblst: &[(u8, u8, u8)],
) -> Vec<(u8, u8, u8)> {
(0..leds).map(|i| rgblst[i % rgblst.len()]).collect()
}
pub fn make_color_spectrum_pattern(
leds: usize,
offset: usize,
lightness: f64,
led_color: &LedColor,
) -> Vec<(u8, u8, u8)> {
(0..leds)
.map(|i| {
let hue = ((i + offset) % leds) as f64 / leds as f64;
led_color.hsl_color(hue, 1.0, lightness)
})
.collect()
}
pub fn make_random_select_color_pattern(
leds: usize,
rgblst: &[(u8, u8, u8)],
probs: Option<&[f64]>,
) -> Result<Vec<(u8, u8, u8)>> {
let mut rng = rand::thread_rng();
let pattern = (0..leds)
.map(|_| {
if let Some(probs) = probs {
let ind = Pattern::random_discrete(probs)?;
Ok(rgblst[ind])
} else {
let ind = rng.gen_range(0..rgblst.len());
Ok(rgblst[ind])
}
})
.collect::<Result<Vec<_>>>()?;
Ok(pattern)
}
pub fn make_random_blend_color_pattern(
leds: usize,
rgb1: (u8, u8, u8),
rgb2: (u8, u8, u8),
) -> Vec<(u8, u8, u8)> {
let mut rng = rand::thread_rng();
(0..leds)
.map(|_| {
let prop = rng.gen::<f64>();
Pattern::blend_colors(rgb1, rgb2, prop)
})
.collect()
}
pub fn make_random_colors_pattern(
leds: usize,
lightness: f64,
led_color: &LedColor,
) -> Vec<(u8, u8, u8)> {
let mut rng = rand::thread_rng();
(0..leds)
.map(|_| {
let hue = rng.gen::<f64>();
led_color.hsl_color(hue, 1.0, lightness)
})
.collect()
}
pub fn make_random_lightness_pattern(
leds: usize,
hue: f64,
led_color: &LedColor,
) -> Vec<(u8, u8, u8)> {
let mut rng = rand::thread_rng();
(0..leds)
.map(|_| {
let lightness = rng.gen::<f64>() * 2.0 - 1.0;
led_color.hsl_color(hue, 1.0, lightness)
})
.collect()
}
pub fn make_random_hsl_pattern(
leds: usize,
hue: Option<(f64, f64)>,
sat: Option<(f64, f64)>,
light: Option<(f64, f64)>,
led_color: &LedColor, ) -> Result<Vec<(u8, u8, u8)>> {
let color_func = Pattern::random_hsl_color_func(hue, sat, light, led_color)?;
let pattern = (0..leds)
.map(|_| color_func())
.collect::<Result<Vec<_>>>()?;
Ok(pattern)
}
}