palette/luma/
luma.rs

1use core::{
2    any::TypeId,
3    convert::TryInto,
4    fmt,
5    marker::PhantomData,
6    ops::{Add, Div},
7};
8
9use crate::{
10    bool_mask::{HasBoolMask, LazySelect},
11    cast::{ComponentOrder, Packed, UintCast},
12    color_difference::Wcag21RelativeContrast,
13    convert::FromColorUnclamped,
14    encoding::{FromLinear, IntoLinear, Linear, Srgb},
15    luma::LumaStandard,
16    num::{Arithmetics, MinMax, PartialCmp, Real},
17    stimulus::{FromStimulus, Stimulus, StimulusColor},
18    white_point::D65,
19    Alpha, IntoColor, Xyz, Yxy,
20};
21
22/// Luminance with an alpha component. See the [`Lumaa` implementation
23/// in `Alpha`](crate::Alpha#Lumaa).
24pub type Lumaa<S = Srgb, T = f32> = Alpha<Luma<S, T>, T>;
25
26/// Luminance.
27///
28/// Luma is a purely gray scale color space, which is included more for
29/// completeness than anything else, and represents how bright a color is
30/// perceived to be. It's basically the `Y` component of [CIE
31/// XYZ](crate::Xyz). The lack of any form of hue representation limits
32/// the set of operations that can be performed on it.
33#[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)]
34#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
35#[palette(
36    palette_internal,
37    luma_standard = "S",
38    component = "T",
39    skip_derives(Xyz, Yxy, Luma)
40)]
41#[repr(C)]
42#[doc(alias = "gray")]
43#[doc(alias = "grey")]
44pub struct Luma<S = Srgb, T = f32> {
45    /// The lightness of the color. 0.0 is black and 1.0 is white.
46    pub luma: T,
47
48    /// The kind of RGB standard. sRGB is the default.
49    #[cfg_attr(feature = "serializing", serde(skip))]
50    #[palette(unsafe_zero_sized)]
51    pub standard: PhantomData<S>,
52}
53
54impl<S, T> Luma<S, T> {
55    /// Create a luminance color.
56    pub const fn new(luma: T) -> Luma<S, T> {
57        Luma {
58            luma,
59            standard: PhantomData,
60        }
61    }
62
63    /// Convert into another component type.
64    pub fn into_format<U>(self) -> Luma<S, U>
65    where
66        U: FromStimulus<T>,
67    {
68        Luma {
69            luma: U::from_stimulus(self.luma),
70            standard: PhantomData,
71        }
72    }
73
74    /// Convert from another component type.
75    pub fn from_format<U>(color: Luma<S, U>) -> Self
76    where
77        T: FromStimulus<U>,
78    {
79        color.into_format()
80    }
81
82    /// Convert to a `(luma,)` tuple.
83    pub fn into_components(self) -> (T,) {
84        (self.luma,)
85    }
86
87    /// Convert from a `(luma,)` tuple.
88    pub fn from_components((luma,): (T,)) -> Self {
89        Self::new(luma)
90    }
91
92    fn reinterpret_as<S2>(self) -> Luma<S2, T>
93    where
94        S: LumaStandard,
95        S2: LumaStandard<WhitePoint = S::WhitePoint>,
96    {
97        Luma {
98            luma: self.luma,
99            standard: PhantomData,
100        }
101    }
102}
103
104impl<S, T> Luma<S, T>
105where
106    T: Stimulus,
107{
108    /// Return the `luma` value minimum.
109    pub fn min_luma() -> T {
110        T::zero()
111    }
112
113    /// Return the `luma` value maximum.
114    pub fn max_luma() -> T {
115        T::max_intensity()
116    }
117}
118
119impl<S> Luma<S, u8> {
120    /// Convert to a packed `u16` with with specifiable component order.
121    ///
122    /// ```
123    /// use palette::{luma, SrgbLuma};
124    ///
125    /// let integer = SrgbLuma::new(96u8).into_u16::<luma::channels::La>();
126    /// assert_eq!(0x60FF, integer);
127    /// ```
128    ///
129    /// It's also possible to use `From` and `Into`, which defaults to the
130    /// `0xAALL` component order:
131    ///
132    /// ```
133    /// use palette::SrgbLuma;
134    ///
135    /// let integer = u16::from(SrgbLuma::new(96u8));
136    /// assert_eq!(0xFF60, integer);
137    /// ```
138    ///
139    /// See [Packed](crate::cast::Packed) for more details.
140    #[inline]
141    pub fn into_u16<O>(self) -> u16
142    where
143        O: ComponentOrder<Lumaa<S, u8>, u16>,
144    {
145        O::pack(Lumaa::from(self))
146    }
147
148    /// Convert from a packed `u16` with specifiable component order.
149    ///
150    /// ```
151    /// use palette::{luma, SrgbLuma};
152    ///
153    /// let luma = SrgbLuma::from_u16::<luma::channels::La>(0x60FF);
154    /// assert_eq!(SrgbLuma::new(96u8), luma);
155    /// ```
156    ///
157    /// It's also possible to use `From` and `Into`, which defaults to the
158    /// `0xAALL` component order:
159    ///
160    /// ```
161    /// use palette::SrgbLuma;
162    ///
163    /// let luma = SrgbLuma::from(0x60u16);
164    /// assert_eq!(SrgbLuma::new(96u8), luma);
165    /// ```
166    ///
167    /// See [Packed](crate::cast::Packed) for more details.
168    #[inline]
169    pub fn from_u16<O>(color: u16) -> Self
170    where
171        O: ComponentOrder<Lumaa<S, u8>, u16>,
172    {
173        O::unpack(color).color
174    }
175}
176
177impl<S, T> Luma<S, T>
178where
179    S: LumaStandard,
180{
181    /// Convert the color to linear luminance.
182    ///
183    /// Some transfer functions allow the component type to be converted at the
184    /// same time. This is usually offered with increased performance, compared
185    /// to using [`into_format`][Luma::into_format].
186    ///
187    /// ```
188    /// use palette::{SrgbLuma, LinLuma};
189    ///
190    /// let linear: LinLuma<_, f32> = SrgbLuma::new(96u8).into_linear();
191    /// ```
192    ///
193    /// See the transfer function types in the [`encoding`](crate::encoding)
194    /// module for details and performance characteristics.
195    pub fn into_linear<U>(self) -> Luma<Linear<S::WhitePoint>, U>
196    where
197        S::TransferFn: IntoLinear<U, T>,
198    {
199        Luma::new(S::TransferFn::into_linear(self.luma))
200    }
201
202    /// Convert linear luminance to non-linear luminance.
203    ///
204    /// Some transfer functions allow the component type to be converted at the
205    /// same time. This is usually offered with increased performance, compared
206    /// to using [`into_format`][Luma::into_format].
207    ///
208    /// ```
209    /// use palette::{SrgbLuma, LinLuma};
210    ///
211    /// let encoded = SrgbLuma::<u8>::from_linear(LinLuma::new(0.95f32));
212    /// ```
213    ///
214    /// See the transfer function types in the [`encoding`](crate::encoding)
215    /// module for details and performance characteristics.
216    pub fn from_linear<U>(color: Luma<Linear<S::WhitePoint>, U>) -> Luma<S, T>
217    where
218        S::TransferFn: FromLinear<U, T>,
219    {
220        Luma::new(S::TransferFn::from_linear(color.luma))
221    }
222}
223
224impl<Wp, T> Luma<Linear<Wp>, T> {
225    /// Convert a linear color to a different encoding.
226    ///
227    /// Some transfer functions allow the component type to be converted at the
228    /// same time. This is usually offered with increased performance, compared
229    /// to using [`into_format`][Luma::into_format].
230    ///
231    /// ```
232    /// use palette::{SrgbLuma, LinLuma};
233    ///
234    /// let encoded: SrgbLuma<u8> = LinLuma::new(0.95f32).into_encoding();
235    /// ```
236    ///
237    /// See the transfer function types in the [`encoding`](crate::encoding)
238    /// module for details and performance characteristics.
239    pub fn into_encoding<U, St>(self) -> Luma<St, U>
240    where
241        St: LumaStandard<WhitePoint = Wp>,
242        St::TransferFn: FromLinear<T, U>,
243    {
244        Luma::<St, U>::from_linear(self)
245    }
246
247    /// Convert from linear luminance from a different encoding.
248    ///
249    /// Some transfer functions allow the component type to be converted at the
250    /// same time. This is usually offered with increased performance, compared
251    /// to using [`into_format`][Luma::into_format].
252    ///
253    /// ```
254    /// use palette::{SrgbLuma, LinLuma};
255    ///
256    /// let linear = LinLuma::<_, f32>::from_encoding(SrgbLuma::new(96u8));
257    /// ```
258    ///
259    /// See the transfer function types in the [`encoding`](crate::encoding)
260    /// module for details and performance characteristics.
261    pub fn from_encoding<U, St>(color: Luma<St, U>) -> Self
262    where
263        St: LumaStandard<WhitePoint = Wp>,
264        St::TransferFn: IntoLinear<T, U>,
265    {
266        color.into_linear()
267    }
268}
269
270// Safety:
271//
272// Luma is a transparent wrapper around its component, which fulfills the
273// requirements of UintCast.
274unsafe impl<S> UintCast for Luma<S, u8> {
275    type Uint = u8;
276}
277
278// Safety:
279//
280// Luma is a transparent wrapper around its component, which fulfills the
281// requirements of UintCast.
282unsafe impl<S> UintCast for Luma<S, u16> {
283    type Uint = u16;
284}
285
286// Safety:
287//
288// Luma is a transparent wrapper around its component, which fulfills the
289// requirements of UintCast.
290unsafe impl<S> UintCast for Luma<S, u32> {
291    type Uint = u32;
292}
293
294// Safety:
295//
296// Luma is a transparent wrapper around its component, which fulfills the
297// requirements of UintCast.
298unsafe impl<S> UintCast for Luma<S, u64> {
299    type Uint = u64;
300}
301
302// Safety:
303//
304// Luma is a transparent wrapper around its component, which fulfills the
305// requirements of UintCast.
306unsafe impl<S> UintCast for Luma<S, u128> {
307    type Uint = u128;
308}
309
310///<span id="Lumaa"></span>[`Lumaa`](crate::luma::Lumaa) implementations.
311impl<S, T, A> Alpha<Luma<S, T>, A> {
312    /// Create a luminance color with transparency.
313    pub const fn new(luma: T, alpha: A) -> Self {
314        Alpha {
315            color: Luma::new(luma),
316            alpha,
317        }
318    }
319
320    /// Convert into another component type.
321    pub fn into_format<U, B>(self) -> Alpha<Luma<S, U>, B>
322    where
323        U: FromStimulus<T>,
324        B: FromStimulus<A>,
325    {
326        Alpha {
327            color: self.color.into_format(),
328            alpha: B::from_stimulus(self.alpha),
329        }
330    }
331
332    /// Convert from another component type.
333    pub fn from_format<U, B>(color: Alpha<Luma<S, U>, B>) -> Self
334    where
335        T: FromStimulus<U>,
336        A: FromStimulus<B>,
337    {
338        color.into_format()
339    }
340
341    /// Convert to a `(luma, alpha)` tuple.
342    pub fn into_components(self) -> (T, A) {
343        (self.color.luma, self.alpha)
344    }
345
346    /// Convert from a `(luma, alpha)` tuple.
347    pub fn from_components((luma, alpha): (T, A)) -> Self {
348        Self::new(luma, alpha)
349    }
350}
351
352impl<S> Lumaa<S, u8> {
353    /// Convert to a packed `u16` with with a specific component order.
354    ///
355    /// ```
356    /// use palette::{luma, SrgbLumaa};
357    ///
358    /// let integer = SrgbLumaa::new(96u8, 255).into_u16::<luma::channels::Al>();
359    /// assert_eq!(0xFF60, integer);
360    /// ```
361    ///
362    /// It's also possible to use `From` and `Into`, which defaults to the
363    /// `0xLLAA` component order:
364    ///
365    /// ```
366    /// use palette::SrgbLumaa;
367    ///
368    /// let integer = u16::from(SrgbLumaa::new(96u8, 255));
369    /// assert_eq!(0x60FF, integer);
370    /// ```
371    ///
372    /// See [Packed](crate::cast::Packed) for more details.
373    #[inline]
374    pub fn into_u16<O>(self) -> u16
375    where
376        O: ComponentOrder<Lumaa<S, u8>, u16>,
377    {
378        O::pack(self)
379    }
380
381    /// Convert from a packed `u16` with a specific component order.
382    ///
383    /// ```
384    /// use palette::{luma, SrgbLumaa};
385    ///
386    /// let luma = SrgbLumaa::from_u16::<luma::channels::Al>(0xFF60);
387    /// assert_eq!(SrgbLumaa::new(96u8, 255), luma);
388    /// ```
389    ///
390    /// It's also possible to use `From` and `Into`, which defaults to the
391    /// `0xLLAA` component order:
392    ///
393    /// ```
394    /// use palette::SrgbLumaa;
395    ///
396    /// let luma = SrgbLumaa::from(0x60FF);
397    /// assert_eq!(SrgbLumaa::new(96u8, 255), luma);
398    /// ```
399    ///
400    /// See [Packed](crate::cast::Packed) for more details.
401    #[inline]
402    pub fn from_u16<O>(color: u16) -> Self
403    where
404        O: ComponentOrder<Lumaa<S, u8>, u16>,
405    {
406        O::unpack(color)
407    }
408}
409
410impl<S, T, A> Alpha<Luma<S, T>, A>
411where
412    S: LumaStandard,
413{
414    /// Convert the color to linear luminance with transparency.
415    ///
416    /// Some transfer functions allow the component type to be converted at the
417    /// same time. This is usually offered with increased performance, compared
418    /// to using [`into_format`][Luma::into_format].
419    ///
420    /// ```
421    /// use palette::{SrgbLumaa, LinLumaa};
422    ///
423    /// let linear: LinLumaa<_, f32> = SrgbLumaa::new(96u8, 38).into_linear();
424    /// ```
425    ///
426    /// See the transfer function types in the [`encoding`](crate::encoding)
427    /// module for details and performance characteristics.
428    pub fn into_linear<U, B>(self) -> Alpha<Luma<Linear<S::WhitePoint>, U>, B>
429    where
430        S::TransferFn: IntoLinear<U, T>,
431        B: FromStimulus<A>,
432    {
433        Alpha {
434            color: self.color.into_linear(),
435            alpha: B::from_stimulus(self.alpha),
436        }
437    }
438
439    /// Convert linear luminance to non-linear luminance with transparency.
440    ///
441    /// Some transfer functions allow the component type to be converted at the
442    /// same time. This is usually offered with increased performance, compared
443    /// to using [`into_format`][Luma::into_format].
444    ///
445    /// ```
446    /// use palette::{SrgbLumaa, LinLumaa};
447    ///
448    /// let encoded = SrgbLumaa::<u8>::from_linear(LinLumaa::new(0.95f32, 0.75));
449    /// ```
450    ///
451    /// See the transfer function types in the [`encoding`](crate::encoding)
452    /// module for details and performance characteristics.
453    pub fn from_linear<U, B>(color: Alpha<Luma<Linear<S::WhitePoint>, U>, B>) -> Self
454    where
455        S::TransferFn: FromLinear<U, T>,
456        A: FromStimulus<B>,
457    {
458        Alpha {
459            color: Luma::from_linear(color.color),
460            alpha: A::from_stimulus(color.alpha),
461        }
462    }
463}
464
465impl<Wp, T, A> Alpha<Luma<Linear<Wp>, T>, A> {
466    /// Convert a linear color to a different encoding with transparency.
467    ///
468    /// Some transfer functions allow the component type to be converted at the
469    /// same time. This is usually offered with increased performance, compared
470    /// to using [`into_format`][Luma::into_format].
471    ///
472    /// ```
473    /// use palette::{SrgbLumaa, LinLumaa};
474    ///
475    /// let encoded: SrgbLumaa<u8> = LinLumaa::new(0.95f32, 0.75).into_encoding();
476    /// ```
477    ///
478    /// See the transfer function types in the [`encoding`](crate::encoding)
479    /// module for details and performance characteristics.
480    pub fn into_encoding<U, B, St>(self) -> Alpha<Luma<St, U>, B>
481    where
482        St: LumaStandard<WhitePoint = Wp>,
483        St::TransferFn: FromLinear<T, U>,
484        B: FromStimulus<A>,
485    {
486        Alpha::<Luma<St, U>, B>::from_linear(self)
487    }
488
489    /// Convert to linear luminance from a different encoding with transparency.
490    ///
491    /// Some transfer functions allow the component type to be converted at the
492    /// same time. This is usually offered with increased performance, compared
493    /// to using [`into_format`][Luma::into_format].
494    ///
495    /// ```
496    /// use palette::{SrgbLumaa, LinLumaa};
497    ///
498    /// let linear = LinLumaa::<_, f32>::from_encoding(SrgbLumaa::new(96u8, 38));
499    /// ```
500    ///
501    /// See the transfer function types in the [`encoding`](crate::encoding)
502    /// module for details and performance characteristics.
503    pub fn from_encoding<U, B, St>(color: Alpha<Luma<St, U>, B>) -> Self
504    where
505        St: LumaStandard<WhitePoint = Wp>,
506        St::TransferFn: IntoLinear<T, U>,
507        A: FromStimulus<B>,
508    {
509        color.into_linear()
510    }
511}
512
513impl_reference_component_methods!(Luma<S>, [luma], standard);
514impl_struct_of_arrays_methods!(Luma<S>, [luma], standard);
515
516impl<S1, S2, T> FromColorUnclamped<Luma<S2, T>> for Luma<S1, T>
517where
518    S1: LumaStandard + 'static,
519    S2: LumaStandard<WhitePoint = S1::WhitePoint> + 'static,
520    S1::TransferFn: FromLinear<T, T>,
521    S2::TransferFn: IntoLinear<T, T>,
522{
523    fn from_color_unclamped(color: Luma<S2, T>) -> Self {
524        if TypeId::of::<S1>() == TypeId::of::<S2>() {
525            color.reinterpret_as()
526        } else {
527            Self::from_linear(color.into_linear().reinterpret_as())
528        }
529    }
530}
531
532impl<S, T> FromColorUnclamped<Xyz<S::WhitePoint, T>> for Luma<S, T>
533where
534    S: LumaStandard,
535    S::TransferFn: FromLinear<T, T>,
536{
537    fn from_color_unclamped(color: Xyz<S::WhitePoint, T>) -> Self {
538        Self::from_linear(Luma {
539            luma: color.y,
540            standard: PhantomData,
541        })
542    }
543}
544
545impl<S, T> FromColorUnclamped<Yxy<S::WhitePoint, T>> for Luma<S, T>
546where
547    S: LumaStandard,
548    S::TransferFn: FromLinear<T, T>,
549{
550    fn from_color_unclamped(color: Yxy<S::WhitePoint, T>) -> Self {
551        Self::from_linear(Luma {
552            luma: color.luma,
553            standard: PhantomData,
554        })
555    }
556}
557
558impl_tuple_conversion!(Luma<S> as (T));
559
560impl_is_within_bounds! {
561    Luma<S> {
562        luma => [Self::min_luma(), Self::max_luma()]
563    }
564    where T: Stimulus
565}
566impl_clamp! {
567    Luma<S> {
568        luma => [Self::min_luma(), Self::max_luma()]
569    }
570    other {standard}
571    where T: Stimulus
572}
573
574impl_mix!(Luma<S>);
575impl_lighten!(Luma<S> increase {luma => [Self::min_luma(), Self::max_luma()]} other {} phantom: standard where T: Stimulus);
576impl_premultiply!(Luma<S> {luma} phantom: standard);
577impl_euclidean_distance!(Luma<S> {luma});
578
579impl<S, T> StimulusColor for Luma<S, T> where T: Stimulus {}
580
581impl<S, T> HasBoolMask for Luma<S, T>
582where
583    T: HasBoolMask,
584{
585    type Mask = T::Mask;
586}
587
588impl<S, T> Default for Luma<S, T>
589where
590    T: Stimulus,
591{
592    fn default() -> Luma<S, T> {
593        Luma::new(Self::min_luma())
594    }
595}
596
597impl_color_add!(Luma<S>, [luma], standard);
598impl_color_sub!(Luma<S>, [luma], standard);
599impl_color_mul!(Luma<S>, [luma], standard);
600impl_color_div!(Luma<S>, [luma], standard);
601
602impl_array_casts!(Luma<S, T>, [T; 1]);
603
604impl<S, T> AsRef<T> for Luma<S, T> {
605    #[inline]
606    fn as_ref(&self) -> &T {
607        &self.luma
608    }
609}
610
611impl<S, T> AsMut<T> for Luma<S, T> {
612    #[inline]
613    fn as_mut(&mut self) -> &mut T {
614        &mut self.luma
615    }
616}
617
618impl<S, T> From<T> for Luma<S, T> {
619    #[inline]
620    fn from(luma: T) -> Self {
621        Self::new(luma)
622    }
623}
624
625macro_rules! impl_luma_cast_other {
626    ($($other: ty),+) => {
627        $(
628            impl<'a, S> From<&'a $other> for &'a Luma<S, $other>
629            where
630                $other: AsRef<Luma<S, $other>>,
631            {
632                #[inline]
633                fn from(luma: &'a $other) -> Self {
634                    luma.as_ref()
635                }
636            }
637
638            impl<'a, S> From<&'a mut $other> for &'a mut Luma<S, $other>
639            where
640                $other: AsMut<Luma<S, $other>>,
641            {
642                #[inline]
643                fn from(luma: &'a mut $other) -> Self {
644                    luma.as_mut()
645                }
646            }
647
648            impl<S> AsRef<Luma<S, $other>> for $other {
649                #[inline]
650                fn as_ref(&self) -> &Luma<S, $other> {
651                    core::slice::from_ref(self).try_into().unwrap()
652                }
653            }
654
655            impl<S> AsMut<Luma<S, $other>> for $other {
656                #[inline]
657                fn as_mut(&mut self) -> &mut Luma<S, $other> {
658                    core::slice::from_mut(self).try_into().unwrap()
659                }
660            }
661
662            impl<S> From<Luma<S, $other>> for $other {
663                #[inline]
664                fn from(color: Luma<S, $other>) -> Self {
665                    color.luma
666                }
667            }
668
669            impl<'a, S> From<&'a Luma<S, $other>> for &'a $other {
670                #[inline]
671                fn from(color: &'a Luma<S, $other>) -> Self {
672                    color.as_ref()
673                }
674            }
675
676            impl<'a, S> From<&'a mut Luma<S, $other>> for &'a mut $other {
677                #[inline]
678                fn from(color: &'a mut Luma<S, $other>) -> Self {
679                    color.as_mut()
680                }
681            }
682        )+
683    };
684}
685impl_luma_cast_other!(u8, u16, u32, u64, u128, f32, f64);
686
687impl<S, T, P, O> From<Luma<S, T>> for Packed<O, P>
688where
689    O: ComponentOrder<Lumaa<S, T>, P>,
690    Lumaa<S, T>: From<Luma<S, T>>,
691{
692    #[inline]
693    fn from(color: Luma<S, T>) -> Self {
694        Self::from(Lumaa::from(color))
695    }
696}
697
698impl<S, T, O, P> From<Lumaa<S, T>> for Packed<O, P>
699where
700    O: ComponentOrder<Lumaa<S, T>, P>,
701{
702    #[inline]
703    fn from(color: Lumaa<S, T>) -> Self {
704        Packed::pack(color)
705    }
706}
707
708impl<S, O, P> From<Packed<O, P>> for Luma<S, u8>
709where
710    O: ComponentOrder<Lumaa<S, u8>, P>,
711{
712    #[inline]
713    fn from(packed: Packed<O, P>) -> Self {
714        Lumaa::from(packed).color
715    }
716}
717
718impl<S, T, O, P> From<Packed<O, P>> for Lumaa<S, T>
719where
720    O: ComponentOrder<Lumaa<S, T>, P>,
721{
722    #[inline]
723    fn from(packed: Packed<O, P>) -> Self {
724        packed.unpack()
725    }
726}
727
728impl<S> From<u16> for Luma<S, u8> {
729    #[inline]
730    fn from(color: u16) -> Self {
731        Self::from_u16::<super::channels::Al>(color)
732    }
733}
734
735impl<S> From<u16> for Lumaa<S, u8> {
736    #[inline]
737    fn from(color: u16) -> Self {
738        Self::from_u16::<super::channels::La>(color)
739    }
740}
741
742impl<S> From<Luma<S, u8>> for u16 {
743    #[inline]
744    fn from(color: Luma<S, u8>) -> Self {
745        Luma::into_u16::<super::channels::Al>(color)
746    }
747}
748
749impl<S> From<Lumaa<S, u8>> for u16 {
750    #[inline]
751    fn from(color: Lumaa<S, u8>) -> Self {
752        Lumaa::into_u16::<super::channels::La>(color)
753    }
754}
755
756impl_simd_array_conversion!(Luma<S>, [luma], standard);
757impl_struct_of_array_traits!(Luma<S>, [luma], standard);
758
759impl_copy_clone!(Luma<S>, [luma], standard);
760impl_eq!(Luma<S>, [luma]);
761
762impl<S, T> fmt::LowerHex for Luma<S, T>
763where
764    T: fmt::LowerHex,
765{
766    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
767        let size = f.width().unwrap_or(::core::mem::size_of::<T>() * 2);
768        write!(f, "{:0width$x}", self.luma, width = size)
769    }
770}
771
772impl<S, T> fmt::UpperHex for Luma<S, T>
773where
774    T: fmt::UpperHex,
775{
776    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
777        let size = f.width().unwrap_or(::core::mem::size_of::<T>() * 2);
778        write!(f, "{:0width$X}", self.luma, width = size)
779    }
780}
781
782#[allow(deprecated)]
783impl<S, T> crate::RelativeContrast for Luma<S, T>
784where
785    T: Real + Arithmetics + PartialCmp,
786    T::Mask: LazySelect<T>,
787    S: LumaStandard,
788    S::TransferFn: IntoLinear<T, T>,
789{
790    type Scalar = T;
791
792    #[inline]
793    fn get_contrast_ratio(self, other: Self) -> T {
794        let luma1 = self.into_linear();
795        let luma2 = other.into_linear();
796
797        crate::contrast_ratio(luma1.luma, luma2.luma)
798    }
799}
800
801impl<S, T> Wcag21RelativeContrast for Luma<S, T>
802where
803    Self: IntoColor<Luma<Linear<D65>, T>>,
804    S: LumaStandard<WhitePoint = D65>,
805    T: Real + Add<T, Output = T> + Div<T, Output = T> + PartialCmp + MinMax,
806{
807    type Scalar = T;
808
809    fn relative_luminance(self) -> Luma<Linear<D65>, Self::Scalar> {
810        self.into_color()
811    }
812}
813
814impl_rand_traits_cartesian!(UniformLuma, Luma<S> {luma} phantom: standard: PhantomData<S>);
815
816#[cfg(feature = "bytemuck")]
817unsafe impl<S, T> bytemuck::Zeroable for Luma<S, T> where T: bytemuck::Zeroable {}
818
819#[cfg(feature = "bytemuck")]
820unsafe impl<S: 'static, T> bytemuck::Pod for Luma<S, T> where T: bytemuck::Pod {}
821
822#[cfg(test)]
823mod test {
824    use crate::encoding::Srgb;
825    use crate::Luma;
826
827    test_convert_into_from_xyz!(Luma);
828
829    #[test]
830    fn ranges() {
831        assert_ranges! {
832            Luma<Srgb, f64>;
833            clamped {
834                luma: 0.0 => 1.0
835            }
836            clamped_min {}
837            unclamped {}
838        }
839    }
840
841    raw_pixel_conversion_tests!(Luma<Srgb>: luma);
842
843    #[test]
844    fn lower_hex() {
845        assert_eq!(format!("{:x}", Luma::<Srgb, u8>::new(161)), "a1");
846    }
847
848    #[test]
849    fn lower_hex_small_numbers() {
850        assert_eq!(format!("{:x}", Luma::<Srgb, u8>::new(1)), "01");
851        assert_eq!(format!("{:x}", Luma::<Srgb, u16>::new(1)), "0001");
852        assert_eq!(format!("{:x}", Luma::<Srgb, u32>::new(1)), "00000001");
853        assert_eq!(
854            format!("{:x}", Luma::<Srgb, u64>::new(1)),
855            "0000000000000001"
856        );
857    }
858
859    #[test]
860    fn lower_hex_custom_width() {
861        assert_eq!(format!("{:03x}", Luma::<Srgb, u8>::new(1)), "001");
862        assert_eq!(format!("{:03x}", Luma::<Srgb, u16>::new(1)), "001");
863        assert_eq!(format!("{:03x}", Luma::<Srgb, u32>::new(1)), "001");
864        assert_eq!(format!("{:03x}", Luma::<Srgb, u64>::new(1)), "001");
865    }
866
867    #[test]
868    fn upper_hex() {
869        assert_eq!(format!("{:X}", Luma::<Srgb, u8>::new(161)), "A1");
870    }
871
872    #[test]
873    fn upper_hex_small_numbers() {
874        assert_eq!(format!("{:X}", Luma::<Srgb, u8>::new(1)), "01");
875        assert_eq!(format!("{:X}", Luma::<Srgb, u16>::new(1)), "0001");
876        assert_eq!(format!("{:X}", Luma::<Srgb, u32>::new(1)), "00000001");
877        assert_eq!(
878            format!("{:X}", Luma::<Srgb, u64>::new(1)),
879            "0000000000000001"
880        );
881    }
882
883    #[test]
884    fn upper_hex_custom_width() {
885        assert_eq!(format!("{:03X}", Luma::<Srgb, u8>::new(1)), "001");
886        assert_eq!(format!("{:03X}", Luma::<Srgb, u16>::new(1)), "001");
887        assert_eq!(format!("{:03X}", Luma::<Srgb, u32>::new(1)), "001");
888        assert_eq!(format!("{:03X}", Luma::<Srgb, u64>::new(1)), "001");
889    }
890
891    #[test]
892    fn check_min_max_components() {
893        assert_eq!(Luma::<Srgb, f32>::min_luma(), 0.0);
894        assert_eq!(Luma::<Srgb, f32>::max_luma(), 1.0);
895    }
896
897    struct_of_arrays_tests!(
898        Luma<Srgb>[luma] phantom: standard,
899        super::Lumaa::new(0.1f32, 0.4),
900        super::Lumaa::new(0.2, 0.5),
901        super::Lumaa::new(0.3, 0.6)
902    );
903
904    #[cfg(feature = "serializing")]
905    #[test]
906    fn serialize() {
907        let serialized = ::serde_json::to_string(&Luma::<Srgb>::new(0.3)).unwrap();
908
909        assert_eq!(serialized, r#"{"luma":0.3}"#);
910    }
911
912    #[cfg(feature = "serializing")]
913    #[test]
914    fn deserialize() {
915        let deserialized: Luma<Srgb> = ::serde_json::from_str(r#"{"luma":0.3}"#).unwrap();
916
917        assert_eq!(deserialized, Luma::<Srgb>::new(0.3));
918    }
919
920    test_uniform_distribution! {
921        Luma<Srgb, f32> {
922            luma: (0.0, 1.0)
923        },
924        min: Luma::new(0.0f32),
925        max: Luma::new(1.0)
926    }
927}