1use super::palette::Palette;
2use super::ShapeStyle;
3
4use plotters_backend::{BackendColor, BackendStyle};
5
6use std::marker::PhantomData;
7
8/// Any color representation
9pub trait Color {
10 /// Normalize this color representation to the backend color
11 fn to_backend_color(&self) -> BackendColor;
12
13 /// Convert the RGB representation to the standard RGB tuple
14 #[inline(always)]
15 fn rgb(&self) -> (u8, u8, u8) {
16 self.to_backend_color().rgb
17 }
18
19 /// Get the alpha channel of the color
20 #[inline(always)]
21 fn alpha(&self) -> f64 {
22 self.to_backend_color().alpha
23 }
24
25 /// Mix the color with given opacity
26 fn mix(&self, value: f64) -> RGBAColor {
27 let (r, g, b) = self.rgb();
28 let a = self.alpha() * value;
29 RGBAColor(r, g, b, a)
30 }
31
32 /// Convert the color into the RGBA color which is internally used by Plotters
33 fn to_rgba(&self) -> RGBAColor {
34 let (r, g, b) = self.rgb();
35 let a = self.alpha();
36 RGBAColor(r, g, b, a)
37 }
38
39 /// Make a filled style form the color
40 fn filled(&self) -> ShapeStyle
41 where
42 Self: Sized,
43 {
44 Into::<ShapeStyle>::into(self).filled()
45 }
46
47 /// Make a shape style with stroke width from a color
48 fn stroke_width(&self, width: u32) -> ShapeStyle
49 where
50 Self: Sized,
51 {
52 Into::<ShapeStyle>::into(self).stroke_width(width)
53 }
54}
55
56impl<T: Color> Color for &'_ T {
57 fn to_backend_color(&self) -> BackendColor {
58 <T as Color>::to_backend_color(*self)
59 }
60}
61
62/// The RGBA representation of the color, Plotters use RGBA as the internal representation
63/// of color
64///
65/// If you want to directly create a RGB color with transparency use [RGBColor::mix]
66#[derive(Copy, Clone, PartialEq, Debug, Default)]
67pub struct RGBAColor(pub u8, pub u8, pub u8, pub f64);
68
69impl Color for RGBAColor {
70 #[inline(always)]
71 fn to_backend_color(&self) -> BackendColor {
72 BackendColor {
73 rgb: (self.0, self.1, self.2),
74 alpha: self.3,
75 }
76 }
77}
78
79impl From<RGBColor> for RGBAColor {
80 fn from(rgb: RGBColor) -> Self {
81 Self(rgb.0, rgb.1, rgb.2, 1.0)
82 }
83}
84
85/// A color in the given palette
86#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
87pub struct PaletteColor<P: Palette>(usize, PhantomData<P>);
88
89impl<P: Palette> PaletteColor<P> {
90 /// Pick a color from the palette
91 pub fn pick(idx: usize) -> PaletteColor<P> {
92 PaletteColor(idx % P::COLORS.len(), PhantomData)
93 }
94}
95
96impl<P: Palette> Color for PaletteColor<P> {
97 #[inline(always)]
98 fn to_backend_color(&self) -> BackendColor {
99 BackendColor {
100 rgb: P::COLORS[self.0],
101 alpha: 1.0,
102 }
103 }
104}
105
106/// The color described by its RGB value
107#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
108pub struct RGBColor(pub u8, pub u8, pub u8);
109
110impl BackendStyle for RGBAColor {
111 fn color(&self) -> BackendColor {
112 self.to_backend_color()
113 }
114}
115
116impl Color for RGBColor {
117 #[inline(always)]
118 fn to_backend_color(&self) -> BackendColor {
119 BackendColor {
120 rgb: (self.0, self.1, self.2),
121 alpha: 1.0,
122 }
123 }
124}
125impl BackendStyle for RGBColor {
126 fn color(&self) -> BackendColor {
127 self.to_backend_color()
128 }
129}
130
131/// The color described by HSL color space
132#[derive(Copy, Clone, PartialEq, Debug, Default)]
133pub struct HSLColor(pub f64, pub f64, pub f64);
134
135impl Color for HSLColor {
136 #[inline(always)]
137 #[allow(clippy::many_single_char_names)]
138 fn to_backend_color(&self) -> BackendColor {
139 let (h, s, l) = (
140 self.0.min(1.0).max(0.0),
141 self.1.min(1.0).max(0.0),
142 self.2.min(1.0).max(0.0),
143 );
144
145 if s == 0.0 {
146 let value = (l * 255.0).round() as u8;
147 return BackendColor {
148 rgb: (value, value, value),
149 alpha: 1.0,
150 };
151 }
152
153 let q = if l < 0.5 {
154 l * (1.0 + s)
155 } else {
156 l + s - l * s
157 };
158 let p = 2.0 * l - q;
159
160 let cvt = |mut t| {
161 if t < 0.0 {
162 t += 1.0;
163 }
164 if t > 1.0 {
165 t -= 1.0;
166 }
167 let value = if t < 1.0 / 6.0 {
168 p + (q - p) * 6.0 * t
169 } else if t < 1.0 / 2.0 {
170 q
171 } else if t < 2.0 / 3.0 {
172 p + (q - p) * (2.0 / 3.0 - t) * 6.0
173 } else {
174 p
175 };
176 (value * 255.0).round() as u8
177 };
178
179 BackendColor {
180 rgb: (cvt(h + 1.0 / 3.0), cvt(h), cvt(h - 1.0 / 3.0)),
181 alpha: 1.0,
182 }
183 }
184}
185