@@ -8,46 +8,39 @@ use serde::{Deserialize, Serialize};
88
99use std:: marker:: PhantomData ;
1010
11- /// Any color representation
11+ /// Common trait for all color representations.
1212pub trait Color {
13- /// Normalize this color representation to the backend color
1413 fn to_backend_color ( & self ) -> BackendColor ;
1514
16- /// Convert the RGB representation to the standard RGB tuple
1715 #[ inline( always) ]
1816 fn rgb ( & self ) -> ( u8 , u8 , u8 ) {
1917 self . to_backend_color ( ) . rgb
2018 }
2119
22- /// Get the alpha channel of the color
2320 #[ inline( always) ]
2421 fn alpha ( & self ) -> f64 {
2522 self . to_backend_color ( ) . alpha
2623 }
2724
28- /// Mix the color with given opacity
2925 fn mix ( & self , value : f64 ) -> RGBAColor {
3026 let ( r, g, b) = self . rgb ( ) ;
3127 let a = self . alpha ( ) * value;
3228 RGBAColor ( r, g, b, a)
3329 }
3430
35- /// Convert the color into the RGBA color which is internally used by Plotters
3631 fn to_rgba ( & self ) -> RGBAColor {
3732 let ( r, g, b) = self . rgb ( ) ;
3833 let a = self . alpha ( ) ;
3934 RGBAColor ( r, g, b, a)
4035 }
4136
42- /// Make a filled style form the color
4337 fn filled ( & self ) -> ShapeStyle
4438 where
4539 Self : Sized ,
4640 {
4741 Into :: < ShapeStyle > :: into ( self ) . filled ( )
4842 }
4943
50- /// Make a shape style with stroke width from a color
5144 fn stroke_width ( & self , width : u32 ) -> ShapeStyle
5245 where
5346 Self : Sized ,
@@ -62,10 +55,6 @@ impl<T: Color> Color for &'_ T {
6255 }
6356}
6457
65- /// The RGBA representation of the color, Plotters use RGBA as the internal representation
66- /// of color
67- ///
68- /// If you want to directly create a RGB color with transparency use [RGBColor::mix]
6958#[ derive( Copy , Clone , PartialEq , Debug , Default ) ]
7059#[ cfg_attr( feature = "serialization" , derive( Serialize , Deserialize ) ) ]
7160pub struct RGBAColor ( pub u8 , pub u8 , pub u8 , pub f64 ) ;
@@ -86,13 +75,11 @@ impl From<RGBColor> for RGBAColor {
8675 }
8776}
8877
89- /// A color in the given palette
9078#[ derive( Copy , Clone , Eq , PartialEq , Hash , Debug , Default ) ]
9179#[ cfg_attr( feature = "serialization" , derive( Serialize , Deserialize ) ) ]
9280pub struct PaletteColor < P : Palette > ( usize , PhantomData < P > ) ;
9381
9482impl < P : Palette > PaletteColor < P > {
95- /// Pick a color from the palette
9683 pub fn pick ( idx : usize ) -> PaletteColor < P > {
9784 PaletteColor ( idx % P :: COLORS . len ( ) , PhantomData )
9885 }
@@ -108,7 +95,6 @@ impl<P: Palette> Color for PaletteColor<P> {
10895 }
10996}
11097
111- /// The color described by its RGB value
11298#[ derive( Copy , Clone , Eq , PartialEq , Hash , Debug , Default ) ]
11399#[ cfg_attr( feature = "serialization" , derive( Serialize , Deserialize ) ) ]
114100pub struct RGBColor ( pub u8 , pub u8 , pub u8 ) ;
@@ -128,31 +114,40 @@ impl Color for RGBColor {
128114 }
129115 }
130116}
117+
131118impl BackendStyle for RGBColor {
132119 fn color ( & self ) -> BackendColor {
133120 self . to_backend_color ( )
134121 }
135122}
136123
137- /// The color described by HSL color space
138124#[ derive( Copy , Clone , PartialEq , Debug , Default ) ]
139125#[ cfg_attr( feature = "serialization" , derive( Serialize , Deserialize ) ) ]
140126pub struct HSLColor ( pub f64 , pub f64 , pub f64 ) ;
141127
128+ impl HSLColor {
129+ #[ inline]
130+ pub fn from_degrees ( h_deg : f64 , s : f64 , l : f64 ) -> Self {
131+ Self ( h_deg / 360.0 , s, l)
132+ }
133+ }
134+
142135impl Color for HSLColor {
143136 #[ inline( always) ]
144137 #[ allow( clippy:: many_single_char_names) ]
145138 fn to_backend_color ( & self ) -> BackendColor {
146- let ( h, s, l) = (
147- self . 0 . clamp ( 0.0 , 1.0 ) ,
148- self . 1 . clamp ( 0.0 , 1.0 ) ,
149- self . 2 . clamp ( 0.0 , 1.0 ) ,
150- ) ;
139+ let h = if self . 0 > 1.0 {
140+ ( self . 0 / 360.0 ) . rem_euclid ( 1.0 )
141+ } else {
142+ self . 0 . rem_euclid ( 1.0 )
143+ } ;
144+ let s = self . 1 . clamp ( 0.0 , 1.0 ) ;
145+ let l = self . 2 . clamp ( 0.0 , 1.0 ) ;
151146
152147 if s == 0.0 {
153- let value = ( l * 255.0 ) . round ( ) as u8 ;
148+ let v = ( l * 255.0 ) . round ( ) as u8 ;
154149 return BackendColor {
155- rgb : ( value , value , value ) ,
150+ rgb : ( v , v , v ) ,
156151 alpha : 1.0 ,
157152 } ;
158153 }
@@ -164,13 +159,8 @@ impl Color for HSLColor {
164159 } ;
165160 let p = 2.0 * l - q;
166161
167- let cvt = |mut t| {
168- if t < 0.0 {
169- t += 1.0 ;
170- }
171- if t > 1.0 {
172- t -= 1.0 ;
173- }
162+ let cvt = |t : f64 | {
163+ let t = t. rem_euclid ( 1.0 ) ;
174164 let value = if t < 1.0 / 6.0 {
175165 p + ( q - p) * 6.0 * t
176166 } else if t < 1.0 / 2.0 {
@@ -189,3 +179,29 @@ impl Color for HSLColor {
189179 }
190180 }
191181}
182+
183+ #[ cfg( test) ]
184+ mod hue_robustness_tests {
185+ use super :: * ;
186+
187+ #[ test]
188+ fn degrees_passed_directly_should_work_for_common_cases ( ) {
189+ let red = HSLColor ( 0.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
190+ assert_eq ! ( red, ( 255 , 0 , 0 ) ) ;
191+
192+ let green = HSLColor ( 120.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
193+ assert_eq ! ( green, ( 0 , 255 , 0 ) ) ;
194+
195+ let blue = HSLColor ( 240.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
196+ assert_eq ! ( blue, ( 0 , 0 , 255 ) ) ;
197+ }
198+
199+ #[ test]
200+ fn from_degrees_and_direct_degrees_are_equivalent ( ) {
201+ for & deg in & [ 0.0 , 30.0 , 60.0 , 120.0 , 180.0 , 240.0 , 300.0 , 360.0 ] {
202+ let a = HSLColor ( deg, 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
203+ let b = HSLColor :: from_degrees ( deg, 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
204+ assert_eq ! ( a, b) ;
205+ }
206+ }
207+ }
0 commit comments