1use crate::channel::{
7 ChannelCast, ChannelFormatCast, ColorChannel, FreeChannel, FreeChannelScalar,
8};
9use crate::color::{Bounded, Broadcast, Color, Flatten, FromTuple, HomogeneousColor, Lerp};
10use crate::convert::FromColor;
11use crate::linalg::Matrix3;
12use crate::tags::LmsTag;
13use crate::xyz::Xyz;
14#[cfg(feature = "approx")]
15use approx;
16use num_traits;
17use std::fmt;
18use std::marker::PhantomData;
19use std::mem;
20use std::slice;
21
22pub trait LmsModel<T>: Clone + PartialEq {
24 fn forward_transform() -> Matrix3<T>;
26 fn inverse_transform() -> Matrix3<T>;
28}
29
30#[repr(C)]
44#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
45pub struct Lms<T, Model> {
46 l: FreeChannel<T>,
47 m: FreeChannel<T>,
48 s: FreeChannel<T>,
49 model: PhantomData<Model>,
50}
51
52#[derive(Copy, Clone, Debug, PartialEq)]
54pub struct CieCam2002;
55#[derive(Copy, Clone, Debug, PartialEq)]
57pub struct CieCam97s;
58#[derive(Copy, Clone, Debug, PartialEq)]
60pub struct Bradford;
61
62pub type LmsCam2002<T> = Lms<T, CieCam2002>;
64pub type LmsCam97s<T> = Lms<T, CieCam97s>;
66pub type LmsBradford<T> = Lms<T, Bradford>;
68
69impl<T, Model> Lms<T, Model>
70where
71 T: FreeChannelScalar,
72 Model: LmsModel<T>,
73{
74 pub fn new(l: T, m: T, s: T) -> Self {
76 Lms {
77 l: FreeChannel::new(l),
78 m: FreeChannel::new(m),
79 s: FreeChannel::new(s),
80 model: PhantomData,
81 }
82 }
83
84 pub fn color_cast<TOut>(&self) -> Lms<TOut, Model>
86 where
87 T: ChannelFormatCast<TOut>,
88 TOut: FreeChannelScalar,
89 {
90 Lms {
91 l: self.l.clone().channel_cast(),
92 m: self.m.clone().channel_cast(),
93 s: self.s.clone().channel_cast(),
94 model: PhantomData,
95 }
96 }
97
98 pub fn l(&self) -> T {
100 self.l.0.clone()
101 }
102 pub fn m(&self) -> T {
104 self.m.0.clone()
105 }
106 pub fn s(&self) -> T {
108 self.s.0.clone()
109 }
110 pub fn l_mut(&mut self) -> &mut T {
112 &mut self.l.0
113 }
114 pub fn m_mut(&mut self) -> &mut T {
116 &mut self.m.0
117 }
118 pub fn s_mut(&mut self) -> &mut T {
120 &mut self.s.0
121 }
122 pub fn set_l(&mut self, val: T) {
124 self.l.0 = val;
125 }
126 pub fn set_m(&mut self, val: T) {
128 self.m.0 = val;
129 }
130 pub fn set_s(&mut self, val: T) {
132 self.s.0 = val;
133 }
134}
135
136impl<T, Model> Color for Lms<T, Model>
137where
138 T: FreeChannelScalar,
139 Model: LmsModel<T>,
140{
141 type Tag = LmsTag;
142 type ChannelsTuple = (T, T, T);
143
144 #[inline]
145 fn num_channels() -> u32 {
146 3
147 }
148 fn to_tuple(self) -> Self::ChannelsTuple {
149 (self.l.0, self.m.0, self.s.0)
150 }
151}
152
153impl<T, Model> FromTuple for Lms<T, Model>
154where
155 T: FreeChannelScalar,
156 Model: LmsModel<T>,
157{
158 fn from_tuple(values: (T, T, T)) -> Self {
159 Lms::new(values.0, values.1, values.2)
160 }
161}
162
163impl<T, Model> HomogeneousColor for Lms<T, Model>
164where
165 T: FreeChannelScalar,
166 Model: LmsModel<T>,
167{
168 type ChannelFormat = T;
169
170 impl_color_homogeneous_color_square!(Lms<T> {l, m, s}, phantom={model});
171}
172
173impl<T, Model> Bounded for Lms<T, Model>
174where
175 T: FreeChannelScalar,
176 Model: LmsModel<T>,
177{
178 impl_color_bounded!(Lms { l, m, s }, phantom = { model });
179}
180
181impl<T, Model> Broadcast for Lms<T, Model>
182where
183 T: FreeChannelScalar,
184 Model: LmsModel<T>,
185{
186 impl_color_broadcast!(Lms<T> {l, m, s}, chan=FreeChannel, phantom={model});
187}
188
189impl<T, Model> Lerp for Lms<T, Model>
190where
191 T: FreeChannelScalar,
192 Model: LmsModel<T>,
193 FreeChannel<T>: Lerp,
194{
195 type Position = <FreeChannel<T> as Lerp>::Position;
196 impl_color_lerp_square!(Lms { l, m, s }, phantom = { model });
197}
198
199impl<T, Model> Flatten for Lms<T, Model>
200where
201 T: FreeChannelScalar,
202 Model: LmsModel<T>,
203{
204 impl_color_as_slice!(T);
205 impl_color_from_slice_square!(Lms<T> {l:FreeChannel - 0, m:FreeChannel - 1,
206 s:FreeChannel - 2});
207}
208
209#[cfg(feature = "approx")]
210impl<T, Model> approx::AbsDiffEq for Lms<T, Model>
211where
212 T: FreeChannelScalar + approx::AbsDiffEq,
213 T::Epsilon: Clone,
214 Model: LmsModel<T>,
215{
216 impl_abs_diff_eq!({l, m, s});
217}
218#[cfg(feature = "approx")]
219impl<T, Model> approx::RelativeEq for Lms<T, Model>
220where
221 T: FreeChannelScalar + approx::RelativeEq,
222 T::Epsilon: Clone,
223 Model: LmsModel<T>,
224{
225 impl_rel_eq!({l, m, s});
226}
227#[cfg(feature = "approx")]
228impl<T, Model> approx::UlpsEq for Lms<T, Model>
229where
230 T: FreeChannelScalar + approx::UlpsEq,
231 T::Epsilon: Clone,
232 Model: LmsModel<T>,
233{
234 impl_ulps_eq!({l, m, s});
235}
236
237impl<T, Model> Default for Lms<T, Model>
238where
239 T: FreeChannelScalar,
240 Model: LmsModel<T>,
241{
242 impl_color_default!(
243 Lms {
244 l: FreeChannel,
245 m: FreeChannel,
246 s: FreeChannel
247 },
248 phantom = { model }
249 );
250}
251
252impl<T, Model> fmt::Display for Lms<T, Model>
253where
254 T: FreeChannelScalar + fmt::Display,
255 Model: LmsModel<T>,
256{
257 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258 write!(f, "LMS({}, {}, {})", self.l, self.m, self.s)
259 }
260}
261
262impl<T, Model> FromColor<Xyz<T>> for Lms<T, Model>
263where
264 T: FreeChannelScalar,
265 Model: LmsModel<T>,
266{
267 fn from_color(from: &Xyz<T>) -> Self {
268 let transform = Model::forward_transform();
269 let (l, m, s) = transform.transform_vector(from.clone().to_tuple());
270 Lms::new(l, m, s)
271 }
272}
273
274impl<T, Model> FromColor<Lms<T, Model>> for Xyz<T>
275where
276 T: FreeChannelScalar,
277 Model: LmsModel<T>,
278{
279 fn from_color(from: &Lms<T, Model>) -> Self {
280 let transform = Model::inverse_transform();
281 let (x, y, z) = transform.transform_vector(from.clone().to_tuple());
282 Xyz::new(x, y, z)
283 }
284}
285
286impl<T> LmsModel<T> for CieCam2002
287where
288 T: FreeChannelScalar,
289{
290 fn forward_transform() -> Matrix3<T> {
291 Matrix3::<T>::new([
292 num_traits::cast(0.7328).unwrap(),
293 num_traits::cast(0.4296).unwrap(),
294 num_traits::cast(-0.1624).unwrap(),
295 num_traits::cast(-0.7036).unwrap(),
296 num_traits::cast(1.6975).unwrap(),
297 num_traits::cast(0.0061).unwrap(),
298 num_traits::cast(0.0030).unwrap(),
299 num_traits::cast(0.0136).unwrap(),
300 num_traits::cast(0.9834).unwrap(),
301 ])
302 }
303
304 fn inverse_transform() -> Matrix3<T> {
305 Matrix3::<T>::new([
306 num_traits::cast(1.09612).unwrap(),
307 num_traits::cast(-0.27887).unwrap(),
308 num_traits::cast(0.18275).unwrap(),
309 num_traits::cast(0.45437).unwrap(),
310 num_traits::cast(0.47353).unwrap(),
311 num_traits::cast(0.07209).unwrap(),
312 num_traits::cast(-0.009628).unwrap(),
313 num_traits::cast(-0.005698).unwrap(),
314 num_traits::cast(1.015326).unwrap(),
315 ])
316 }
317}
318
319impl<T> LmsModel<T> for CieCam97s
320where
321 T: FreeChannelScalar,
322{
323 fn forward_transform() -> Matrix3<T> {
324 Matrix3::<T>::new([
325 num_traits::cast(0.8562).unwrap(),
326 num_traits::cast(0.3372).unwrap(),
327 num_traits::cast(-0.1934).unwrap(),
328 num_traits::cast(-0.8360).unwrap(),
329 num_traits::cast(1.8327).unwrap(),
330 num_traits::cast(0.0033).unwrap(),
331 num_traits::cast(0.0357).unwrap(),
332 num_traits::cast(-0.0469).unwrap(),
333 num_traits::cast(1.0112).unwrap(),
334 ])
335 }
336
337 fn inverse_transform() -> Matrix3<T> {
338 Matrix3::<T>::new([
339 num_traits::cast(0.98740).unwrap(),
340 num_traits::cast(-0.17683).unwrap(),
341 num_traits::cast(0.18943).unwrap(),
342 num_traits::cast(0.45044).unwrap(),
343 num_traits::cast(0.46493).unwrap(),
344 num_traits::cast(0.08463).unwrap(),
345 num_traits::cast(-0.01397).unwrap(),
346 num_traits::cast(0.027807).unwrap(),
347 num_traits::cast(0.98616).unwrap(),
348 ])
349 }
350}
351
352impl<T> LmsModel<T> for Bradford
353where
354 T: FreeChannelScalar,
355{
356 fn forward_transform() -> Matrix3<T> {
357 Matrix3::<T>::new([
358 num_traits::cast(0.8951).unwrap(),
359 num_traits::cast(0.2664).unwrap(),
360 num_traits::cast(-0.1614).unwrap(),
361 num_traits::cast(-0.7502).unwrap(),
362 num_traits::cast(1.7135).unwrap(),
363 num_traits::cast(0.0367).unwrap(),
364 num_traits::cast(0.0389).unwrap(),
365 num_traits::cast(-0.0685).unwrap(),
366 num_traits::cast(1.0296).unwrap(),
367 ])
368 }
369
370 fn inverse_transform() -> Matrix3<T> {
371 Matrix3::<T>::new([
372 num_traits::cast(0.98699).unwrap(),
373 num_traits::cast(-0.14705).unwrap(),
374 num_traits::cast(0.15996).unwrap(),
375 num_traits::cast(0.43231).unwrap(),
376 num_traits::cast(0.51836).unwrap(),
377 num_traits::cast(0.04929).unwrap(),
378 num_traits::cast(-0.00853).unwrap(),
379 num_traits::cast(0.040043).unwrap(),
380 num_traits::cast(0.96849).unwrap(),
381 ])
382 }
383}
384
385#[cfg(test)]
386mod test {
387 use super::*;
388 use crate::xyz::Xyz;
389 use approx::*;
390
391 #[test]
392 fn test_construct() {
393 let c1 = LmsCam2002::new(0.4, 0.6, 0.2);
394 assert_relative_eq!(c1.l(), 0.4);
395 assert_relative_eq!(c1.m(), 0.6);
396 assert_relative_eq!(c1.s(), 0.2);
397 assert_eq!(c1.to_tuple(), (0.4, 0.6, 0.2));
398 assert_relative_eq!(LmsCam2002::from_tuple(c1.to_tuple()), c1);
399 }
400
401 #[test]
402 fn test_lerp() {
403 let c1 = LmsCam97s::new(0.5, 0.9, 0.0);
404 let c2 = LmsCam97s::new(0.7, 0.0, 0.4);
405 assert_relative_eq!(c1.lerp(&c2, 0.0), c1);
406 assert_relative_eq!(c1.lerp(&c2, 1.0), c2);
407 assert_relative_eq!(c1.lerp(&c2, 0.5), LmsCam97s::new(0.6, 0.45, 0.2));
408 assert_relative_eq!(c1.lerp(&c2, 0.75), LmsCam97s::new(0.65, 0.225, 0.3));
409 }
410
411 #[test]
412 fn test_normalize() {
413 let c1 = LmsCam2002::new(-50.0, 50.0, 1e7);
414 assert!(c1.is_normalized());
415 assert_relative_eq!(c1.normalize(), c1);
416 }
417
418 #[test]
419 fn test_flatten() {
420 let c1 = LmsBradford::new(0.2, 0.5, 1.0);
421 assert_eq!(c1.as_slice(), &[0.2, 0.5, 1.0]);
422 assert_relative_eq!(LmsBradford::from_slice(c1.as_slice()), c1);
423 }
424
425 #[test]
426 fn test_from_xyz() {
427 let c1 = Xyz::new(0.5, 0.2, 0.0);
428 let t1 = Lms::<_, CieCam2002>::from_color(&c1);
429 assert_relative_eq!(t1, Lms::new(0.45232, -0.01230, 0.00422), epsilon = 1e-4);
430 assert_relative_eq!(Xyz::from_color(&t1), c1, epsilon = 1e-4);
431
432 let c2 = Xyz::new(0.3, 0.3, 0.3);
433 let t2 = Lms::<_, CieCam2002>::from_color(&c2);
434 assert_relative_eq!(t2, Lms::new(0.3, 0.3, 0.3), epsilon = 1e-4);
435 assert_relative_eq!(Xyz::from_color(&t2), c2, epsilon = 1e-4);
436
437 let c3 = Xyz::new(0.6, 0.4, 0.5);
438 let t3 = Lms::<_, CieCam97s>::from_color(&c3);
439 assert_relative_eq!(t3, Lms::new(0.5519, 0.23313, 0.50826), epsilon = 1e-4);
440 assert_relative_eq!(Xyz::from_color(&t3), c3, epsilon = 1e-4);
441
442 let c4 = Xyz::new(0.2, 0.3, 0.6);
443 let t4 = Lms::<_, Bradford>::from_color(&c4);
444 assert_relative_eq!(t4, Lms::new(0.1621, 0.38603, 0.6050), epsilon = 1e-4);
445 assert_relative_eq!(Xyz::from_color(&t4), c4, epsilon = 1e-4);
446 }
447
448 #[test]
449 fn test_to_xyz() {
450 let c1 = LmsCam2002::new(0.25, 0.50, 0.75);
451 let t1 = Xyz::from_color(&c1);
452 assert_relative_eq!(t1, Xyz::new(0.2716575, 0.404425, 0.75624), epsilon = 1e-4);
453 assert_relative_eq!(LmsCam2002::from_color(&t1), c1, epsilon = 1e-4);
454 }
455
456 #[test]
457 fn test_color_cast() {
458 let c1 = LmsCam2002::new(0.25, 0.50, 0.75);
459 assert_relative_eq!(c1.color_cast(), c1);
460 assert_relative_eq!(c1.color_cast(), LmsCam2002::new(0.25f32, 0.50f32, 0.75f32));
461 assert_relative_eq!(c1.color_cast::<f32>().color_cast(), c1);
462 }
463}