dreg_core/style.rs
1#![allow(clippy::unreadable_literal)]
2
3
4
5use std::{fmt, str::FromStr};
6
7
8
9#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
10pub struct Style {
11 pub color_mode: ColorMode,
12 pub fg: Option<Color>,
13 pub bg: Option<Color>,
14 #[cfg(feature = "underline-color")]
15 pub underline_color: Option<Color>,
16 pub add_modifier: Modifier,
17 pub sub_modifier: Modifier,
18}
19
20impl Style {
21 pub const fn new() -> Self {
22 Self {
23 color_mode: ColorMode::overwrite(),
24 fg: None,
25 bg: None,
26 #[cfg(feature = "underline-color")]
27 underline_color: None,
28 add_modifier: Modifier::empty(),
29 sub_modifier: Modifier::empty(),
30 }
31 }
32
33 pub const fn fg(mut self, color: Color) -> Self {
34 self.fg = Some(color);
35 self
36 }
37
38 pub const fn bg(mut self, color: Color) -> Self {
39 self.bg = Some(color);
40 self
41 }
42
43 pub const fn color_mode(mut self, color_mode: ColorMode) -> Self {
44 self.color_mode = color_mode;
45 self
46 }
47
48 pub const fn add_modifier(mut self, modifier: Modifier) -> Self {
49 self.sub_modifier = self.sub_modifier.difference(modifier);
50 self.add_modifier = self.add_modifier.union(modifier);
51 self
52 }
53
54 pub const fn remove_modifier(mut self, modifier: Modifier) -> Self {
55 self.add_modifier = self.add_modifier.difference(modifier);
56 self.sub_modifier = self.sub_modifier.union(modifier);
57 self
58 }
59}
60
61impl Style {
62 pub fn patch<S: Into<Self>>(mut self, other: S) -> Self {
63 let other: Style = other.into();
64 match other.color_mode {
65 ColorMode::Overwrite => {
66 self.fg = other.fg.or(self.fg);
67 self.bg = other.bg.or(self.bg);
68 }
69 ColorMode::Additive => {
70 if let Some(other_fg) = other.fg {
71 if let Some(self_fg) = self.fg {
72 let other_rgb = other_fg.as_rgb();
73 let self_rgb = self_fg.as_rgb();
74
75 // let r = ((other_rgb.0 as u16) * (self_rgb.0 as u16)).div_ceil(255) as u8;
76 // let g = ((other_rgb.1 as u16) * (self_rgb.1 as u16)).div_ceil(255) as u8;
77 // let b = ((other_rgb.2 as u16) * (self_rgb.2 as u16)).div_ceil(255) as u8;
78 let r = other_rgb.0.saturating_add(self_rgb.0);
79 let g = other_rgb.1.saturating_add(self_rgb.1);
80 let b = other_rgb.2.saturating_add(self_rgb.2);
81
82 self.fg = Some(Color::Rgb(r, g, b));
83 } else {
84 self.fg = Some(other_fg);
85 }
86 }
87 if let Some(other_bg) = other.bg {
88 if let Some(self_bg) = self.bg {
89 let other_rgb = other_bg.as_rgb();
90 let self_rgb = self_bg.as_rgb();
91
92 let r = other_rgb.0.saturating_add(self_rgb.0);
93 let g = other_rgb.1.saturating_add(self_rgb.1);
94 let b = other_rgb.2.saturating_add(self_rgb.2);
95 // let r = ((other_rgb.0 as u16) * (self_rgb.0 as u16)).div_ceil(255) as u8;
96 // let g = ((other_rgb.1 as u16) * (self_rgb.1 as u16)).div_ceil(255) as u8;
97 // let b = ((other_rgb.2 as u16) * (self_rgb.2 as u16)).div_ceil(255) as u8;
98
99 self.bg = Some(Color::Rgb(r, g, b));
100 } else {
101 self.bg = Some(other_bg);
102 }
103 }
104 }
105 ColorMode::Subtractive => {
106 if let Some(other_fg) = other.fg {
107 if let Some(self_fg) = self.fg {
108 let other_rgb = other_fg.as_rgb();
109 let self_rgb = self_fg.as_rgb();
110 let r = other_rgb.0.saturating_sub(self_rgb.0);
111 let g = other_rgb.1.saturating_sub(self_rgb.1);
112 let b = other_rgb.2.saturating_sub(self_rgb.2);
113
114 self.fg = Some(Color::Rgb(r, g, b));
115 } else {
116 self.fg = Some(other_fg);
117 }
118 }
119 if let Some(other_bg) = other.bg {
120 if let Some(self_bg) = self.bg {
121 let other_rgb = other_bg.as_rgb();
122 let self_rgb = self_bg.as_rgb();
123 let r = other_rgb.0.saturating_sub(self_rgb.0);
124 let g = other_rgb.1.saturating_sub(self_rgb.1);
125 let b = other_rgb.2.saturating_sub(self_rgb.2);
126
127 self.bg = Some(Color::Rgb(r, g, b));
128 } else {
129 self.bg = Some(other_bg);
130 }
131 }
132 }
133 ColorMode::Blend => {
134 if let Some(other_fg) = other.fg {
135 if let Some(self_fg) = self.fg {
136 let other_rgb = other_fg.as_rgb();
137 let self_rgb = self_fg.as_rgb();
138 let r = other_rgb.0.saturating_add(self_rgb.0.saturating_sub(other_rgb.0));
139 let g = other_rgb.1.saturating_add(self_rgb.1.saturating_sub(other_rgb.1));
140 let b = other_rgb.2.saturating_add(self_rgb.2.saturating_sub(other_rgb.2));
141
142 self.fg = Some(Color::Rgb(r, g, b));
143 } else {
144 self.fg = Some(other_fg);
145 }
146 }
147 if let Some(other_bg) = other.bg {
148 if let Some(self_bg) = self.bg {
149 let other_rgb = other_bg.as_rgb();
150 let self_rgb = self_bg.as_rgb();
151 let r = other_rgb.0.saturating_add(self_rgb.0.saturating_sub(other_rgb.0));
152 let g = other_rgb.1.saturating_add(self_rgb.1.saturating_sub(other_rgb.1));
153 let b = other_rgb.2.saturating_add(self_rgb.2.saturating_sub(other_rgb.2));
154
155 self.bg = Some(Color::Rgb(r, g, b));
156 } else {
157 self.bg = Some(other_bg);
158 }
159 }
160 }
161 ColorMode::Mix => {
162 if let Some(other_fg) = other.fg {
163 if let Some(self_fg) = self.fg {
164 let other_rgb = other_fg.as_rgb();
165 let self_rgb = self_fg.as_rgb();
166 let r = self_rgb.0.saturating_add(other_rgb.0.saturating_sub(self_rgb.0));
167 let g = self_rgb.1.saturating_add(other_rgb.1.saturating_sub(self_rgb.1));
168 let b = self_rgb.2.saturating_add(other_rgb.2.saturating_sub(self_rgb.2));
169
170 self.fg = Some(Color::Rgb(r, g, b));
171 } else {
172 self.fg = Some(other_fg);
173 }
174 }
175 if let Some(other_bg) = other.bg {
176 if let Some(self_bg) = self.bg {
177 let other_rgb = other_bg.as_rgb();
178 let self_rgb = self_bg.as_rgb();
179 let r = self_rgb.0.saturating_add(other_rgb.0.saturating_sub(self_rgb.0));
180 let g = self_rgb.1.saturating_add(other_rgb.1.saturating_sub(self_rgb.1));
181 let b = self_rgb.2.saturating_add(other_rgb.2.saturating_sub(self_rgb.2));
182
183 self.bg = Some(Color::Rgb(r, g, b));
184 } else {
185 self.bg = Some(other_bg);
186 }
187 }
188 }
189 }
190
191 #[cfg(feature = "underline-color")]
192 {
193 self.underline_color = other.underline_color.or(self.underline_color);
194 }
195
196 self.add_modifier.remove(other.sub_modifier);
197 self.add_modifier.insert(other.add_modifier);
198 self.sub_modifier.remove(other.add_modifier);
199 self.sub_modifier.insert(other.sub_modifier);
200
201 self
202 }
203}
204
205
206
207// ================================================================================================
208
209
210
211/// The way in which an [`Element`] is rendered to the screen.
212#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
213pub enum ColorMode {
214 /// Ignore the buffer's current contents and overwrite cells with the colors provided to the
215 /// renderer.
216 #[default]
217 Overwrite,
218 /// Add the renderer's colors to the current cells in the buffer.
219 Additive,
220 Subtractive,
221 /// Blend the renderer's colors with the current cells in the buffer. This is absolutely
222 /// necessary for transparent images and overlays.
223 Blend,
224 Mix,
225}
226
227impl ColorMode {
228 pub const fn overwrite() -> Self {
229 Self::Overwrite
230 }
231
232 pub const fn additive() -> Self {
233 Self::Additive
234 }
235
236 pub const fn subtractive() -> Self {
237 Self::Subtractive
238 }
239
240 pub const fn blend() -> Self {
241 Self::Blend
242 }
243
244 pub const fn mix() -> Self {
245 Self::Mix
246 }
247}
248
249
250
251bitflags::bitflags! {
252 /// Modifier changes the way a piece of text is displayed.
253 ///
254 /// They are bitflags so they can easily be composed.
255 ///
256 /// `From<Modifier> for Style` is implemented so you can use `Modifier` anywhere that accepts
257 /// `Into<Style>`.
258 ///
259 /// ## Examples
260 ///
261 /// ```rust
262 /// use dreg::prelude::*;
263 ///
264 /// let m = Modifier::BOLD | Modifier::ITALIC;
265 /// ```
266 #[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
267 pub struct Modifier: u16 {
268 const BOLD = 0b0000_0000_0001;
269 const DIM = 0b0000_0000_0010;
270 const ITALIC = 0b0000_0000_0100;
271 const UNDERLINED = 0b0000_0000_1000;
272 const SLOW_BLINK = 0b0000_0001_0000;
273 const RAPID_BLINK = 0b0000_0010_0000;
274 const REVERSED = 0b0000_0100_0000;
275 const HIDDEN = 0b0000_1000_0000;
276 const CROSSED_OUT = 0b0001_0000_0000;
277 }
278}
279
280/// Implement the `Debug` trait for `Modifier` manually.
281///
282/// This will avoid printing the empty modifier as 'Borders(0x0)' and instead print it as 'NONE'.
283impl fmt::Debug for Modifier {
284 /// Format the modifier as `NONE` if the modifier is empty or as a list of flags separated by
285 /// `|` otherwise.
286 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
287 if self.is_empty() {
288 return write!(f, "NONE");
289 }
290 write!(f, "{}", self.0)
291 }
292}
293
294
295
296/// ANSI Color
297///
298/// All colors from the [ANSI color table] are supported (though some names are not exactly the
299/// same).
300///
301/// | Color Name | Color | Foreground | Background |
302/// |----------------|-------------------------|------------|------------|
303/// | `black` | [`Color::Black`] | 30 | 40 |
304/// | `red` | [`Color::Red`] | 31 | 41 |
305/// | `green` | [`Color::Green`] | 32 | 42 |
306/// | `yellow` | [`Color::Yellow`] | 33 | 43 |
307/// | `blue` | [`Color::Blue`] | 34 | 44 |
308/// | `magenta` | [`Color::Magenta`] | 35 | 45 |
309/// | `cyan` | [`Color::Cyan`] | 36 | 46 |
310/// | `gray`* | [`Color::Gray`] | 37 | 47 |
311/// | `darkgray`* | [`Color::DarkGray`] | 90 | 100 |
312/// | `lightred` | [`Color::LightRed`] | 91 | 101 |
313/// | `lightgreen` | [`Color::LightGreen`] | 92 | 102 |
314/// | `lightyellow` | [`Color::LightYellow`] | 93 | 103 |
315/// | `lightblue` | [`Color::LightBlue`] | 94 | 104 |
316/// | `lightmagenta` | [`Color::LightMagenta`] | 95 | 105 |
317/// | `lightcyan` | [`Color::LightCyan`] | 96 | 106 |
318/// | `white`* | [`Color::White`] | 97 | 107 |
319///
320/// - `gray` is sometimes called `white` - this is not supported as we use `white` for bright white
321/// - `gray` is sometimes called `silver` - this is supported
322/// - `darkgray` is sometimes called `light black` or `bright black` (both are supported)
323/// - `white` is sometimes called `light white` or `bright white` (both are supported)
324/// - we support `bright` and `light` prefixes for all colors
325/// - we support `-` and `_` and ` ` as separators for all colors
326/// - we support both `gray` and `grey` spellings
327///
328/// `From<Color> for Style` is implemented by creating a style with the foreground color set to the
329/// given color. This allows you to use colors anywhere that accepts `Into<Style>`.
330///
331/// # Example
332///
333/// ```
334/// use std::str::FromStr;
335///
336/// use ratatui::prelude::*;
337///
338/// assert_eq!(Color::from_str("red"), Ok(Color::Red));
339/// assert_eq!("red".parse(), Ok(Color::Red));
340/// assert_eq!("lightred".parse(), Ok(Color::LightRed));
341/// assert_eq!("light red".parse(), Ok(Color::LightRed));
342/// assert_eq!("light-red".parse(), Ok(Color::LightRed));
343/// assert_eq!("light_red".parse(), Ok(Color::LightRed));
344/// assert_eq!("lightRed".parse(), Ok(Color::LightRed));
345/// assert_eq!("bright red".parse(), Ok(Color::LightRed));
346/// assert_eq!("bright-red".parse(), Ok(Color::LightRed));
347/// assert_eq!("silver".parse(), Ok(Color::Gray));
348/// assert_eq!("dark-grey".parse(), Ok(Color::DarkGray));
349/// assert_eq!("dark gray".parse(), Ok(Color::DarkGray));
350/// assert_eq!("light-black".parse(), Ok(Color::DarkGray));
351/// assert_eq!("white".parse(), Ok(Color::White));
352/// assert_eq!("bright white".parse(), Ok(Color::White));
353/// ```
354///
355/// [ANSI color table]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
356#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
357pub enum Color {
358 /// Resets the foreground or background color
359 #[default]
360 Reset,
361 /// ANSI Color: Black. Foreground: 30, Background: 40
362 Black,
363 /// ANSI Color: Red. Foreground: 31, Background: 41
364 Red,
365 /// ANSI Color: Green. Foreground: 32, Background: 42
366 Green,
367 /// ANSI Color: Yellow. Foreground: 33, Background: 43
368 Yellow,
369 /// ANSI Color: Blue. Foreground: 34, Background: 44
370 Blue,
371 /// ANSI Color: Magenta. Foreground: 35, Background: 45
372 Magenta,
373 /// ANSI Color: Cyan. Foreground: 36, Background: 46
374 Cyan,
375 /// ANSI Color: White. Foreground: 37, Background: 47
376 ///
377 /// Note that this is sometimes called `silver` or `white` but we use `white` for bright white
378 Gray,
379 /// ANSI Color: Bright Black. Foreground: 90, Background: 100
380 ///
381 /// Note that this is sometimes called `light black` or `bright black` but we use `dark gray`
382 DarkGray,
383 /// ANSI Color: Bright Red. Foreground: 91, Background: 101
384 LightRed,
385 /// ANSI Color: Bright Green. Foreground: 92, Background: 102
386 LightGreen,
387 /// ANSI Color: Bright Yellow. Foreground: 93, Background: 103
388 LightYellow,
389 /// ANSI Color: Bright Blue. Foreground: 94, Background: 104
390 LightBlue,
391 /// ANSI Color: Bright Magenta. Foreground: 95, Background: 105
392 LightMagenta,
393 /// ANSI Color: Bright Cyan. Foreground: 96, Background: 106
394 LightCyan,
395 /// ANSI Color: Bright White. Foreground: 97, Background: 107
396 /// Sometimes called `bright white` or `light white` in some terminals
397 White,
398 /// An RGB color.
399 ///
400 /// Note that only terminals that support 24-bit true color will display this correctly.
401 /// Notably versions of Windows Terminal prior to Windows 10 and macOS Terminal.app do not
402 /// support this.
403 ///
404 /// If the terminal does not support true color, code using the [`TermwizBackend`] will
405 /// fallback to the default text color. Crossterm and Termion do not have this capability and
406 /// the display will be unpredictable (e.g. Terminal.app may display glitched blinking text).
407 /// See <https://github.com/ratatui-org/ratatui/issues/475> for an example of this problem.
408 ///
409 /// See also: <https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit>
410 ///
411 /// [`TermwizBackend`]: crate::backend::TermwizBackend
412 Rgb(u8, u8, u8),
413 /// An 8-bit 256 color.
414 ///
415 /// See also <https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit>
416 Indexed(u8),
417}
418
419impl Color {
420 /// Convert a u32 to a Color
421 ///
422 /// The u32 should be in the format 0x00RRGGBB.
423 pub const fn from_u32(u: u32) -> Self {
424 let r = (u >> 16) as u8;
425 let g = (u >> 8) as u8;
426 let b = u as u8;
427 Self::Rgb(r, g, b)
428 }
429
430 pub fn as_rgb(&self) -> (u8, u8, u8) {
431 match self {
432 Self::Rgb(r, g, b) => (*r, *g, *b),
433 _ => (0, 0, 0),
434 }
435 }
436}
437
438
439
440/// Error type indicating a failure to parse a color string.
441#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
442pub struct ParseColorError;
443
444impl fmt::Display for ParseColorError {
445 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 write!(f, "Failed to parse Colors")
447 }
448}
449
450impl std::error::Error for ParseColorError {}
451
452/// Converts a string representation to a `Color` instance.
453///
454/// The `from_str` function attempts to parse the given string and convert it to the corresponding
455/// `Color` variant. It supports named colors, RGB values, and indexed colors. If the string cannot
456/// be parsed, a `ParseColorError` is returned.
457///
458/// See the [`Color`] documentation for more information on the supported color names.
459impl FromStr for Color {
460 type Err = ParseColorError;
461
462 fn from_str(s: &str) -> Result<Self, Self::Err> {
463 Ok(
464 // There is a mix of different color names and formats in the wild.
465 // This is an attempt to support as many as possible.
466 match s
467 .to_lowercase()
468 .replace([' ', '-', '_'], "")
469 .replace("bright", "light")
470 .replace("grey", "gray")
471 .replace("silver", "gray")
472 .replace("lightblack", "darkgray")
473 .replace("lightwhite", "white")
474 .replace("lightgray", "white")
475 .as_ref()
476 {
477 "reset" => Self::Reset,
478 "black" => Self::Black,
479 "red" => Self::Red,
480 "green" => Self::Green,
481 "yellow" => Self::Yellow,
482 "blue" => Self::Blue,
483 "magenta" => Self::Magenta,
484 "cyan" => Self::Cyan,
485 "gray" => Self::Gray,
486 "darkgray" => Self::DarkGray,
487 "lightred" => Self::LightRed,
488 "lightgreen" => Self::LightGreen,
489 "lightyellow" => Self::LightYellow,
490 "lightblue" => Self::LightBlue,
491 "lightmagenta" => Self::LightMagenta,
492 "lightcyan" => Self::LightCyan,
493 "white" => Self::White,
494 _ => {
495 if let Ok(index) = s.parse::<u8>() {
496 Self::Indexed(index)
497 } else if let Some((r, g, b)) = parse_hex_color(s) {
498 Self::Rgb(r, g, b)
499 } else {
500 return Err(ParseColorError);
501 }
502 }
503 },
504 )
505 }
506}
507
508fn parse_hex_color(input: &str) -> Option<(u8, u8, u8)> {
509 if !input.starts_with('#') || input.len() != 7 {
510 return None;
511 }
512 let r = u8::from_str_radix(input.get(1..3)?, 16).ok()?;
513 let g = u8::from_str_radix(input.get(3..5)?, 16).ok()?;
514 let b = u8::from_str_radix(input.get(5..7)?, 16).ok()?;
515 Some((r, g, b))
516}
517
518impl fmt::Display for Color {
519 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
520 match self {
521 Self::Reset => write!(f, "Reset"),
522 Self::Black => write!(f, "Black"),
523 Self::Red => write!(f, "Red"),
524 Self::Green => write!(f, "Green"),
525 Self::Yellow => write!(f, "Yellow"),
526 Self::Blue => write!(f, "Blue"),
527 Self::Magenta => write!(f, "Magenta"),
528 Self::Cyan => write!(f, "Cyan"),
529 Self::Gray => write!(f, "Gray"),
530 Self::DarkGray => write!(f, "DarkGray"),
531 Self::LightRed => write!(f, "LightRed"),
532 Self::LightGreen => write!(f, "LightGreen"),
533 Self::LightYellow => write!(f, "LightYellow"),
534 Self::LightBlue => write!(f, "LightBlue"),
535 Self::LightMagenta => write!(f, "LightMagenta"),
536 Self::LightCyan => write!(f, "LightCyan"),
537 Self::White => write!(f, "White"),
538 Self::Rgb(r, g, b) => write!(f, "#{r:02X}{g:02X}{b:02X}"),
539 Self::Indexed(i) => write!(f, "{i}"),
540 }
541 }
542}
543
544impl Color {
545 /// Converts a HSL representation to a `Color::Rgb` instance.
546 ///
547 /// The `from_hsl` function converts the Hue, Saturation and Lightness values to a
548 /// corresponding `Color` RGB equivalent.
549 ///
550 /// Hue values should be in the range [0, 360].
551 /// Saturation and L values should be in the range [0, 100].
552 /// Values that are not in the range are clamped to be within the range.
553 ///
554 /// # Examples
555 ///
556 /// ```
557 /// use ratatui::prelude::*;
558 ///
559 /// let color: Color = Color::from_hsl(360.0, 100.0, 100.0);
560 /// assert_eq!(color, Color::Rgb(255, 255, 255));
561 ///
562 /// let color: Color = Color::from_hsl(0.0, 0.0, 0.0);
563 /// assert_eq!(color, Color::Rgb(0, 0, 0));
564 /// ```
565 pub fn from_hsl(h: f64, s: f64, l: f64) -> Self {
566 // Clamp input values to valid ranges
567 let h = h.clamp(0.0, 360.0);
568 let s = s.clamp(0.0, 100.0);
569 let l = l.clamp(0.0, 100.0);
570
571 // Delegate to the function for normalized HSL to RGB conversion
572 normalized_hsl_to_rgb(h / 360.0, s / 100.0, l / 100.0)
573 }
574}
575
576/// Converts normalized HSL (Hue, Saturation, Lightness) values to RGB (Red, Green, Blue) color
577/// representation. H, S, and L values should be in the range [0, 1].
578///
579/// Based on <https://github.com/killercup/hsl-rs/blob/b8a30e11afd75f262e0550725333293805f4ead0/src/lib.rs>
580fn normalized_hsl_to_rgb(hue: f64, saturation: f64, lightness: f64) -> Color {
581 // This function can be made into `const` in the future.
582 // This comment contains the relevant information for making it `const`.
583 //
584 // If it is `const` and made public, users can write the following:
585 //
586 // ```rust
587 // const SLATE_50: Color = normalized_hsl_to_rgb(0.210, 0.40, 0.98);
588 // ```
589 //
590 // For it to be const now, we need `#![feature(const_fn_floating_point_arithmetic)]`
591 // Tracking issue: https://github.com/rust-lang/rust/issues/57241
592 //
593 // We would also need to remove the use of `.round()` in this function, i.e.:
594 //
595 // ```rust
596 // Color::Rgb((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
597 // ```
598
599 // Initialize RGB components
600 let red: f64;
601 let green: f64;
602 let blue: f64;
603
604 // Check if the color is achromatic (grayscale)
605 if saturation == 0.0 {
606 red = lightness;
607 green = lightness;
608 blue = lightness;
609 } else {
610 // Calculate RGB components for colored cases
611 let q = if lightness < 0.5 {
612 lightness * (1.0 + saturation)
613 } else {
614 lightness + saturation - lightness * saturation
615 };
616 let p = 2.0 * lightness - q;
617 red = hue_to_rgb(p, q, hue + 1.0 / 3.0);
618 green = hue_to_rgb(p, q, hue);
619 blue = hue_to_rgb(p, q, hue - 1.0 / 3.0);
620 }
621
622 // Scale RGB components to the range [0, 255] and create a Color::Rgb instance
623 Color::Rgb(
624 (red * 255.0).round() as u8,
625 (green * 255.0).round() as u8,
626 (blue * 255.0).round() as u8,
627 )
628}
629
630/// Helper function to calculate RGB component for a specific hue value.
631fn hue_to_rgb(p: f64, q: f64, t: f64) -> f64 {
632 // Adjust the hue value to be within the valid range [0, 1]
633 let mut t = t;
634 if t < 0.0 {
635 t += 1.0;
636 }
637 if t > 1.0 {
638 t -= 1.0;
639 }
640
641 // Calculate the RGB component based on the hue value
642 if t < 1.0 / 6.0 {
643 p + (q - p) * 6.0 * t
644 } else if t < 1.0 / 2.0 {
645 q
646 } else if t < 2.0 / 3.0 {
647 p + (q - p) * (2.0 / 3.0 - t) * 6.0
648 } else {
649 p
650 }
651}