charts_rs/charts/
color.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13use serde::{Deserialize, Serialize};
14use substring::Substring;
15
16#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug, Default)]
17pub struct Color {
18    pub r: u8,
19    pub g: u8,
20    pub b: u8,
21    pub a: u8,
22}
23
24impl Color {
25    /// Converts color to hex format.
26    pub fn hex(&self) -> String {
27        format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
28    }
29    /// Converts color to rgba format.
30    pub fn rgba(&self) -> String {
31        let fa = (self.a as f32) / 255.0;
32        format!("rgba({},{},{},{:.1})", self.r, self.g, self.b, fa)
33    }
34    /// Gets opacity value of color.
35    pub fn opacity(&self) -> f32 {
36        let a = self.a as f32;
37        a / 255.0
38    }
39    /// Returns true if color is zero.
40    pub fn is_zero(&self) -> bool {
41        self.r == 0 && self.g == 0 && self.b == 0 && self.a == 0
42    }
43    /// Returns true if color is transparent.
44    pub fn is_transparent(&self) -> bool {
45        self.a == 0
46    }
47    /// Returns true if color is not transparent.
48    pub fn is_nontransparent(&self) -> bool {
49        self.a == 255
50    }
51    /// Returns white color.
52    pub fn white() -> Color {
53        (255, 255, 255).into()
54    }
55    /// Returns black color.
56    pub fn black() -> Color {
57        (0, 0, 0).into()
58    }
59    pub fn transparent() -> Color {
60        (0, 0, 0, 0).into()
61    }
62    /// Sets color with new alpha.
63    pub fn with_alpha(&self, a: u8) -> Color {
64        let mut c = *self;
65        c.a = a;
66        c
67    }
68    /// Returns ture if the color is light.
69    pub fn is_light(&self) -> bool {
70        let mut r = self.r as f64;
71        let mut g = self.g as f64;
72        let mut b = self.b as f64;
73        r = r * r * 0.299;
74        g = g * g * 0.587;
75        b = b * b * 0.114;
76        (r + g + b).sqrt() > 127.5
77    }
78}
79
80impl From<(u8, u8, u8)> for Color {
81    fn from(values: (u8, u8, u8)) -> Self {
82        Color {
83            r: values.0,
84            g: values.1,
85            b: values.2,
86            a: 255,
87        }
88    }
89}
90impl From<(u8, u8, u8, u8)> for Color {
91    fn from(values: (u8, u8, u8, u8)) -> Self {
92        Color {
93            r: values.0,
94            g: values.1,
95            b: values.2,
96            a: values.3,
97        }
98    }
99}
100
101fn parse_hex(hex: &str) -> u8 {
102    u8::from_str_radix(hex, 16).unwrap_or_default()
103}
104
105impl From<&str> for Color {
106    fn from(value: &str) -> Self {
107        let mut c = Color::default();
108        if !value.starts_with('#') {
109            return c;
110        }
111        let hex = value.substring(1, value.len());
112        if hex.len() == 3 {
113            c.r = parse_hex(&hex.substring(0, 1).repeat(2));
114            c.g = parse_hex(&hex.substring(1, 2).repeat(2));
115            c.b = parse_hex(&hex.substring(2, 3).repeat(2));
116        } else {
117            c.r = parse_hex(hex.substring(0, 2));
118            c.g = parse_hex(hex.substring(2, 4));
119            c.b = parse_hex(hex.substring(4, 6));
120        }
121        c.a = 255;
122        c
123    }
124}
125
126pub(crate) fn get_color(colors: &[Color], index: usize) -> Color {
127    let i = index % colors.len();
128    *colors.get(i).unwrap_or_else(|| &colors[0])
129}
130
131#[cfg(test)]
132mod tests {
133    use super::Color;
134    use pretty_assertions::assert_eq;
135    #[test]
136    fn color_hex() {
137        let mut c: Color = (200, 200, 200).into();
138        assert_eq!("#C8C8C8", c.hex());
139
140        c = (51, 51, 51).into();
141        assert_eq!("#333333", c.hex());
142    }
143    #[test]
144    fn color_rgba() {
145        let mut c: Color = (200, 200, 200).into();
146        assert_eq!("rgba(200,200,200,1.0)", c.rgba());
147        c = (51, 51, 51, 51).into();
148        assert_eq!("rgba(51,51,51,0.2)", c.rgba());
149    }
150    #[test]
151    fn color_opacity() {
152        let mut c: Color = (200, 200, 200).into();
153        assert_eq!(1.0, c.opacity());
154        c = (51, 51, 51, 51).into();
155        assert_eq!(0.2, c.opacity());
156    }
157    #[test]
158    fn color_is_zero() {
159        let mut c: Color = (200, 200, 200).into();
160        assert!(!c.is_zero());
161        c = (0, 0, 0, 0).into();
162        assert!(c.is_zero());
163    }
164    #[test]
165    fn color_is_transparent() {
166        let mut c: Color = (200, 200, 200).into();
167        assert!(!c.is_transparent());
168        assert!(c.is_nontransparent());
169        c = (200, 200, 200, 0).into();
170        assert!(c.is_transparent());
171        c = (200, 200, 200, 100).into();
172        assert!(!c.is_nontransparent());
173    }
174    #[test]
175    fn color_static() {
176        assert_eq!("rgba(255,255,255,1.0)", Color::white().rgba());
177        assert_eq!("rgba(0,0,0,1.0)", Color::black().rgba());
178    }
179
180    #[test]
181    fn color_with_alpha() {
182        let mut c = Color::white();
183        assert_eq!("rgba(255,255,255,1.0)", c.rgba());
184        c = c.with_alpha(51);
185        assert_eq!("rgba(255,255,255,0.2)", c.rgba());
186    }
187}