glow_control_lib/led/
pattern.rs

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            // The sum of probabilities does not equal 1 within the tolerance
17            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(); // generates a float between 0.0 and 1.0
23        for (ind, &prob) in probs.iter().enumerate() {
24            acc += prob;
25            if acc >= r {
26                return Ok(ind);
27            }
28        }
29
30        // This point should not be reached if the distribution is valid
31        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, // Use explicit lifetime 'a
76    ) -> Result<Box<dyn Fn() -> Result<(u8, u8, u8)> + 'a>> {
77        // Helper to generate a random value within a given range or default to the full range if None
78        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            // Use the provided LedColor instance to convert HSL to RGB
91            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, // Pass a reference to LedColor
206    ) -> Result<Vec<(u8, u8, u8)>> {
207        // Now we pass the reference directly without dereferencing
208        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}