Common/
color.rs

1use std::cmp::{max, min, PartialEq};
2use crate::color::OutputType::{CMY, HSV, RGB};
3use crate::fixture::{Channel, ChannelError, FixtureError, PropertyType};
4
5/// Represents a color with its channel values for all supported color models
6/// (RGB, CMY, and HSV).
7pub struct Color {
8    output_type: OutputType,
9    color1: Option<Channel>,
10    color2: Option<Channel>,
11    color3: Option<Channel>,
12    red: u16,
13    green: u16,
14    blue: u16,
15    cyan: u16,
16    magenta: u16,
17    yellow: u16,
18    hue: u16,
19    saturation: u16,
20    value: u16,
21}
22
23/// A color template used by [`FixtureType`] to define how colors are
24/// generated for fixtures of that type.
25///
26/// Each color channel holds a DMX value and an optional second value
27/// for fine-grained 16-bit control
28pub struct ColorType {
29    output_type: Option<OutputType>,
30    color1: Option<(u16, Option<u16>)>,
31    color2: Option<(u16, Option<u16>)>,
32    color3: Option<(u16, Option<u16>)>,
33}
34#[derive(Debug, PartialEq, Copy, Clone)]
35enum OutputType {
36    RGB,
37    HSV,
38    CMY
39}
40
41/// Identifies a specific channel or property of a [`Color`].
42#[derive(Clone, Debug)]
43pub enum ColorPropertyType {
44    Red,
45    Green,
46    Blue,
47    Cyan,
48    Magenta,
49    Yellow,
50    Hue,
51    Saturation,
52    Value
53}
54
55impl ColorPropertyType {
56    fn new(color_number: u16, output_type: OutputType) -> Option<ColorPropertyType> {
57        match (color_number, output_type) {
58            (1, RGB) => Some(ColorPropertyType::Red),
59            (2, RGB) => Some(ColorPropertyType::Green),
60            (3, RGB) => Some(ColorPropertyType::Blue),
61
62            (1, CMY) => Some(ColorPropertyType::Cyan),
63            (2, CMY) => Some(ColorPropertyType::Magenta),
64            (3, CMY) => Some(ColorPropertyType::Yellow),
65
66            (1, HSV) => Some(ColorPropertyType::Hue),
67            (2, HSV) => Some(ColorPropertyType::Saturation),
68            (3, HSV) => Some(ColorPropertyType::Value),
69
70            _ => None
71        }
72    }
73
74    fn to_output_type(&self) -> (u16, OutputType) {
75        match self {
76            ColorPropertyType::Red => (1, RGB),
77            ColorPropertyType::Green => (2, RGB),
78            ColorPropertyType::Blue => (3, RGB),
79
80            ColorPropertyType::Cyan => (1, CMY),
81            ColorPropertyType::Magenta => (2, CMY),
82            ColorPropertyType::Yellow => (3, CMY),
83
84            ColorPropertyType::Hue => (1, HSV),
85            ColorPropertyType::Saturation => (2, HSV),
86            ColorPropertyType::Value => (3, HSV),
87        }
88    }
89
90    /// Parses a [`ColorPropertyType`] from a string.
91    ///
92    /// # Arguments
93    ///
94    /// * `property` - The property name (e.g. `"red"`, `"hue"`, `"saturation"`)
95    ///
96    /// # Errors
97    ///
98    /// Returns [`FixtureError::InvalidPropertyType`] if `property` does not
99    /// match any known color property.
100    pub fn from_string(property: &str) -> Result<ColorPropertyType, FixtureError> {
101        match property {
102            "red" => Ok(ColorPropertyType::Red),
103            "green" => Ok(ColorPropertyType::Green),
104            "blue" => Ok(ColorPropertyType::Blue),
105            "cyan" => Ok(ColorPropertyType::Cyan),
106            "magenta" => Ok(ColorPropertyType::Magenta),
107            "yellow" => Ok(ColorPropertyType::Yellow),
108            "hue" => Ok(ColorPropertyType::Hue),
109            "saturation" => Ok(ColorPropertyType::Saturation),
110            "value" => Ok(ColorPropertyType::Value),
111            _ => Err(FixtureError::InvalidPropertyType(property.to_string()))
112        }
113    }
114}
115
116impl ColorType {
117
118    /// Creates an empty [`ColorType`] with no output type or channels set.
119    pub(crate) fn new() -> Self {
120        Self {
121            output_type: None,
122            color1: None,
123            color2: None,
124            color3: None,
125        }
126    }
127
128    /// Parses a color channel name and assigns its DMX value to the corresponding slot.
129    ///
130    /// Accepts `"red"`, `"green"`, `"blue"` (RGB), `"cyan"`, `"magenta"`, `"yellow"` (CMY),
131    /// or `"hue"`, `"saturation"`, `"value"` (HSV).
132    ///
133    /// Returns `Ok(true)` if the channel was recognized and set, `Ok(false)` if the
134    /// channel name is unknown.
135    ///
136    /// # Errors
137    ///
138    /// Returns [`FixtureError::MultipleColorOutputTypes`] if the channel belongs to a
139    /// different color model than one already assigned (e.g. mixing RGB and HSV).
140    pub(crate) fn parse(&mut self, s: String, value: (u16, Option<u16>)) -> Result<bool, FixtureError> {
141        let (new_type, slot) = match s.as_str() {
142            "red"        => (RGB, 1),
143            "green"      => (RGB, 2),
144            "blue"       => (RGB, 3),
145
146            "cyan"       => (CMY, 1),
147            "magenta"    => (CMY, 2),
148            "yellow"     => (CMY, 3),
149
150            "hue"        => (HSV, 1),
151            "saturation" => (HSV, 2),
152            "value"      => (HSV, 3),
153
154            _ => return Ok(false),
155        };
156
157        if let Some(old_type) = self.output_type {
158            if old_type != new_type {
159                return Err(FixtureError::MultipleColorOutputTypes(
160                   format!("{s} is incompatible with {:?}", old_type)
161                ));
162            }
163        }
164
165        self.output_type = Some(new_type);
166
167        let target = match slot {
168            1 => &mut self.color1,
169            2 => &mut self.color2,
170            3 => &mut self.color3,
171            _ => unreachable!()
172        };
173
174        *target = Some(value);
175
176        Ok(true)
177    }
178
179    /// Returns `true` if at least one color channel has been set.
180    pub(crate) fn exists(&self) -> bool {
181        self.output_type.is_some()
182    }
183}
184
185impl Color {
186
187    /// Creates a [`Color`] from a [`ColorType`] template and reserves the required DMXChannels.
188    ///
189    /// Called internally by [`Fixture::new`].
190    ///
191    /// The default channel value depends on the color model: CMY channels default to
192    /// `u16::MAX` (full), all others default to `0`.
193    ///
194    /// # Arguments
195    ///
196    /// * `color_type`      - The template defining which channels and color model to use
197    /// * `device_channel`  - The DMXChannel offset of the device
198    /// * `universe`        - The DMX universe to reserve channels in
199    /// * `fixture_name`    - The fixture name, used for channel reservation
200    ///
201    /// # Errors
202    ///
203    /// Returns a [`ChannelError`] if any channel could not be created or reserved.
204    pub(crate) fn new(
205        color_type: &ColorType, device_channel: u16, universe: usize, fixture_name: &str
206    ) -> Result<Self, ChannelError> {
207        let default_value = if color_type.output_type == Some(CMY) {
208            u16::MAX
209        } else if let Some(_) = color_type.output_type {
210            0
211        } else {
212            // ColorType::new() must only be called when ColorType::exists() returns true
213            unreachable!();
214        };
215
216        let color1 = color_type.color1
217            .map(|c| Channel::new(c, default_value, device_channel))
218            .transpose()?;
219        let color2 = color_type.color2
220            .map(|c| Channel::new(c, default_value, device_channel))
221            .transpose()?;
222        let color3 = color_type.color3
223            .map(|c| Channel::new(c,default_value, device_channel))
224            .transpose()?;
225
226        let output_type = color_type.output_type.unwrap();
227
228
229
230        if let Some(color) = &color1 {
231            color.reserve_pending(fixture_name, universe)?;
232        }
233        if let Some(color) = &color2 {
234            color.reserve_pending(fixture_name, universe)?;
235        }
236        if let Some(color) = &color3 {
237            color.reserve_pending(fixture_name, universe)?;
238        }
239
240        if let Some(color) = &color1 {
241            let property = PropertyType::Color(ColorPropertyType::new(1, output_type).unwrap());
242            color.reserve_final(fixture_name, universe, property);
243        }
244        if let Some(color) = &color2 {
245            let property = PropertyType::Color(ColorPropertyType::new(2, output_type).unwrap());
246            color.reserve_final(fixture_name, universe, property);
247        }
248        if let Some(color) = &color3 {
249            let property = PropertyType::Color(ColorPropertyType::new(3, output_type).unwrap());
250            color.reserve_final(fixture_name, universe, property);
251        }
252
253
254
255
256        Ok(Self {
257            output_type,
258            color1,
259            color2,
260            color3,
261            red: 0,
262            green: 0,
263            blue: 0,
264            cyan: u16::MAX,
265            magenta: u16::MAX,
266            yellow: u16::MAX,
267            hue: 0,
268            saturation: 0,
269            value: 0,
270
271        })
272    }
273    
274    fn set_color(&mut self) {
275        let (v1, v2, v3) = match self.output_type {
276            RGB => (self.red, self.green, self.blue),
277            HSV => (self.hue, self.saturation, self.value),
278            CMY => (self.cyan, self.magenta, self.red),
279        };
280        if let Some(c) = self.color1.as_mut() {
281            c.value = v1
282        }
283        if let Some(c) = self.color2.as_mut() {
284            c.value = v2
285        }
286        if let Some(c) = self.color3.as_mut() {
287            c.value = v3
288        }
289    }
290
291    fn set_rgb(&mut self, red: u16, green: u16, blue: u16) {
292        self.red = red;
293        self.green = green;
294        self.blue = blue;
295
296        self.cyan = u16::MAX - red;
297        self.magenta = u16::MAX - green;
298        self.yellow = u16::MAX - blue;
299
300        let max = max(red, max(green, blue));
301        let min = min(red, min(green, blue));
302        let delta = max - min;
303        self.value = max;
304        self.saturation = if max == 0 {
305            0
306        } else {
307            ( (delta as f32 * u16::MAX as f32) / max as f32 ).round() as u16
308        };
309        let mut hue: i32 = (u16::MAX as f32 / 6.0_f32
310            * (if delta == 0 {
311                0.0
312            } else if max == red {
313                ((green as f32 - blue as f32) / delta as f32) % 6.0
314            } else if max == green {
315                ((blue as f32 - red as f32) / delta as f32) + 2.0
316            } else {
317                ((red as f32 - green as f32) / delta as f32) + 4.0
318            })) as i32;
319
320        if hue < 0 {
321            hue = hue + u16::MAX as i32;
322        }
323
324        self.hue = hue as u16;
325        
326        self.set_color()
327    }
328
329    fn set_hsv(&mut self, hue: u16, saturation: u16, value: u16) {
330        self.hue = hue;
331        self.saturation = saturation;
332        self.value = value;
333        //Achtung: es folgt eine unstetige Kackfunktion
334        let c = (value as f32 * (saturation as f32 / u16::MAX as f32)).round() as u16;
335        let m = value.saturating_sub(c);
336        let h = hue as f32 / (u16::MAX as f32 / 6f32);
337        let x = ( c as f32 * (1.0 - ((h % 2.0) - 1.0).abs()) ).round() as u16;
338
339        let (r, g, b) = match h {
340            n if n < 1.0 => (c, x, 0),
341            n if n < 2.0 => (x, c, 0),
342            n if n < 3.0 => (0, c, x),
343            n if n < 4.0 => (0, x, c),
344            n if n < 5.0 => (x, 0, c),
345            n if n <= 6.0 => (c, 0, x),
346            _ => (0, 0, 0)
347        };
348
349        self.red = r.saturating_add(m);
350        self.green = g.saturating_add(m);
351        self.blue = b.saturating_add(m);
352
353        self.cyan = u16::MAX - self.red;
354        self.magenta = u16::MAX - self.green;
355        self.yellow= u16::MAX - self.blue;
356        
357        self.set_color()
358    }
359
360    /// Sets a single color property and updates all DMXChannels accordingly.
361    ///
362    /// CMY values are converted to RGB internally (`u16::MAX - value`),
363    /// HSV values are converted to RGB via [`Color::set_hsv`].
364    /// RGB values are converted to HSV via ['Color::set_rgb']
365    pub fn set(&mut self, property: ColorPropertyType, value: u16) {
366        let (color_number, output_type) = property.to_output_type();
367
368        let (mut value1, mut value2, mut value3) = match output_type {
369            RGB => (self.red, self.green, self.blue),
370            CMY => (self.cyan, self.magenta, self.yellow),
371            HSV => (self.hue, self.saturation, self.value),
372        };
373
374        match color_number {
375            1 => value1 = value,
376            2 => value2 = value,
377            3 => value3 = value,
378            _ => unreachable!(),
379        }
380
381        match output_type {
382            RGB => self.set_rgb(value1, value2, value3),
383            HSV => self.set_hsv(value1, value2, value3),
384            CMY => self.set_rgb(
385                u16::MAX - value1,
386                u16::MAX - value2,
387                u16::MAX - value3
388            ),
389        }
390
391
392
393    }
394
395    /// Returns the current DMX output values of all active color channels.
396    ///
397    /// Each entry is a `(value, channel_index)` pair. For channels with 16-bit
398    /// fine control, two entries are returned — coarse first, then fine.
399    pub fn get_values(&self) -> Vec<(u16, u8)> {
400        let mut output = Vec::new();
401
402        if let Some(c) = self.color1.as_ref() {
403            output.push(c.get_value());
404            if let Some(fine_value) = c.get_fine_value() {
405                output.push(fine_value);
406            }
407        }
408
409        if let Some(c) = self.color2.as_ref() {
410            output.push(c.get_value());
411            if let Some(fine_value) = c.get_fine_value() {
412                output.push(fine_value);
413            }
414        }
415
416        if let Some(c) = self.color3.as_ref() {
417            output.push(c.get_value());
418            if let Some(fine_value) = c.get_fine_value() {
419                output.push(fine_value);
420            }
421        }
422
423
424        output
425
426    }
427}