@@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
2
2
3
3
use crate :: clamp:: Clamp ;
4
4
use crate :: colorspace:: { self , ColorSpace } ;
5
+ use crate :: hs:: HS ;
5
6
use crate :: { WIDE_GAMUT_MAX_X , WIDE_GAMUT_MAX_Y } ;
6
7
7
8
#[ derive( Copy , Debug , Serialize , Deserialize , Clone , PartialEq ) ]
@@ -23,18 +24,56 @@ impl XY {
23
24
y : 0.32902 ,
24
25
} ;
25
26
26
- #[ allow( clippy:: many_single_char_names) ]
27
27
#[ must_use]
28
28
pub fn from_rgb ( red : u8 , green : u8 , blue : u8 ) -> ( Self , f64 ) {
29
29
let [ r, g, b] = [ red, green, blue] . map ( Clamp :: unit_from_u8) ;
30
+ Self :: from_rgb_unit ( r, g, b)
31
+ }
30
32
33
+ #[ allow( clippy:: many_single_char_names) ]
34
+ #[ must_use]
35
+ pub fn from_rgb_unit ( r : f64 , g : f64 , b : f64 ) -> ( Self , f64 ) {
31
36
let [ x, y, b] = Self :: COLOR_SPACE . rgb_to_xyy ( r, g, b) ;
32
37
33
38
let max_y = Self :: COLOR_SPACE . find_maximum_y ( x, y) ;
34
39
35
40
( Self { x, y } , b / max_y * 255.0 )
36
41
}
37
42
43
+ #[ must_use]
44
+ pub fn from_hs ( hs : HS ) -> ( Self , f64 ) {
45
+ let lightness: f64 = 0.5 ;
46
+ Self :: from_hsl ( hs, lightness)
47
+ }
48
+
49
+ #[ must_use]
50
+ pub fn from_hsl ( hs : HS , lightness : f64 ) -> ( Self , f64 ) {
51
+ let [ r, g, b] = Self :: rgb_from_hsl ( hs, lightness) ;
52
+ Self :: from_rgb_unit ( r, g, b)
53
+ }
54
+
55
+ #[ must_use]
56
+ pub fn rgb_from_hsl ( hs : HS , lightness : f64 ) -> [ f64 ; 3 ] {
57
+ let c = ( 1.0 - ( 2.0f64 . mul_add ( lightness, -1.0 ) ) . abs ( ) ) * hs. sat ;
58
+ let h = hs. hue * 6.0 ;
59
+ let x = c * ( 1.0 - ( h % 2.0 - 1.0 ) . abs ( ) ) ;
60
+ let m = lightness - c / 2.0 ;
61
+
62
+ if h < 1.0 {
63
+ [ m + c, m + x, m]
64
+ } else if h < 2.0 {
65
+ [ m + x, m + c, m]
66
+ } else if h < 3.0 {
67
+ [ m, m + c, m + x]
68
+ } else if h < 4.0 {
69
+ [ m, m + x, m + c]
70
+ } else if h < 5.0 {
71
+ [ m + x, m, m + c]
72
+ } else {
73
+ [ m + c, m + 0.0 , m + x]
74
+ }
75
+ }
76
+
38
77
#[ must_use]
39
78
pub fn to_rgb ( & self , brightness : f64 ) -> [ u8 ; 3 ] {
40
79
Self :: COLOR_SPACE
@@ -85,3 +124,50 @@ impl From<XY> for [f64; 2] {
85
124
[ value. x , value. y ]
86
125
}
87
126
}
127
+
128
+ #[ cfg( test) ]
129
+ mod tests {
130
+ use crate :: hs:: HS ;
131
+ use crate :: xy:: XY ;
132
+
133
+ macro_rules! compare {
134
+ ( $expr: expr, $value: expr) => {
135
+ let a = $expr;
136
+ let b = $value;
137
+ eprintln!( "{a} vs {b:.4}" ) ;
138
+ assert!( ( a - b) . abs( ) < 1e-4 ) ;
139
+ } ;
140
+ }
141
+
142
+ macro_rules! compare_rgb {
143
+ ( $a: expr, $b: expr) => { {
144
+ eprintln!( "Comparing r" ) ;
145
+ compare!( $a[ 0 ] , $b[ 0 ] ) ;
146
+ eprintln!( "Comparing g" ) ;
147
+ compare!( $a[ 1 ] , $b[ 1 ] ) ;
148
+ eprintln!( "Comparing b" ) ;
149
+ compare!( $a[ 2 ] , $b[ 2 ] ) ;
150
+ } } ;
151
+ }
152
+
153
+ macro_rules! compare_hsl_rgb {
154
+ ( $h: expr, $s: expr, $rgb: expr) => { {
155
+ let sat = $s;
156
+ compare_rgb!( XY :: rgb_from_hsl( HS { hue: $h, sat } , 0.5 ) , $rgb) ;
157
+ } } ;
158
+ }
159
+
160
+ #[ test]
161
+ fn rgb_from_hsl ( ) {
162
+ const ONE : f64 = 1.0 ;
163
+ let sat = 1.0 ;
164
+
165
+ compare_hsl_rgb ! ( 0.0 / 3.0 , sat, [ ONE , 0.0 , 0.0 ] ) ; // red
166
+ compare_hsl_rgb ! ( 0.5 / 3.0 , sat, [ ONE , ONE , 0.0 ] ) ; // red-green
167
+ compare_hsl_rgb ! ( 1.0 / 3.0 , sat, [ 0.0 , ONE , 0.0 ] ) ; // green
168
+ compare_hsl_rgb ! ( 1.5 / 3.0 , sat, [ 0.0 , ONE , ONE ] ) ; // green-blue
169
+ compare_hsl_rgb ! ( 2.0 / 3.0 , sat, [ 0.0 , 0.0 , ONE ] ) ; // blue
170
+ compare_hsl_rgb ! ( 2.5 / 3.0 , sat, [ ONE , 0.0 , ONE ] ) ; // blue-red
171
+ compare_hsl_rgb ! ( 3.0 / 3.0 , sat, [ ONE , 0.0 , 0.0 ] ) ; // red (wrapped around)
172
+ }
173
+ }
0 commit comments