1use core::marker::PhantomData;
2
3use crate::{
4 bool_mask::HasBoolMask,
5 convert::{ConvertOnce, FromColorUnclamped, Matrix3},
6 num::{Arithmetics, Zero},
7 stimulus::{FromStimulus, Stimulus, StimulusColor},
8 xyz::meta::HasXyzMeta,
9 Alpha, Xyz,
10};
11
12use super::matrix::{HasLmsMatrix, XyzToLms};
13
14pub type Lmsa<M, T> = Alpha<Lms<M, T>, T>;
17
18#[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)]
63#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
64#[palette(palette_internal, component = "T", skip_derives(Lms, Xyz))]
65#[repr(C)]
66pub struct Lms<M, T> {
67 pub long: T,
70
71 pub medium: T,
74
75 pub short: T,
78
79 #[cfg_attr(feature = "serializing", serde(skip))]
82 #[palette(unsafe_zero_sized)]
83 pub meta: PhantomData<M>,
84}
85
86impl<M, T> Lms<M, T> {
87 pub const fn new(long: T, medium: T, short: T) -> Self {
89 Self {
90 long,
91 medium,
92 short,
93 meta: PhantomData,
94 }
95 }
96
97 pub fn into_format<U>(self) -> Lms<M, U>
108 where
109 U: FromStimulus<T>,
110 {
111 Lms {
112 long: U::from_stimulus(self.long),
113 medium: U::from_stimulus(self.medium),
114 short: U::from_stimulus(self.short),
115 meta: PhantomData,
116 }
117 }
118
119 pub fn from_format<U>(color: Lms<M, U>) -> Self
130 where
131 T: FromStimulus<U>,
132 {
133 color.into_format()
134 }
135
136 pub fn into_components(self) -> (T, T, T) {
138 (self.long, self.medium, self.short)
139 }
140
141 pub fn from_components((long, medium, short): (T, T, T)) -> Self {
143 Self::new(long, medium, short)
144 }
145
146 pub fn with_meta<NewM>(self) -> Lms<NewM, T> {
153 Lms {
154 long: self.long,
155 medium: self.medium,
156 short: self.short,
157 meta: PhantomData,
158 }
159 }
160}
161
162impl<M, T> Lms<M, T>
163where
164 T: Zero,
165{
166 pub fn min_short() -> T {
168 T::zero()
169 }
170
171 pub fn min_medium() -> T {
173 T::zero()
174 }
175
176 pub fn min_long() -> T {
178 T::zero()
179 }
180}
181
182impl<M, T> Lms<M, T> {
183 #[inline]
185 pub fn matrix_from_xyz() -> Matrix3<Xyz<M::XyzMeta, T>, Self>
186 where
187 M: HasXyzMeta + HasLmsMatrix,
188 M::LmsMatrix: XyzToLms<T>,
189 {
190 Matrix3::from_array(M::LmsMatrix::xyz_to_lms_matrix())
191 }
192}
193
194impl<S, T, A> Alpha<Lms<S, T>, A> {
196 pub const fn new(red: T, green: T, blue: T, alpha: A) -> Self {
198 Alpha {
199 color: Lms::new(red, green, blue),
200 alpha,
201 }
202 }
203
204 pub fn into_format<U, B>(self) -> Alpha<Lms<S, U>, B>
215 where
216 U: FromStimulus<T>,
217 B: FromStimulus<A>,
218 {
219 Alpha {
220 color: self.color.into_format(),
221 alpha: B::from_stimulus(self.alpha),
222 }
223 }
224
225 pub fn from_format<U, B>(color: Alpha<Lms<S, U>, B>) -> Self
236 where
237 T: FromStimulus<U>,
238 A: FromStimulus<B>,
239 {
240 color.into_format()
241 }
242
243 pub fn into_components(self) -> (T, T, T, A) {
245 (
246 self.color.long,
247 self.color.medium,
248 self.color.short,
249 self.alpha,
250 )
251 }
252
253 pub fn from_components((long, medium, short, alpha): (T, T, T, A)) -> Self {
255 Self::new(long, medium, short, alpha)
256 }
257
258 pub fn with_meta<NewM>(self) -> Alpha<Lms<NewM, T>, A> {
265 Alpha {
266 color: self.color.with_meta(),
267 alpha: self.alpha,
268 }
269 }
270}
271
272impl<M, T> FromColorUnclamped<Lms<M, T>> for Lms<M, T> {
273 #[inline]
274 fn from_color_unclamped(val: Lms<M, T>) -> Self {
275 val
276 }
277}
278
279impl<M, T> FromColorUnclamped<Xyz<M::XyzMeta, T>> for Lms<M, T>
280where
281 M: HasLmsMatrix + HasXyzMeta,
282 M::LmsMatrix: XyzToLms<T>,
283 T: Arithmetics,
284{
285 #[inline]
286 fn from_color_unclamped(val: Xyz<M::XyzMeta, T>) -> Self {
287 Self::matrix_from_xyz().convert_once(val)
288 }
289}
290
291impl<M, T> StimulusColor for Lms<M, T> where T: Stimulus {}
292
293impl<M, T> HasBoolMask for Lms<M, T>
294where
295 T: HasBoolMask,
296{
297 type Mask = T::Mask;
298}
299
300impl<M, T> Default for Lms<M, T>
301where
302 T: Default,
303{
304 fn default() -> Lms<M, T> {
305 Lms::new(T::default(), T::default(), T::default())
306 }
307}
308
309impl<M> From<Lms<M, f32>> for Lms<M, f64> {
310 #[inline]
311 fn from(color: Lms<M, f32>) -> Self {
312 color.into_format()
313 }
314}
315
316impl<M> From<Lmsa<M, f32>> for Lmsa<M, f64> {
317 #[inline]
318 fn from(color: Lmsa<M, f32>) -> Self {
319 color.into_format()
320 }
321}
322
323impl<M> From<Lms<M, f64>> for Lms<M, f32> {
324 #[inline]
325 fn from(color: Lms<M, f64>) -> Self {
326 color.into_format()
327 }
328}
329
330impl<M> From<Lmsa<M, f64>> for Lmsa<M, f32> {
331 #[inline]
332 fn from(color: Lmsa<M, f64>) -> Self {
333 color.into_format()
334 }
335}
336
337#[cfg(feature = "bytemuck")]
338unsafe impl<M, T> bytemuck::Zeroable for Lms<M, T> where T: bytemuck::Zeroable {}
339
340#[cfg(feature = "bytemuck")]
341unsafe impl<M: 'static, T> bytemuck::Pod for Lms<M, T> where T: bytemuck::Pod {}
342
343impl_reference_component_methods!(Lms<M>, [long, medium, short], meta);
344impl_struct_of_arrays_methods!(Lms<M>, [long, medium, short], meta);
345
346impl_is_within_bounds! {
347 Lms<M> {
348 long => [Self::min_long(), None],
349 medium => [Self::min_medium(), None],
350 short => [Self::min_short(), None]
351 }
352 where T: Stimulus
353}
354impl_clamp! {
355 Lms<M> {
356 long => [Self::min_long()],
357 medium => [Self::min_medium()],
358 short => [Self::min_short()]
359 }
360 other {meta}
361 where T: Stimulus
362}
363
364impl_mix!(Lms<M>);
365impl_premultiply!(Lms<M> {long, medium, short} phantom: meta);
366impl_euclidean_distance!(Lms<M> {long, medium, short});
367
368impl_color_add!(Lms<M>, [long, medium, short], meta);
369impl_color_sub!(Lms<M>, [long, medium, short], meta);
370impl_color_mul!(Lms<M>, [long, medium, short], meta);
371impl_color_div!(Lms<M>, [long, medium, short], meta);
372
373impl_tuple_conversion!(Lms<M> as (T, T, T));
374impl_array_casts!(Lms<M, T>, [T; 3]);
375impl_simd_array_conversion!(Lms<M>, [long, medium, short], meta);
376impl_struct_of_array_traits!(Lms<M>, [long, medium, short], meta);
377
378impl_eq!(Lms<M>, [long, medium, short]);
379impl_copy_clone!(Lms<M>, [long, medium, short], meta);
380
381impl_rand_traits_cartesian!(UniformLms, Lms<M> {long, medium, short} phantom: meta: PhantomData<M>);
382
383#[cfg(test)]
384mod test {
385 use crate::{lms::VonKriesLms, white_point::D65};
386
387 #[cfg(feature = "alloc")]
388 use super::Lmsa;
389
390 #[cfg(feature = "random")]
391 use super::Lms;
392
393 #[cfg(feature = "approx")]
394 use crate::{convert::FromColorUnclamped, lms::BradfordLms, Xyz};
395
396 test_convert_into_from_xyz!(VonKriesLms<D65, f32>);
397 raw_pixel_conversion_tests!(VonKriesLms<D65>: long, medium, short);
398 raw_pixel_conversion_fail_tests!(VonKriesLms<D65>: long, medium, short);
399
400 #[cfg(feature = "approx")]
401 #[test]
402 fn von_kries_xyz_roundtrip() {
403 let xyz = Xyz::new(0.2f32, 0.4, 0.8);
404 let lms = VonKriesLms::<D65, _>::from_color_unclamped(xyz);
405 assert_relative_eq!(Xyz::from_color_unclamped(lms), xyz);
406 }
407
408 #[cfg(feature = "approx")]
409 #[test]
410 fn bradford_xyz_roundtrip() {
411 let xyz = Xyz::new(0.2f32, 0.4, 0.8);
412 let lms = BradfordLms::<D65, _>::from_color_unclamped(xyz);
413 assert_relative_eq!(Xyz::from_color_unclamped(lms), xyz);
414 }
415
416 #[cfg(feature = "serializing")]
417 #[test]
418 fn serialize() {
419 let serialized =
420 ::serde_json::to_string(&VonKriesLms::<D65, f32>::new(0.3, 0.8, 0.1)).unwrap();
421
422 assert_eq!(serialized, r#"{"long":0.3,"medium":0.8,"short":0.1}"#);
423 }
424
425 #[cfg(feature = "serializing")]
426 #[test]
427 fn deserialize() {
428 let deserialized: VonKriesLms<D65, f32> =
429 ::serde_json::from_str(r#"{"long":0.3,"medium":0.8,"short":0.1}"#).unwrap();
430
431 assert_eq!(deserialized, VonKriesLms::<D65, f32>::new(0.3, 0.8, 0.1));
432 }
433
434 struct_of_arrays_tests!(
435 VonKriesLms<D65>[long, medium, short] phantom: meta,
436 Lmsa::new(0.1f32, 0.2, 0.3, 0.4),
437 Lmsa::new(0.2, 0.3, 0.4, 0.5),
438 Lmsa::new(0.3, 0.4, 0.5, 0.6)
439 );
440
441 test_uniform_distribution! {
442 VonKriesLms<D65, f32> {
443 long: (0.0, 1.0),
444 medium: (0.0, 1.0),
445 short: (0.0, 1.0)
446 },
447 min: Lms::new(0.0f32, 0.0, 0.0),
448 max: Lms::new(1.0, 1.0, 1.0)
449 }
450}