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
22pub type Lumaa<S = Srgb, T = f32> = Alpha<Luma<S, T>, T>;
25
26#[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 pub luma: T,
47
48 #[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 pub const fn new(luma: T) -> Luma<S, T> {
57 Luma {
58 luma,
59 standard: PhantomData,
60 }
61 }
62
63 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 pub fn from_format<U>(color: Luma<S, U>) -> Self
76 where
77 T: FromStimulus<U>,
78 {
79 color.into_format()
80 }
81
82 pub fn into_components(self) -> (T,) {
84 (self.luma,)
85 }
86
87 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 pub fn min_luma() -> T {
110 T::zero()
111 }
112
113 pub fn max_luma() -> T {
115 T::max_intensity()
116 }
117}
118
119impl<S> Luma<S, u8> {
120 #[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 #[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 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 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 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 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
270unsafe impl<S> UintCast for Luma<S, u8> {
275 type Uint = u8;
276}
277
278unsafe impl<S> UintCast for Luma<S, u16> {
283 type Uint = u16;
284}
285
286unsafe impl<S> UintCast for Luma<S, u32> {
291 type Uint = u32;
292}
293
294unsafe impl<S> UintCast for Luma<S, u64> {
299 type Uint = u64;
300}
301
302unsafe impl<S> UintCast for Luma<S, u128> {
307 type Uint = u128;
308}
309
310impl<S, T, A> Alpha<Luma<S, T>, A> {
312 pub const fn new(luma: T, alpha: A) -> Self {
314 Alpha {
315 color: Luma::new(luma),
316 alpha,
317 }
318 }
319
320 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 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 pub fn into_components(self) -> (T, A) {
343 (self.color.luma, self.alpha)
344 }
345
346 pub fn from_components((luma, alpha): (T, A)) -> Self {
348 Self::new(luma, alpha)
349 }
350}
351
352impl<S> Lumaa<S, u8> {
353 #[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 #[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 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 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 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 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}