hyperion/instance/
core.rs

1use crate::{
2    color::{color_to16, ChannelAdjustments, ChannelAdjustmentsBuilder},
3    image::{prelude::*, Reducer},
4    models::{Color, Color16, InstanceConfig, Leds},
5};
6
7use super::{BlackBorderDetector, MuxedMessage, MuxedMessageData, Smoothing, SmoothingUpdate};
8
9/// Core part of an instance
10///
11/// This handles incoming message and computes LED colors.
12pub struct Core {
13    leds: Leds,
14    color_data: Vec<Color16>,
15    black_border_detector: BlackBorderDetector,
16    channel_adjustments: ChannelAdjustments,
17    smoothing: Smoothing,
18    notified_inconsistent_led_data: bool,
19    reducer: Reducer,
20}
21
22impl Core {
23    pub async fn new(config: &InstanceConfig) -> Self {
24        let led_count = config.leds.leds.len();
25        let black_border_detector = BlackBorderDetector::new(config.black_border_detector.clone());
26        let channel_adjustments = ChannelAdjustmentsBuilder::new(&config.color)
27            .led_count(led_count as _)
28            .build();
29        let smoothing = Smoothing::new(config.smoothing.clone(), led_count);
30
31        Self {
32            leds: config.leds.clone(),
33            color_data: vec![Color16::default(); led_count],
34            black_border_detector,
35            channel_adjustments,
36            smoothing,
37            notified_inconsistent_led_data: false,
38            reducer: Default::default(),
39        }
40    }
41
42    fn handle_color(&mut self, color: Color) {
43        self.color_data.fill(color_to16(color));
44    }
45
46    fn handle_image(&mut self, image: &impl Image) {
47        // Update the black border
48        self.black_border_detector.process(image);
49        let black_border = self.black_border_detector.current_border();
50
51        // Crop the image using a view
52        let image = {
53            let (x, y) = black_border.get_ranges(image.width(), image.height());
54            image.wrap(x, y)
55        };
56
57        // Update the 16-bit color data from the LED ranges and the image
58        self.reducer
59            .reduce(&image, &self.leds.leds[..], &mut self.color_data);
60    }
61
62    fn handle_led_colors(&mut self, led_colors: &[Color]) {
63        let led_count = self.color_data.len();
64        let data_count = led_colors.len();
65
66        let (src, dst, fill) = if led_count == data_count {
67            self.notified_inconsistent_led_data = false;
68
69            let (dst, fill) = self.color_data.split_at_mut(led_count);
70            (led_colors, dst, fill)
71        } else if led_count < data_count {
72            if !self.notified_inconsistent_led_data {
73                self.notified_inconsistent_led_data = true;
74                warn!(extra = %(data_count - led_count), "too much LED data");
75            }
76
77            let (dst, fill) = self.color_data.split_at_mut(led_count);
78            (&led_colors[..led_count], dst, fill)
79        } else {
80            if !self.notified_inconsistent_led_data {
81                self.notified_inconsistent_led_data = true;
82                warn!(missing = %(led_count - data_count), "not enough LED data");
83            }
84
85            let (dst, fill) = self.color_data.split_at_mut(data_count);
86            (led_colors, dst, fill)
87        };
88
89        for (led_mut, color) in dst.iter_mut().zip(src.iter()) {
90            *led_mut = color_to16(*color);
91        }
92
93        fill.fill(Color16::default());
94    }
95
96    pub fn handle_message(&mut self, message: MuxedMessage) {
97        // Update color data
98        match message.data() {
99            MuxedMessageData::SolidColor { color, .. } => {
100                self.handle_color(*color);
101            }
102            MuxedMessageData::Image { image, .. } => {
103                self.handle_image(image.as_ref());
104            }
105            MuxedMessageData::LedColors { led_colors, .. } => {
106                self.handle_led_colors(led_colors);
107            }
108        }
109
110        // In-place transform colors
111        self.channel_adjustments.apply(&mut self.color_data);
112
113        // Update the smoothing state with the new color data
114        self.smoothing.set_target(&self.color_data);
115    }
116
117    pub async fn update(&mut self) -> (&[Color], SmoothingUpdate) {
118        self.smoothing.update().await
119    }
120}