@@ -6,6 +6,7 @@ use plotters_backend::{BackendColor, BackendStyle};
66#[ cfg( feature = "serialization" ) ]
77use serde:: { Deserialize , Serialize } ;
88
9+ use std:: fmt;
910use std:: marker:: PhantomData ;
1011
1112/// Any color representation
@@ -139,12 +140,53 @@ impl BackendStyle for RGBColor {
139140#[ cfg_attr( feature = "serialization" , derive( Serialize , Deserialize ) ) ]
140141pub struct HSLColor ( pub f64 , pub f64 , pub f64 ) ;
141142
143+ /// Errors that can occur when constructing an `HSLColor`.
144+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
145+ pub enum HSLColorError {
146+ /// Hue (or degrees input) must be finite.
147+ NonFiniteHue ,
148+ /// Saturation must be in the closed interval `[0, 1]`.
149+ SaturationOutOfRange ,
150+ /// Lightness must be in the closed interval `[0, 1]`.
151+ LightnessOutOfRange ,
152+ }
153+
154+ impl fmt:: Display for HSLColorError {
155+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
156+ match self {
157+ HSLColorError :: NonFiniteHue => f. write_str ( "hue must be finite" ) ,
158+ HSLColorError :: SaturationOutOfRange => f. write_str ( "saturation must be in [0, 1]" ) ,
159+ HSLColorError :: LightnessOutOfRange => f. write_str ( "lightness must be in [0, 1]" ) ,
160+ }
161+ }
162+ }
163+
164+ impl std:: error:: Error for HSLColorError { }
165+
142166impl HSLColor {
167+ /// Creates an `HSLColor` from normalized components, returning an error if any are out of range.
168+ pub fn try_new ( h : f64 , s : f64 , l : f64 ) -> Result < Self , HSLColorError > {
169+ if !h. is_finite ( ) {
170+ return Err ( HSLColorError :: NonFiniteHue ) ;
171+ }
172+ if !s. is_finite ( ) || s < 0.0 || s > 1.0 {
173+ return Err ( HSLColorError :: SaturationOutOfRange ) ;
174+ }
175+ if !l. is_finite ( ) || l < 0.0 || l > 1.0 {
176+ return Err ( HSLColorError :: LightnessOutOfRange ) ;
177+ }
178+ Ok ( Self ( h, s, l) )
179+ }
180+
143181 /// Creates an `HSLColor` from degrees, wrapping into `[0, 360)` before normalizing.
144- /// Prefer this helper when specifying hue in degrees.
182+ /// Prefer this helper when specifying hue in degrees. Returns an error if saturation
183+ /// or lightness fall outside `[0, 1]` or if the input hue is non-finite.
145184 #[ inline]
146- pub fn from_degrees ( h_deg : f64 , s : f64 , l : f64 ) -> Self {
147- Self ( h_deg. rem_euclid ( 360.0 ) / 360.0 , s, l)
185+ pub fn from_degrees ( h_deg : f64 , s : f64 , l : f64 ) -> Result < Self , HSLColorError > {
186+ if !h_deg. is_finite ( ) {
187+ return Err ( HSLColorError :: NonFiniteHue ) ;
188+ }
189+ Self :: try_new ( h_deg. rem_euclid ( 360.0 ) / 360.0 , s, l)
148190 }
149191}
150192
@@ -207,30 +249,64 @@ mod hue_robustness_tests {
207249
208250 #[ test]
209251 fn degrees_passed_via_helper_should_work_for_common_cases ( ) {
210- let red = HSLColor :: from_degrees ( 0.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
252+ let red = HSLColor :: from_degrees ( 0.0 , 1.0 , 0.5 )
253+ . unwrap ( )
254+ . to_backend_color ( )
255+ . rgb ;
211256 assert_eq ! ( red, ( 255 , 0 , 0 ) ) ;
212257
213- let green = HSLColor :: from_degrees ( 120.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
258+ let green = HSLColor :: from_degrees ( 120.0 , 1.0 , 0.5 )
259+ . unwrap ( )
260+ . to_backend_color ( )
261+ . rgb ;
214262 assert_eq ! ( green, ( 0 , 255 , 0 ) ) ;
215263
216- let blue = HSLColor :: from_degrees ( 240.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
264+ let blue = HSLColor :: from_degrees ( 240.0 , 1.0 , 0.5 )
265+ . unwrap ( )
266+ . to_backend_color ( )
267+ . rgb ;
217268 assert_eq ! ( blue, ( 0 , 0 , 255 ) ) ;
218269 }
219270
220271 #[ test]
221272 fn from_degrees_wraps_and_matches_normalized ( ) {
222273 let normalized = HSLColor ( 120.0 / 360.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
223- let via_helper = HSLColor :: from_degrees ( 120.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
274+ let via_helper = HSLColor :: from_degrees ( 120.0 , 1.0 , 0.5 )
275+ . unwrap ( )
276+ . to_backend_color ( )
277+ . rgb ;
224278 assert_eq ! ( normalized, via_helper) ;
225279
226280 let wrap_positive =
227- HSLColor :: from_degrees ( 720.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
281+ HSLColor :: from_degrees ( 720.0 , 1.0 , 0.5 ) . unwrap ( ) . to_backend_color ( ) . rgb ;
228282 let wrap_negative =
229- HSLColor :: from_degrees ( -120.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
283+ HSLColor :: from_degrees ( -120.0 , 1.0 , 0.5 ) . unwrap ( ) . to_backend_color ( ) . rgb ;
230284 let canonical =
231- HSLColor :: from_degrees ( 0.0 , 1.0 , 0.5 ) . to_backend_color ( ) . rgb ;
285+ HSLColor :: from_degrees ( 0.0 , 1.0 , 0.5 ) . unwrap ( ) . to_backend_color ( ) . rgb ;
232286
233287 assert_eq ! ( wrap_positive, canonical) ;
234- assert_eq ! ( wrap_negative, HSLColor :: from_degrees( 240.0 , 1.0 , 0.5 ) . to_backend_color( ) . rgb) ;
288+ assert_eq ! (
289+ wrap_negative,
290+ HSLColor :: from_degrees( 240.0 , 1.0 , 0.5 )
291+ . unwrap( )
292+ . to_backend_color( )
293+ . rgb
294+ ) ;
295+ }
296+
297+ #[ test]
298+ fn from_degrees_rejects_out_of_range_components ( ) {
299+ assert ! ( matches!(
300+ HSLColor :: from_degrees( 0.0 , -0.1 , 0.5 ) ,
301+ Err ( HSLColorError :: SaturationOutOfRange )
302+ ) ) ;
303+ assert ! ( matches!(
304+ HSLColor :: from_degrees( 0.0 , 0.5 , 1.1 ) ,
305+ Err ( HSLColorError :: LightnessOutOfRange )
306+ ) ) ;
307+ assert ! ( matches!(
308+ HSLColor :: from_degrees( f64 :: INFINITY , 0.5 , 0.5 ) ,
309+ Err ( HSLColorError :: NonFiniteHue )
310+ ) ) ;
235311 }
236312}
0 commit comments