@@ -102,6 +102,137 @@ impl HslRepresentation {
102102    } 
103103} 
104104
105+ pub  struct  LchRepresentation ; 
106+ impl  LchRepresentation  { 
107+     // References available at http://brucelindbloom.com/ in the "Math" section 
108+ 
109+     // CIE Constants 
110+     // http://brucelindbloom.com/index.html?LContinuity.html (16) (17) 
111+     const  CIE_EPSILON :  f32  = 216.0  / 24389.0 ; 
112+     const  CIE_KAPPA :  f32  = 24389.0  / 27.0 ; 
113+     // D65 White Reference: 
114+     // https://en.wikipedia.org/wiki/Illuminant_D65#Definition 
115+     const  D65_WHITE_X :  f32  = 0.95047 ; 
116+     const  D65_WHITE_Y :  f32  = 1.0 ; 
117+     const  D65_WHITE_Z :  f32  = 1.08883 ; 
118+ 
119+     /// converts a color in LCH space to sRGB space 
120+ #[ inline]  
121+     pub  fn  lch_to_nonlinear_srgb ( lightness :  f32 ,  chroma :  f32 ,  hue :  f32 )  -> [ f32 ;  3 ]  { 
122+         let  lightness = lightness *  100.0 ; 
123+         let  chroma = chroma *  100.0 ; 
124+ 
125+         // convert LCH to Lab 
126+         // http://www.brucelindbloom.com/index.html?Eqn_LCH_to_Lab.html 
127+         let  l = lightness; 
128+         let  a = chroma *  hue. to_radians ( ) . cos ( ) ; 
129+         let  b = chroma *  hue. to_radians ( ) . sin ( ) ; 
130+ 
131+         // convert Lab to XYZ 
132+         // http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html 
133+         let  fy = ( l + 16.0 )  / 116.0 ; 
134+         let  fx = a / 500.0  + fy; 
135+         let  fz = fy - b / 200.0 ; 
136+         let  xr = { 
137+             let  fx3 = fx. powf ( 3.0 ) ; 
138+ 
139+             if  fx3 > Self :: CIE_EPSILON  { 
140+                 fx3
141+             }  else  { 
142+                 ( 116.0  *  fx - 16.0 )  / Self :: CIE_KAPPA 
143+             } 
144+         } ; 
145+         let  yr = if  l > Self :: CIE_EPSILON  *  Self :: CIE_KAPPA  { 
146+             ( ( l + 16.0 )  / 116.0 ) . powf ( 3.0 ) 
147+         }  else  { 
148+             l / Self :: CIE_KAPPA 
149+         } ; 
150+         let  zr = { 
151+             let  fz3 = fz. powf ( 3.0 ) ; 
152+ 
153+             if  fz3 > Self :: CIE_EPSILON  { 
154+                 fz3
155+             }  else  { 
156+                 ( 116.0  *  fz - 16.0 )  / Self :: CIE_KAPPA 
157+             } 
158+         } ; 
159+         let  x = xr *  Self :: D65_WHITE_X ; 
160+         let  y = yr *  Self :: D65_WHITE_Y ; 
161+         let  z = zr *  Self :: D65_WHITE_Z ; 
162+ 
163+         // XYZ to sRGB 
164+         // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html 
165+         // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html (sRGB, XYZ to RGB [M]-1) 
166+         let  red = x *  3.2404542  + y *  -1.5371385  + z *  -0.4985314 ; 
167+         let  green = x *  -0.969266  + y *  1.8760108  + z *  0.041556 ; 
168+         let  blue = x *  0.0556434  + y *  -0.2040259  + z *  1.0572252 ; 
169+ 
170+         [ 
171+             red. linear_to_nonlinear_srgb ( ) . max ( 0.0 ) . min ( 1.0 ) , 
172+             green. linear_to_nonlinear_srgb ( ) . max ( 0.0 ) . min ( 1.0 ) , 
173+             blue. linear_to_nonlinear_srgb ( ) . max ( 0.0 ) . min ( 1.0 ) , 
174+         ] 
175+     } 
176+ 
177+     /// converts a color in sRGB space to LCH space 
178+ #[ inline]  
179+     pub  fn  nonlinear_srgb_to_lch ( [ red,  green,  blue] :  [ f32 ;  3 ] )  -> ( f32 ,  f32 ,  f32 )  { 
180+         // RGB to XYZ 
181+         // http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html 
182+         let  red = red. nonlinear_to_linear_srgb ( ) ; 
183+         let  green = green. nonlinear_to_linear_srgb ( ) ; 
184+         let  blue = blue. nonlinear_to_linear_srgb ( ) ; 
185+ 
186+         // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html (sRGB, RGB to XYZ [M]) 
187+         let  x = red *  0.4124564  + green *  0.3575761  + blue *  0.1804375 ; 
188+         let  y = red *  0.2126729  + green *  0.7151522  + blue *  0.072175 ; 
189+         let  z = red *  0.0193339  + green *  0.119192  + blue *  0.9503041 ; 
190+ 
191+         // XYZ to Lab 
192+         // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html 
193+         let  xr = x / Self :: D65_WHITE_X ; 
194+         let  yr = y / Self :: D65_WHITE_Y ; 
195+         let  zr = z / Self :: D65_WHITE_Z ; 
196+         let  fx = if  xr > Self :: CIE_EPSILON  { 
197+             xr. cbrt ( ) 
198+         }  else  { 
199+             ( Self :: CIE_KAPPA  *  xr + 16.0 )  / 116.0 
200+         } ; 
201+         let  fy = if  yr > Self :: CIE_EPSILON  { 
202+             yr. cbrt ( ) 
203+         }  else  { 
204+             ( Self :: CIE_KAPPA  *  yr + 16.0 )  / 116.0 
205+         } ; 
206+         let  fz = if  yr > Self :: CIE_EPSILON  { 
207+             zr. cbrt ( ) 
208+         }  else  { 
209+             ( Self :: CIE_KAPPA  *  zr + 16.0 )  / 116.0 
210+         } ; 
211+         let  l = 116.0  *  fy - 16.0 ; 
212+         let  a = 500.0  *  ( fx - fy) ; 
213+         let  b = 200.0  *  ( fy - fz) ; 
214+ 
215+         // Lab to LCH 
216+         // http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html 
217+         let  c = ( a. powf ( 2.0 )  + b. powf ( 2.0 ) ) . sqrt ( ) ; 
218+         let  h = { 
219+             let  h = b. to_radians ( ) . atan2 ( a. to_radians ( ) ) . to_degrees ( ) ; 
220+ 
221+             if  h < 0.0  { 
222+                 h + 360.0 
223+             }  else  { 
224+                 h
225+             } 
226+         } ; 
227+ 
228+         ( 
229+             ( l / 100.0 ) . max ( 0.0 ) . min ( 1.5 ) , 
230+             ( c / 100.0 ) . max ( 0.0 ) . min ( 1.5 ) , 
231+             h, 
232+         ) 
233+     } 
234+ } 
235+ 
105236#[ cfg( test) ]  
106237mod  test { 
107238    use  super :: * ; 
@@ -214,4 +345,90 @@ mod test {
214345        assert_eq ! ( ( saturation *  100.0 ) . round( )  as  u32 ,  83 ) ; 
215346        assert_eq ! ( ( lightness *  100.0 ) . round( )  as  u32 ,  51 ) ; 
216347    } 
348+ 
349+     #[ test]  
350+     fn  lch_to_srgb ( )  { 
351+         // "truth" from http://www.brucelindbloom.com/ColorCalculator.html 
352+ 
353+         // black 
354+         let  ( lightness,  chroma,  hue)  = ( 0.0 ,  0.0 ,  0.0 ) ; 
355+         let  [ r,  g,  b]  = LchRepresentation :: lch_to_nonlinear_srgb ( lightness,  chroma,  hue) ; 
356+         assert_eq ! ( ( r *  100.0 ) . round( )  as  u32 ,  0 ) ; 
357+         assert_eq ! ( ( g *  100.0 ) . round( )  as  u32 ,  0 ) ; 
358+         assert_eq ! ( ( b *  100.0 ) . round( )  as  u32 ,  0 ) ; 
359+ 
360+         // white 
361+         let  ( lightness,  chroma,  hue)  = ( 1.0 ,  0.0 ,  0.0 ) ; 
362+         let  [ r,  g,  b]  = LchRepresentation :: lch_to_nonlinear_srgb ( lightness,  chroma,  hue) ; 
363+         assert_eq ! ( ( r *  100.0 ) . round( )  as  u32 ,  100 ) ; 
364+         assert_eq ! ( ( g *  100.0 ) . round( )  as  u32 ,  100 ) ; 
365+         assert_eq ! ( ( b *  100.0 ) . round( )  as  u32 ,  100 ) ; 
366+ 
367+         let  ( lightness,  chroma,  hue)  = ( 0.501236 ,  0.777514 ,  327.6608 ) ; 
368+         let  [ r,  g,  b]  = LchRepresentation :: lch_to_nonlinear_srgb ( lightness,  chroma,  hue) ; 
369+         assert_eq ! ( ( r *  100.0 ) . round( )  as  u32 ,  75 ) ; 
370+         assert_eq ! ( ( g *  100.0 ) . round( )  as  u32 ,  25 ) ; 
371+         assert_eq ! ( ( b *  100.0 ) . round( )  as  u32 ,  75 ) ; 
372+ 
373+         // a red 
374+         let  ( lightness,  chroma,  hue)  = ( 0.487122 ,  0.999531 ,  318.7684 ) ; 
375+         let  [ r,  g,  b]  = LchRepresentation :: lch_to_nonlinear_srgb ( lightness,  chroma,  hue) ; 
376+         assert_eq ! ( ( r *  100.0 ) . round( )  as  u32 ,  70 ) ; 
377+         assert_eq ! ( ( g *  100.0 ) . round( )  as  u32 ,  19 ) ; 
378+         assert_eq ! ( ( b *  100.0 ) . round( )  as  u32 ,  90 ) ; 
379+ 
380+         // a green 
381+         let  ( lightness,  chroma,  hue)  = ( 0.732929 ,  0.560925 ,  164.3216 ) ; 
382+         let  [ r,  g,  b]  = LchRepresentation :: lch_to_nonlinear_srgb ( lightness,  chroma,  hue) ; 
383+         assert_eq ! ( ( r *  100.0 ) . round( )  as  u32 ,  10 ) ; 
384+         assert_eq ! ( ( g *  100.0 ) . round( )  as  u32 ,  80 ) ; 
385+         assert_eq ! ( ( b *  100.0 ) . round( )  as  u32 ,  59 ) ; 
386+ 
387+         // a blue 
388+         let  ( lightness,  chroma,  hue)  = ( 0.335030 ,  1.176923 ,  306.7828 ) ; 
389+         let  [ r,  g,  b]  = LchRepresentation :: lch_to_nonlinear_srgb ( lightness,  chroma,  hue) ; 
390+         assert_eq ! ( ( r *  100.0 ) . round( )  as  u32 ,  25 ) ; 
391+         assert_eq ! ( ( g *  100.0 ) . round( )  as  u32 ,  10 ) ; 
392+         assert_eq ! ( ( b *  100.0 ) . round( )  as  u32 ,  92 ) ; 
393+     } 
394+ 
395+     #[ test]  
396+     fn  srgb_to_lch ( )  { 
397+         // "truth" from http://www.brucelindbloom.com/ColorCalculator.html 
398+ 
399+         // black 
400+         let  ( lightness,  chroma,  hue)  = LchRepresentation :: nonlinear_srgb_to_lch ( [ 0.0 ,  0.0 ,  0.0 ] ) ; 
401+         assert_eq ! ( ( lightness *  100.0 ) . round( )  as  u32 ,  0 ) ; 
402+         assert_eq ! ( ( chroma *  100.0 ) . round( )  as  u32 ,  0 ) ; 
403+         assert_eq ! ( hue. round( )  as  u32 ,  0 ) ; 
404+ 
405+         // white 
406+         let  ( lightness,  chroma,  hue)  = LchRepresentation :: nonlinear_srgb_to_lch ( [ 1.0 ,  1.0 ,  1.0 ] ) ; 
407+         assert_eq ! ( ( lightness *  100.0 ) . round( )  as  u32 ,  100 ) ; 
408+         assert_eq ! ( ( chroma *  100.0 ) . round( )  as  u32 ,  0 ) ; 
409+         assert_eq ! ( hue. round( )  as  u32 ,  0 ) ; 
410+ 
411+         let  ( lightness,  chroma,  hue)  = LchRepresentation :: nonlinear_srgb_to_lch ( [ 0.75 ,  0.25 ,  0.75 ] ) ; 
412+         assert_eq ! ( ( lightness *  100.0 ) . round( )  as  u32 ,  50 ) ; 
413+         assert_eq ! ( ( chroma *  100.0 ) . round( )  as  u32 ,  78 ) ; 
414+         assert_eq ! ( hue. round( )  as  u32 ,  328 ) ; 
415+ 
416+         // a red 
417+         let  ( lightness,  chroma,  hue)  = LchRepresentation :: nonlinear_srgb_to_lch ( [ 0.70 ,  0.19 ,  0.90 ] ) ; 
418+         assert_eq ! ( ( lightness *  100.0 ) . round( )  as  u32 ,  49 ) ; 
419+         assert_eq ! ( ( chroma *  100.0 ) . round( )  as  u32 ,  100 ) ; 
420+         assert_eq ! ( hue. round( )  as  u32 ,  319 ) ; 
421+ 
422+         // a green 
423+         let  ( lightness,  chroma,  hue)  = LchRepresentation :: nonlinear_srgb_to_lch ( [ 0.10 ,  0.80 ,  0.59 ] ) ; 
424+         assert_eq ! ( ( lightness *  100.0 ) . round( )  as  u32 ,  73 ) ; 
425+         assert_eq ! ( ( chroma *  100.0 ) . round( )  as  u32 ,  56 ) ; 
426+         assert_eq ! ( hue. round( )  as  u32 ,  164 ) ; 
427+ 
428+         // a blue 
429+         let  ( lightness,  chroma,  hue)  = LchRepresentation :: nonlinear_srgb_to_lch ( [ 0.25 ,  0.10 ,  0.92 ] ) ; 
430+         assert_eq ! ( ( lightness *  100.0 ) . round( )  as  u32 ,  34 ) ; 
431+         assert_eq ! ( ( chroma *  100.0 ) . round( )  as  u32 ,  118 ) ; 
432+         assert_eq ! ( hue. round( )  as  u32 ,  307 ) ; 
433+     } 
217434} 
0 commit comments