1use crate::led::led_color::LedColor;
2use anyhow::{anyhow, Result};
3use rand::prelude::SliceRandom;
4use rand::Rng;
5use rand_distr::Distribution;
6use rand_distr::Poisson;
7
8pub struct Pattern;
9
10impl Pattern {
11 pub fn random_discrete(probs: &[f64]) -> Result<usize> {
12 let sum: f64 = probs.iter().sum();
13 const TOLERANCE: f64 = 1e-5;
14
15 if (sum - 1.0).abs() > TOLERANCE {
16 return Err(anyhow!("Probabilities do not sum up to 1.0"));
18 }
19
20 let mut rng = rand::thread_rng();
21 let mut acc = 0.0;
22 let r: f64 = rng.gen(); for (ind, &prob) in probs.iter().enumerate() {
24 acc += prob;
25 if acc >= r {
26 return Ok(ind);
27 }
28 }
29
30 Err(anyhow!("Invalid probability distribution"))
32 }
33
34 pub fn random_poisson(lam: f64) -> Result<usize> {
35 let poisson = Poisson::new(lam).map_err(|e| anyhow!("Poisson error: {}", e))?;
36 let mut rng = rand::thread_rng();
37 let sample = poisson.sample(&mut rng) as u64;
38
39 if sample > usize::MAX as u64 {
40 return Err(anyhow!("Sampled value is too large for usize"));
41 }
42
43 Ok(sample as usize)
44 }
45
46 pub fn dim_color(rgb: (u8, u8, u8), prop: f64) -> (u8, u8, u8) {
47 let dimmed_r = (rgb.0 as f64 * prop).clamp(0.0, 255.0) as u8;
48 let dimmed_g = (rgb.1 as f64 * prop).clamp(0.0, 255.0) as u8;
49 let dimmed_b = (rgb.2 as f64 * prop).clamp(0.0, 255.0) as u8;
50 (dimmed_r, dimmed_g, dimmed_b)
51 }
52
53 pub fn blend_colors(rgb1: (u8, u8, u8), rgb2: (u8, u8, u8), prop: f64) -> (u8, u8, u8) {
54 let blend =
55 |c1, c2| ((c1 as f64 * (1.0 - prop) + c2 as f64 * prop).clamp(0.0, 255.0) as u8);
56 let blended_r = blend(rgb1.0, rgb2.0);
57 let blended_g = blend(rgb1.1, rgb2.1);
58 let blended_b = blend(rgb1.2, rgb2.2);
59 (blended_r, blended_g, blended_b)
60 }
61
62 pub fn random_color() -> (u8, u8, u8) {
63 let mut rng = rand::thread_rng();
64 let r = rng.gen_range(0..=255);
65 let g = rng.gen_range(0..=255);
66 let b = rng.gen_range(0..=255);
67 (r, g, b)
68 }
69
70 #[allow(clippy::type_complexity)]
71 pub fn random_hsl_color_func<'a>(
72 hue: Option<(f64, f64)>,
73 sat: Option<(f64, f64)>,
74 light: Option<(f64, f64)>,
75 led_color: &'a LedColor, ) -> Result<Box<dyn Fn() -> Result<(u8, u8, u8)> + 'a>> {
77 let random_in_range = |range_option: Option<(f64, f64)>| -> f64 {
79 let mut rng = rand::thread_rng();
80 match range_option {
81 Some((start, end)) => rng.gen_range(start..=end),
82 None => rng.gen(),
83 }
84 };
85
86 Ok(Box::new(move || {
87 let h = random_in_range(hue.or(Some((0.0, 1.0))));
88 let s = random_in_range(sat.or(Some((0.0, 1.0))));
89 let l = random_in_range(light.or(Some((0.0, 1.0))));
90 Ok(led_color.hsl_color(h, s, l))
92 }))
93 }
94
95 pub fn sprinkle_pattern(
96 &self,
97 pat: &mut [(u8, u8, u8)],
98 rgblst: &[(u8, u8, u8)],
99 freq: f64,
100 ) -> Result<()> {
101 let n = Pattern::random_poisson(freq)?;
102 let mut rng = rand::thread_rng();
103 let leds = (0..pat.len()).collect::<Vec<_>>();
104 let inds = leds
105 .choose_multiple(&mut rng, n)
106 .cloned()
107 .collect::<Vec<_>>();
108 for &i in &inds {
109 let &color = rgblst
110 .choose(&mut rng)
111 .ok_or_else(|| anyhow!("Color list is empty"))?;
112 pat[i] = color;
113 }
114 Ok(())
115 }
116
117 pub fn make_alternating_color_pattern(
118 leds: usize,
119 rgblst: &[(u8, u8, u8)],
120 ) -> Vec<(u8, u8, u8)> {
121 (0..leds).map(|i| rgblst[i % rgblst.len()]).collect()
122 }
123
124 pub fn make_color_spectrum_pattern(
125 leds: usize,
126 offset: usize,
127 lightness: f64,
128 led_color: &LedColor,
129 ) -> Vec<(u8, u8, u8)> {
130 (0..leds)
131 .map(|i| {
132 let hue = ((i + offset) % leds) as f64 / leds as f64;
133 led_color.hsl_color(hue, 1.0, lightness)
134 })
135 .collect()
136 }
137
138 pub fn make_random_select_color_pattern(
139 leds: usize,
140 rgblst: &[(u8, u8, u8)],
141 probs: Option<&[f64]>,
142 ) -> Result<Vec<(u8, u8, u8)>> {
143 let mut rng = rand::thread_rng();
144 let pattern = (0..leds)
145 .map(|_| {
146 if let Some(probs) = probs {
147 let ind = Pattern::random_discrete(probs)?;
148 Ok(rgblst[ind])
149 } else {
150 let ind = rng.gen_range(0..rgblst.len());
151 Ok(rgblst[ind])
152 }
153 })
154 .collect::<Result<Vec<_>>>()?;
155 Ok(pattern)
156 }
157
158 pub fn make_random_blend_color_pattern(
159 leds: usize,
160 rgb1: (u8, u8, u8),
161 rgb2: (u8, u8, u8),
162 ) -> Vec<(u8, u8, u8)> {
163 let mut rng = rand::thread_rng();
164 (0..leds)
165 .map(|_| {
166 let prop = rng.gen::<f64>();
167 Pattern::blend_colors(rgb1, rgb2, prop)
168 })
169 .collect()
170 }
171
172 pub fn make_random_colors_pattern(
173 leds: usize,
174 lightness: f64,
175 led_color: &LedColor,
176 ) -> Vec<(u8, u8, u8)> {
177 let mut rng = rand::thread_rng();
178 (0..leds)
179 .map(|_| {
180 let hue = rng.gen::<f64>();
181 led_color.hsl_color(hue, 1.0, lightness)
182 })
183 .collect()
184 }
185
186 pub fn make_random_lightness_pattern(
187 leds: usize,
188 hue: f64,
189 led_color: &LedColor,
190 ) -> Vec<(u8, u8, u8)> {
191 let mut rng = rand::thread_rng();
192 (0..leds)
193 .map(|_| {
194 let lightness = rng.gen::<f64>() * 2.0 - 1.0;
195 led_color.hsl_color(hue, 1.0, lightness)
196 })
197 .collect()
198 }
199
200 pub fn make_random_hsl_pattern(
201 leds: usize,
202 hue: Option<(f64, f64)>,
203 sat: Option<(f64, f64)>,
204 light: Option<(f64, f64)>,
205 led_color: &LedColor, ) -> Result<Vec<(u8, u8, u8)>> {
207 let color_func = Pattern::random_hsl_color_func(hue, sat, light, led_color)?;
209 let pattern = (0..leds)
210 .map(|_| color_func())
211 .collect::<Result<Vec<_>>>()?;
212 Ok(pattern)
213 }
214}