1use core::{
2 any::TypeId,
3 fmt,
4 fmt::Debug,
5 marker::PhantomData,
6 num::ParseIntError,
7 ops::{Add, Div},
8 str::FromStr,
9};
10
11use crate::{
12 alpha::Alpha,
13 angle::{RealAngle, UnsignedAngle},
14 bool_mask::{BitOps, HasBoolMask, LazySelect},
15 cast::{ComponentOrder, Packed},
16 color_difference::Wcag21RelativeContrast,
17 convert::{FromColorUnclamped, IntoColorUnclamped},
18 encoding::{FromLinear, IntoLinear, Linear, Srgb},
19 luma::LumaStandard,
20 matrix::{matrix_inverse, matrix_map, multiply_xyz_to_rgb, rgb_to_xyz_matrix},
21 num::{
22 Abs, Arithmetics, FromScalar, IsValidDivisor, MinMax, One, PartialCmp, Real, Recip, Round,
23 Trigonometry, Zero,
24 },
25 oklab::oklab_to_linear_srgb,
26 rgb::{RgbSpace, RgbStandard},
27 stimulus::{FromStimulus, Stimulus, StimulusColor},
28 white_point::{Any, WhitePoint, D65},
29 FromColor, GetHue, Hsl, Hsv, IntoColor, Luma, Oklab, RgbHue, Xyz, Yxy,
30};
31
32use super::Primaries;
33
34pub type Rgba<S = Srgb, T = f32> = Alpha<Rgb<S, T>, T>;
37
38#[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)]
172#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
173#[palette(
174 palette_internal,
175 rgb_standard = "S",
176 component = "T",
177 skip_derives(Xyz, Hsv, Hsl, Luma, Rgb, Oklab)
178)]
179#[repr(C)]
180pub struct Rgb<S = Srgb, T = f32> {
181 pub red: T,
184
185 pub green: T,
188
189 pub blue: T,
192
193 #[cfg_attr(feature = "serializing", serde(skip))]
195 #[palette(unsafe_zero_sized)]
196 pub standard: PhantomData<S>,
197}
198
199impl<S, T> Rgb<S, T> {
200 pub const fn new(red: T, green: T, blue: T) -> Rgb<S, T> {
217 Rgb {
218 red,
219 green,
220 blue,
221 standard: PhantomData,
222 }
223 }
224
225 pub fn into_format<U>(self) -> Rgb<S, U>
237 where
238 U: FromStimulus<T>,
239 {
240 Rgb {
241 red: U::from_stimulus(self.red),
242 green: U::from_stimulus(self.green),
243 blue: U::from_stimulus(self.blue),
244 standard: PhantomData,
245 }
246 }
247
248 pub fn from_format<U>(color: Rgb<S, U>) -> Self
260 where
261 T: FromStimulus<U>,
262 {
263 color.into_format()
264 }
265
266 pub fn into_components(self) -> (T, T, T) {
268 (self.red, self.green, self.blue)
269 }
270
271 pub fn from_components((red, green, blue): (T, T, T)) -> Self {
273 Self::new(red, green, blue)
274 }
275}
276
277impl<S, T> Rgb<S, T>
278where
279 T: Stimulus,
280{
281 pub fn min_red() -> T {
283 T::zero()
284 }
285
286 pub fn max_red() -> T {
288 T::max_intensity()
289 }
290
291 pub fn min_green() -> T {
293 T::zero()
294 }
295
296 pub fn max_green() -> T {
298 T::max_intensity()
299 }
300
301 pub fn min_blue() -> T {
303 T::zero()
304 }
305
306 pub fn max_blue() -> T {
308 T::max_intensity()
309 }
310}
311
312impl<S> Rgb<S, u8> {
313 #[inline]
334 pub fn into_u32<O>(self) -> u32
335 where
336 O: ComponentOrder<Rgba<S, u8>, u32>,
337 {
338 O::pack(Rgba::from(self))
339 }
340
341 #[inline]
362 pub fn from_u32<O>(color: u32) -> Self
363 where
364 O: ComponentOrder<Rgba<S, u8>, u32>,
365 {
366 O::unpack(color).color
367 }
368}
369
370impl<S: RgbStandard, T> Rgb<S, T> {
371 #[inline(always)]
386 pub fn into_linear<U>(self) -> Rgb<Linear<S::Space>, U>
387 where
388 S::TransferFn: IntoLinear<U, T>,
389 {
390 Rgb::new(
391 S::TransferFn::into_linear(self.red),
392 S::TransferFn::into_linear(self.green),
393 S::TransferFn::into_linear(self.blue),
394 )
395 }
396
397 #[inline(always)]
412 pub fn from_linear<U>(color: Rgb<Linear<S::Space>, U>) -> Self
413 where
414 S::TransferFn: FromLinear<U, T>,
415 {
416 Rgb::new(
417 S::TransferFn::from_linear(color.red),
418 S::TransferFn::from_linear(color.green),
419 S::TransferFn::from_linear(color.blue),
420 )
421 }
422}
423
424impl<S: RgbSpace, T> Rgb<Linear<S>, T> {
425 pub fn into_encoding<U, St>(self) -> Rgb<St, U>
440 where
441 St: RgbStandard<Space = S>,
442 St::TransferFn: FromLinear<T, U>,
443 {
444 Rgb::<St, U>::from_linear(self)
445 }
446
447 pub fn from_encoding<U, St>(color: Rgb<St, U>) -> Self
462 where
463 St: RgbStandard<Space = S>,
464 St::TransferFn: IntoLinear<T, U>,
465 {
466 color.into_linear()
467 }
468}
469
470impl<S, T> Rgb<S, T>
471where
472 S: RgbStandard,
473{
474 #[inline]
475 pub(crate) fn reinterpret_as<St>(self) -> Rgb<St, T>
476 where
477 S::Space: RgbSpace<WhitePoint = <St::Space as RgbSpace>::WhitePoint>,
478 St: RgbStandard,
479 {
480 Rgb {
481 red: self.red,
482 green: self.green,
483 blue: self.blue,
484 standard: PhantomData,
485 }
486 }
487}
488
489impl<S, T, A> Alpha<Rgb<S, T>, A> {
491 pub const fn new(red: T, green: T, blue: T, alpha: A) -> Self {
493 Alpha {
494 color: Rgb::new(red, green, blue),
495 alpha,
496 }
497 }
498
499 pub fn into_format<U, B>(self) -> Alpha<Rgb<S, U>, B>
510 where
511 U: FromStimulus<T>,
512 B: FromStimulus<A>,
513 {
514 Alpha {
515 color: self.color.into_format(),
516 alpha: B::from_stimulus(self.alpha),
517 }
518 }
519
520 pub fn from_format<U, B>(color: Alpha<Rgb<S, U>, B>) -> Self
531 where
532 T: FromStimulus<U>,
533 A: FromStimulus<B>,
534 {
535 color.into_format()
536 }
537
538 pub fn into_components(self) -> (T, T, T, A) {
540 (
541 self.color.red,
542 self.color.green,
543 self.color.blue,
544 self.alpha,
545 )
546 }
547
548 pub fn from_components((red, green, blue, alpha): (T, T, T, A)) -> Self {
550 Self::new(red, green, blue, alpha)
551 }
552}
553
554impl<S> Rgba<S, u8> {
555 #[inline]
576 pub fn into_u32<O>(self) -> u32
577 where
578 O: ComponentOrder<Rgba<S, u8>, u32>,
579 {
580 O::pack(self)
581 }
582
583 #[inline]
604 pub fn from_u32<O>(color: u32) -> Self
605 where
606 O: ComponentOrder<Rgba<S, u8>, u32>,
607 {
608 O::unpack(color)
609 }
610}
611
612impl<S: RgbStandard, T, A> Alpha<Rgb<S, T>, A> {
613 pub fn into_linear<U, B>(self) -> Alpha<Rgb<Linear<S::Space>, U>, B>
628 where
629 S::TransferFn: IntoLinear<U, T>,
630 B: FromStimulus<A>,
631 {
632 Alpha {
633 color: self.color.into_linear(),
634 alpha: B::from_stimulus(self.alpha),
635 }
636 }
637
638 pub fn from_linear<U, B>(color: Alpha<Rgb<Linear<S::Space>, U>, B>) -> Self
653 where
654 S::TransferFn: FromLinear<U, T>,
655 A: FromStimulus<B>,
656 {
657 Alpha {
658 color: Rgb::from_linear(color.color),
659 alpha: A::from_stimulus(color.alpha),
660 }
661 }
662}
663
664impl<S: RgbSpace, T, A> Alpha<Rgb<Linear<S>, T>, A> {
665 pub fn into_encoding<U, B, St>(self) -> Alpha<Rgb<St, U>, B>
680 where
681 St: RgbStandard<Space = S>,
682 St::TransferFn: FromLinear<T, U>,
683 B: FromStimulus<A>,
684 {
685 Alpha::<Rgb<St, U>, B>::from_linear(self)
686 }
687
688 pub fn from_encoding<U, B, St>(color: Alpha<Rgb<St, U>, B>) -> Self
703 where
704 St: RgbStandard<Space = S>,
705 St::TransferFn: IntoLinear<T, U>,
706 A: FromStimulus<B>,
707 {
708 color.into_linear()
709 }
710}
711
712impl_reference_component_methods!(Rgb<S>, [red, green, blue], standard);
713impl_struct_of_arrays_methods!(Rgb<S>, [red, green, blue], standard);
714
715impl<S1, S2, T> FromColorUnclamped<Rgb<S2, T>> for Rgb<S1, T>
716where
717 S1: RgbStandard + 'static,
718 S2: RgbStandard + 'static,
719 S1::TransferFn: FromLinear<T, T>,
720 S2::TransferFn: IntoLinear<T, T>,
721 S2::Space: RgbSpace<WhitePoint = <S1::Space as RgbSpace>::WhitePoint>,
722 Xyz<<S2::Space as RgbSpace>::WhitePoint, T>: FromColorUnclamped<Rgb<S2, T>>,
723 Rgb<S1, T>: FromColorUnclamped<Xyz<<S1::Space as RgbSpace>::WhitePoint, T>>,
724{
725 fn from_color_unclamped(rgb: Rgb<S2, T>) -> Self {
726 let rgb_space1 = TypeId::of::<<S1::Space as RgbSpace>::Primaries>();
727 let rgb_space2 = TypeId::of::<<S2::Space as RgbSpace>::Primaries>();
728
729 if TypeId::of::<S1>() == TypeId::of::<S2>() {
730 rgb.reinterpret_as()
731 } else if rgb_space1 == rgb_space2 {
732 Self::from_linear(rgb.into_linear().reinterpret_as())
733 } else {
734 Self::from_color_unclamped(Xyz::from_color_unclamped(rgb))
735 }
736 }
737}
738
739impl<S, T> FromColorUnclamped<Xyz<<S::Space as RgbSpace>::WhitePoint, T>> for Rgb<S, T>
740where
741 S: RgbStandard,
742 S::TransferFn: FromLinear<T, T>,
743 <S::Space as RgbSpace>::Primaries: Primaries<T::Scalar>,
744 <S::Space as RgbSpace>::WhitePoint: WhitePoint<T::Scalar>,
745 T: Arithmetics + FromScalar,
746 T::Scalar: Real
747 + Recip
748 + IsValidDivisor<Mask = bool>
749 + Arithmetics
750 + Clone
751 + FromScalar<Scalar = T::Scalar>,
752 Yxy<Any, T::Scalar>: IntoColorUnclamped<Xyz<Any, T::Scalar>>,
753{
754 fn from_color_unclamped(color: Xyz<<S::Space as RgbSpace>::WhitePoint, T>) -> Self {
755 let transform_matrix = S::Space::xyz_to_rgb_matrix().map_or_else(
756 || matrix_inverse(rgb_to_xyz_matrix::<S::Space, T::Scalar>()),
757 |matrix| matrix_map(matrix, T::Scalar::from_f64),
758 );
759 Self::from_linear(multiply_xyz_to_rgb(transform_matrix, color))
760 }
761}
762
763impl<S, T> FromColorUnclamped<Hsl<S, T>> for Rgb<S, T>
764where
765 T: Real
766 + RealAngle
767 + UnsignedAngle
768 + Zero
769 + One
770 + Abs
771 + Round
772 + PartialCmp
773 + Arithmetics
774 + Clone,
775 T::Mask: LazySelect<T> + BitOps + Clone,
776{
777 fn from_color_unclamped(hsl: Hsl<S, T>) -> Self {
778 let c = (T::one() - (hsl.lightness.clone() * T::from_f64(2.0) - T::one()).abs())
779 * hsl.saturation;
780 let h = hsl.hue.into_positive_degrees() / T::from_f64(60.0);
781 let h_mod_two = h.clone() - Round::floor(h.clone() * T::from_f64(0.5)) * T::from_f64(2.0);
783 let x = c.clone() * (T::one() - (h_mod_two - T::one()).abs());
784 let m = hsl.lightness - c.clone() * T::from_f64(0.5);
785
786 let is_zone0 = h.gt_eq(&T::zero()) & h.lt(&T::one());
787 let is_zone1 = h.gt_eq(&T::one()) & h.lt(&T::from_f64(2.0));
788 let is_zone2 = h.gt_eq(&T::from_f64(2.0)) & h.lt(&T::from_f64(3.0));
789 let is_zone3 = h.gt_eq(&T::from_f64(3.0)) & h.lt(&T::from_f64(4.0));
790 let is_zone4 = h.gt_eq(&T::from_f64(4.0)) & h.lt(&T::from_f64(5.0));
791
792 let red = lazy_select! {
793 if is_zone1.clone() | &is_zone4 => x.clone(),
794 if is_zone2.clone() | &is_zone3 => T::zero(),
795 else => c.clone(),
796 };
797
798 let green = lazy_select! {
799 if is_zone0.clone() | &is_zone3 => x.clone(),
800 if is_zone1.clone() | &is_zone2 => c.clone(),
801 else => T::zero(),
802 };
803
804 let blue = lazy_select! {
805 if is_zone0 | is_zone1 => T::zero(),
806 if is_zone3 | is_zone4 => c,
807 else => x,
808 };
809
810 Rgb {
811 red: red + m.clone(),
812 green: green + m.clone(),
813 blue: blue + m,
814 standard: PhantomData,
815 }
816 }
817}
818
819impl<S, T> FromColorUnclamped<Hsv<S, T>> for Rgb<S, T>
820where
821 T: Real
822 + RealAngle
823 + UnsignedAngle
824 + Round
825 + Zero
826 + One
827 + Abs
828 + PartialCmp
829 + Arithmetics
830 + Clone,
831 T::Mask: LazySelect<T> + BitOps + Clone,
832{
833 fn from_color_unclamped(hsv: Hsv<S, T>) -> Self {
834 let c = hsv.value.clone() * hsv.saturation;
835 let h = hsv.hue.into_positive_degrees() / T::from_f64(60.0);
836 let h_mod_two = h.clone() - Round::floor(h.clone() * T::from_f64(0.5)) * T::from_f64(2.0);
838 let x = c.clone() * (T::one() - (h_mod_two - T::one()).abs());
839 let m = hsv.value - c.clone();
840
841 let is_zone0 = h.gt_eq(&T::zero()) & h.lt(&T::one());
842 let is_zone1 = h.gt_eq(&T::one()) & h.lt(&T::from_f64(2.0));
843 let is_zone2 = h.gt_eq(&T::from_f64(2.0)) & h.lt(&T::from_f64(3.0));
844 let is_zone3 = h.gt_eq(&T::from_f64(3.0)) & h.lt(&T::from_f64(4.0));
845 let is_zone4 = h.gt_eq(&T::from_f64(4.0)) & h.lt(&T::from_f64(5.0));
846
847 let red = lazy_select! {
848 if is_zone1.clone() | &is_zone4 => x.clone(),
849 if is_zone2.clone() | &is_zone3 => T::zero(),
850 else => c.clone(),
851 };
852
853 let green = lazy_select! {
854 if is_zone0.clone() | &is_zone3 => x.clone(),
855 if is_zone1.clone() | &is_zone2 => c.clone(),
856 else => T::zero(),
857 };
858
859 let blue = lazy_select! {
860 if is_zone0 | is_zone1 => T::zero(),
861 if is_zone3 | is_zone4 => c,
862 else => x,
863 };
864
865 Rgb {
866 red: red + m.clone(),
867 green: green + m.clone(),
868 blue: blue + m,
869 standard: PhantomData,
870 }
871 }
872}
873
874impl<S, St, T> FromColorUnclamped<Luma<St, T>> for Rgb<S, T>
875where
876 S: RgbStandard + 'static,
877 St: LumaStandard<WhitePoint = <S::Space as RgbSpace>::WhitePoint> + 'static,
878 S::TransferFn: FromLinear<T, T>,
879 St::TransferFn: IntoLinear<T, T>,
880 T: Clone,
881{
882 #[inline]
883 fn from_color_unclamped(color: Luma<St, T>) -> Self {
884 if TypeId::of::<S::TransferFn>() == TypeId::of::<St::TransferFn>() {
885 Rgb {
886 red: color.luma.clone(),
887 green: color.luma.clone(),
888 blue: color.luma,
889 standard: PhantomData,
890 }
891 } else {
892 let luma = color.into_linear();
893
894 Self::from_linear(Rgb {
895 red: luma.luma.clone(),
896 green: luma.luma.clone(),
897 blue: luma.luma,
898 standard: PhantomData,
899 })
900 }
901 }
902}
903
904impl<S, T> FromColorUnclamped<Oklab<T>> for Rgb<S, T>
905where
906 T: Real + Arithmetics + Copy,
907 S: RgbStandard,
908 S::TransferFn: FromLinear<T, T>,
909 S::Space: RgbSpace<WhitePoint = D65> + 'static,
910 Rgb<Linear<Srgb>, T>: IntoColorUnclamped<Self>,
911 Xyz<D65, T>: FromColorUnclamped<Oklab<T>> + IntoColorUnclamped<Self>,
912{
913 fn from_color_unclamped(oklab: Oklab<T>) -> Self {
914 if TypeId::of::<<S as RgbStandard>::Space>() == TypeId::of::<Srgb>() {
915 oklab_to_linear_srgb(oklab).into_color_unclamped()
919 } else {
920 Xyz::from_color_unclamped(oklab).into_color_unclamped()
922 }
923 }
924}
925
926impl_is_within_bounds! {
927 Rgb<S> {
928 red => [Self::min_red(), Self::max_red()],
929 green => [Self::min_green(), Self::max_green()],
930 blue => [Self::min_blue(), Self::max_blue()]
931 }
932 where T: Stimulus
933}
934impl_clamp! {
935 Rgb<S> {
936 red => [Self::min_red(), Self::max_red()],
937 green => [Self::min_green(), Self::max_green()],
938 blue => [Self::min_blue(), Self::max_blue()]
939 }
940 other {standard}
941 where T: Stimulus
942}
943
944impl_mix!(Rgb<S>);
945impl_lighten! {
946 Rgb<S>
947 increase {
948 red => [Self::min_red(), Self::max_red()],
949 green => [Self::min_green(), Self::max_green()],
950 blue => [Self::min_blue(), Self::max_blue()]
951 }
952 other {}
953 phantom: standard
954 where T: Stimulus,
955}
956
957impl<S, T> GetHue for Rgb<S, T>
958where
959 T: Real + RealAngle + Trigonometry + Arithmetics + Clone,
960{
961 type Hue = RgbHue<T>;
962
963 fn get_hue(&self) -> RgbHue<T> {
964 let sqrt_3: T = T::from_f64(1.73205081);
965
966 RgbHue::from_cartesian(
967 self.red.clone() * T::from_f64(2.0) - self.green.clone() - self.blue.clone(),
968 sqrt_3 * (self.green.clone() - self.blue.clone()),
969 )
970 }
971}
972
973impl_premultiply!(Rgb<S> {red, green, blue} phantom: standard);
974impl_euclidean_distance!(Rgb<S> {red, green, blue});
975
976impl<S, T> StimulusColor for Rgb<S, T> where T: Stimulus {}
977
978impl<S, T> HasBoolMask for Rgb<S, T>
979where
980 T: HasBoolMask,
981{
982 type Mask = T::Mask;
983}
984
985impl<S, T> Default for Rgb<S, T>
986where
987 T: Stimulus,
988{
989 fn default() -> Rgb<S, T> {
990 Rgb::new(Self::min_red(), Self::min_green(), Self::min_blue())
991 }
992}
993
994impl_color_add!(Rgb<S>, [red, green, blue], standard);
995impl_color_sub!(Rgb<S>, [red, green, blue], standard);
996impl_color_mul!(Rgb<S>, [red, green, blue], standard);
997impl_color_div!(Rgb<S>, [red, green, blue], standard);
998
999impl_tuple_conversion!(Rgb<S> as (T, T, T));
1000impl_array_casts!(Rgb<S, T>, [T; 3]);
1001impl_simd_array_conversion!(Rgb<S>, [red, green, blue], standard);
1002impl_struct_of_array_traits!(Rgb<S>, [red, green, blue], standard);
1003
1004impl_eq!(Rgb<S>, [red, green, blue]);
1005impl_copy_clone!(Rgb<S>, [red, green, blue], standard);
1006
1007impl<S, T> fmt::LowerHex for Rgb<S, T>
1008where
1009 T: fmt::LowerHex,
1010{
1011 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1012 let size = f.width().unwrap_or(::core::mem::size_of::<T>() * 2);
1013 write!(
1014 f,
1015 "{:0width$x}{:0width$x}{:0width$x}",
1016 self.red,
1017 self.green,
1018 self.blue,
1019 width = size
1020 )
1021 }
1022}
1023
1024impl<S, T> fmt::UpperHex for Rgb<S, T>
1025where
1026 T: fmt::UpperHex,
1027{
1028 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1029 let size = f.width().unwrap_or(::core::mem::size_of::<T>() * 2);
1030 write!(
1031 f,
1032 "{:0width$X}{:0width$X}{:0width$X}",
1033 self.red,
1034 self.green,
1035 self.blue,
1036 width = size
1037 )
1038 }
1039}
1040
1041#[derive(Debug)]
1043pub enum FromHexError {
1044 ParseIntError(ParseIntError),
1046 HexFormatError(&'static str),
1048 RgbaHexFormatError(&'static str),
1050}
1051
1052impl From<ParseIntError> for FromHexError {
1053 fn from(err: ParseIntError) -> FromHexError {
1054 FromHexError::ParseIntError(err)
1055 }
1056}
1057
1058impl From<&'static str> for FromHexError {
1059 fn from(err: &'static str) -> FromHexError {
1060 FromHexError::HexFormatError(err)
1061 }
1062}
1063
1064impl core::fmt::Display for FromHexError {
1065 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1066 match self {
1067 FromHexError::ParseIntError(e) => write!(f, "{}", e),
1068 FromHexError::HexFormatError(s) => write!(
1069 f,
1070 "{}, please use format '#fff', 'fff', '#ffffff' or 'ffffff'.",
1071 s
1072 ),
1073 FromHexError::RgbaHexFormatError(s) => write!(
1074 f,
1075 "{}, please use format '#ffff', 'ffff', '#ffffffff' or 'ffffffff'.",
1076 s
1077 ),
1078 }
1079 }
1080}
1081
1082#[cfg(feature = "std")]
1083impl std::error::Error for FromHexError {
1084 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1085 match self {
1086 FromHexError::HexFormatError(_s) => None,
1087 FromHexError::RgbaHexFormatError(_s) => None,
1088 FromHexError::ParseIntError(e) => Some(e),
1089 }
1090 }
1091}
1092
1093impl<S> FromStr for Rgb<S, u8> {
1094 type Err = FromHexError;
1095
1096 fn from_str(hex: &str) -> Result<Self, Self::Err> {
1099 let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped);
1100 match hex_code.len() {
1101 3 => {
1102 let red = u8::from_str_radix(&hex_code[..1], 16)?;
1103 let green = u8::from_str_radix(&hex_code[1..2], 16)?;
1104 let blue = u8::from_str_radix(&hex_code[2..3], 16)?;
1105 let col: Rgb<S, u8> = Rgb::new(red * 17, green * 17, blue * 17);
1106 Ok(col)
1107 }
1108 6 => {
1109 let red = u8::from_str_radix(&hex_code[..2], 16)?;
1110 let green = u8::from_str_radix(&hex_code[2..4], 16)?;
1111 let blue = u8::from_str_radix(&hex_code[4..6], 16)?;
1112 let col: Rgb<S, u8> = Rgb::new(red, green, blue);
1113 Ok(col)
1114 }
1115 _ => Err(FromHexError::HexFormatError("invalid hex code format")),
1116 }
1117 }
1118}
1119
1120impl<S> FromStr for Rgba<S, u8> {
1121 type Err = FromHexError;
1122
1123 fn from_str(hex: &str) -> Result<Self, Self::Err> {
1126 let hex_code = hex.strip_prefix('#').map_or(hex, |stripped| stripped);
1127 match hex_code.len() {
1128 4 => {
1129 let red = u8::from_str_radix(&hex_code[..1], 16)?;
1130 let green = u8::from_str_radix(&hex_code[1..2], 16)?;
1131 let blue = u8::from_str_radix(&hex_code[2..3], 16)?;
1132 let alpha = u8::from_str_radix(&hex_code[3..4], 16)?;
1133 let col: Rgba<S, u8> = Rgba::new(red * 17, green * 17, blue * 17, alpha * 17);
1134 Ok(col)
1135 }
1136 8 => {
1137 let red = u8::from_str_radix(&hex_code[..2], 16)?;
1138 let green = u8::from_str_radix(&hex_code[2..4], 16)?;
1139 let blue = u8::from_str_radix(&hex_code[4..6], 16)?;
1140 let alpha = u8::from_str_radix(&hex_code[6..8], 16)?;
1141 let col: Rgba<S, u8> = Rgba::new(red, green, blue, alpha);
1142 Ok(col)
1143 }
1144 _ => Err(FromHexError::RgbaHexFormatError("invalid hex code format")),
1145 }
1146 }
1147}
1148
1149impl<S, T, P, O> From<Rgb<S, T>> for Packed<O, P>
1150where
1151 O: ComponentOrder<Rgba<S, T>, P>,
1152 Rgba<S, T>: From<Rgb<S, T>>,
1153{
1154 #[inline]
1155 fn from(color: Rgb<S, T>) -> Self {
1156 Self::from(Rgba::from(color))
1157 }
1158}
1159
1160impl<S, T, O, P> From<Rgba<S, T>> for Packed<O, P>
1161where
1162 O: ComponentOrder<Rgba<S, T>, P>,
1163{
1164 #[inline]
1165 fn from(color: Rgba<S, T>) -> Self {
1166 Packed::pack(color)
1167 }
1168}
1169
1170impl<S, O, P> From<Packed<O, P>> for Rgb<S, u8>
1171where
1172 O: ComponentOrder<Rgba<S, u8>, P>,
1173{
1174 #[inline]
1175 fn from(packed: Packed<O, P>) -> Self {
1176 Rgba::from(packed).color
1177 }
1178}
1179
1180impl<S, T, O, P> From<Packed<O, P>> for Rgba<S, T>
1181where
1182 O: ComponentOrder<Rgba<S, T>, P>,
1183{
1184 #[inline]
1185 fn from(packed: Packed<O, P>) -> Self {
1186 packed.unpack()
1187 }
1188}
1189
1190impl<S> From<u32> for Rgb<S, u8> {
1191 #[inline]
1192 fn from(color: u32) -> Self {
1193 Self::from_u32::<super::channels::Argb>(color)
1194 }
1195}
1196
1197impl<S> From<u32> for Rgba<S, u8> {
1198 #[inline]
1199 fn from(color: u32) -> Self {
1200 Self::from_u32::<super::channels::Rgba>(color)
1201 }
1202}
1203
1204impl<S> From<Rgb<S, u8>> for u32 {
1205 #[inline]
1206 fn from(color: Rgb<S, u8>) -> Self {
1207 Rgb::into_u32::<super::channels::Argb>(color)
1208 }
1209}
1210
1211impl<S> From<Rgba<S, u8>> for u32 {
1212 #[inline]
1213 fn from(color: Rgba<S, u8>) -> Self {
1214 Rgba::into_u32::<super::channels::Rgba>(color)
1215 }
1216}
1217
1218impl<S> From<Rgb<S, u8>> for Rgb<S, f32> {
1219 #[inline]
1220 fn from(color: Rgb<S, u8>) -> Self {
1221 color.into_format()
1222 }
1223}
1224
1225impl<S> From<Rgba<S, u8>> for Rgba<S, f32> {
1226 #[inline]
1227 fn from(color: Rgba<S, u8>) -> Self {
1228 color.into_format()
1229 }
1230}
1231
1232impl<S> From<Rgb<S, f32>> for Rgb<S, u8> {
1233 #[inline]
1234 fn from(color: Rgb<S, f32>) -> Self {
1235 color.into_format()
1236 }
1237}
1238
1239impl<S> From<Rgba<S, f32>> for Rgba<S, u8> {
1240 #[inline]
1241 fn from(color: Rgba<S, f32>) -> Self {
1242 color.into_format()
1243 }
1244}
1245
1246impl<S> From<Rgb<S, u8>> for Rgb<S, f64> {
1247 #[inline]
1248 fn from(color: Rgb<S, u8>) -> Self {
1249 color.into_format()
1250 }
1251}
1252
1253impl<S> From<Rgba<S, u8>> for Rgba<S, f64> {
1254 #[inline]
1255 fn from(color: Rgba<S, u8>) -> Self {
1256 color.into_format()
1257 }
1258}
1259
1260impl<S> From<Rgb<S, f64>> for Rgb<S, u8> {
1261 #[inline]
1262 fn from(color: Rgb<S, f64>) -> Self {
1263 color.into_format()
1264 }
1265}
1266
1267impl<S> From<Rgba<S, f64>> for Rgba<S, u8> {
1268 #[inline]
1269 fn from(color: Rgba<S, f64>) -> Self {
1270 color.into_format()
1271 }
1272}
1273
1274impl<S> From<Rgb<S, f32>> for Rgb<S, f64> {
1275 #[inline]
1276 fn from(color: Rgb<S, f32>) -> Self {
1277 color.into_format()
1278 }
1279}
1280
1281impl<S> From<Rgba<S, f32>> for Rgba<S, f64> {
1282 #[inline]
1283 fn from(color: Rgba<S, f32>) -> Self {
1284 color.into_format()
1285 }
1286}
1287
1288impl<S> From<Rgb<S, f64>> for Rgb<S, f32> {
1289 #[inline]
1290 fn from(color: Rgb<S, f64>) -> Self {
1291 color.into_format()
1292 }
1293}
1294
1295impl<S> From<Rgba<S, f64>> for Rgba<S, f32> {
1296 #[inline]
1297 fn from(color: Rgba<S, f64>) -> Self {
1298 color.into_format()
1299 }
1300}
1301
1302#[allow(deprecated)]
1303impl<S, T> crate::RelativeContrast for Rgb<S, T>
1304where
1305 T: Real + Arithmetics + PartialCmp,
1306 T::Mask: LazySelect<T>,
1307 S: RgbStandard,
1308 Xyz<<<S as RgbStandard>::Space as RgbSpace>::WhitePoint, T>: FromColor<Self>,
1309{
1310 type Scalar = T;
1311
1312 #[inline]
1313 fn get_contrast_ratio(self, other: Self) -> T {
1314 let xyz1 = Xyz::from_color(self);
1315 let xyz2 = Xyz::from_color(other);
1316
1317 crate::contrast_ratio(xyz1.y, xyz2.y)
1318 }
1319}
1320
1321impl<S, T> Wcag21RelativeContrast for Rgb<S, T>
1322where
1323 Self: IntoColor<Luma<Linear<D65>, T>>,
1324 S: RgbStandard<Space = crate::encoding::srgb::Srgb>,
1325 T: Real + Add<T, Output = T> + Div<T, Output = T> + PartialCmp + MinMax,
1326{
1327 type Scalar = T;
1328
1329 fn relative_luminance(self) -> Luma<Linear<D65>, Self::Scalar> {
1330 self.into_color()
1331 }
1332}
1333
1334impl_rand_traits_cartesian!(UniformRgb, Rgb<S> {red, green, blue} phantom: standard: PhantomData<S>);
1335
1336#[cfg(feature = "bytemuck")]
1337unsafe impl<S, T> bytemuck::Zeroable for Rgb<S, T> where T: bytemuck::Zeroable {}
1338
1339#[cfg(feature = "bytemuck")]
1340unsafe impl<S: 'static, T> bytemuck::Pod for Rgb<S, T> where T: bytemuck::Pod {}
1341
1342#[cfg(test)]
1343mod test {
1344 use core::str::FromStr;
1345
1346 use crate::encoding::Srgb;
1347 use crate::rgb::channels;
1348
1349 use super::{Rgb, Rgba};
1350
1351 test_convert_into_from_xyz!(Rgb);
1352
1353 #[test]
1354 fn ranges() {
1355 assert_ranges! {
1356 Rgb<Srgb, f64>;
1357 clamped {
1358 red: 0.0 => 1.0,
1359 green: 0.0 => 1.0,
1360 blue: 0.0 => 1.0
1361 }
1362 clamped_min {}
1363 unclamped {}
1364 }
1365 }
1366
1367 raw_pixel_conversion_tests!(Rgb<Srgb>: red, green, blue);
1368 raw_pixel_conversion_fail_tests!(Rgb<Srgb>: red, green, blue);
1369
1370 #[test]
1371 fn lower_hex() {
1372 assert_eq!(
1373 format!("{:x}", Rgb::<Srgb, u8>::new(171, 193, 35)),
1374 "abc123"
1375 );
1376 }
1377
1378 #[test]
1379 fn lower_hex_small_numbers() {
1380 assert_eq!(format!("{:x}", Rgb::<Srgb, u8>::new(1, 2, 3)), "010203");
1381 assert_eq!(
1382 format!("{:x}", Rgb::<Srgb, u16>::new(1, 2, 3)),
1383 "000100020003"
1384 );
1385 assert_eq!(
1386 format!("{:x}", Rgb::<Srgb, u32>::new(1, 2, 3)),
1387 "000000010000000200000003"
1388 );
1389 assert_eq!(
1390 format!("{:x}", Rgb::<Srgb, u64>::new(1, 2, 3)),
1391 "000000000000000100000000000000020000000000000003"
1392 );
1393 }
1394
1395 #[test]
1396 fn lower_hex_custom_width() {
1397 assert_eq!(
1398 format!("{:03x}", Rgb::<Srgb, u8>::new(1, 2, 3)),
1399 "001002003"
1400 );
1401 assert_eq!(
1402 format!("{:03x}", Rgb::<Srgb, u16>::new(1, 2, 3)),
1403 "001002003"
1404 );
1405 assert_eq!(
1406 format!("{:03x}", Rgb::<Srgb, u32>::new(1, 2, 3)),
1407 "001002003"
1408 );
1409 assert_eq!(
1410 format!("{:03x}", Rgb::<Srgb, u64>::new(1, 2, 3)),
1411 "001002003"
1412 );
1413 }
1414
1415 #[test]
1416 fn upper_hex() {
1417 assert_eq!(
1418 format!("{:X}", Rgb::<Srgb, u8>::new(171, 193, 35)),
1419 "ABC123"
1420 );
1421 }
1422
1423 #[test]
1424 fn upper_hex_small_numbers() {
1425 assert_eq!(format!("{:X}", Rgb::<Srgb, u8>::new(1, 2, 3)), "010203");
1426 assert_eq!(
1427 format!("{:X}", Rgb::<Srgb, u16>::new(1, 2, 3)),
1428 "000100020003"
1429 );
1430 assert_eq!(
1431 format!("{:X}", Rgb::<Srgb, u32>::new(1, 2, 3)),
1432 "000000010000000200000003"
1433 );
1434 assert_eq!(
1435 format!("{:X}", Rgb::<Srgb, u64>::new(1, 2, 3)),
1436 "000000000000000100000000000000020000000000000003"
1437 );
1438 }
1439
1440 #[test]
1441 fn upper_hex_custom_width() {
1442 assert_eq!(
1443 format!("{:03X}", Rgb::<Srgb, u8>::new(1, 2, 3)),
1444 "001002003"
1445 );
1446 assert_eq!(
1447 format!("{:03X}", Rgb::<Srgb, u16>::new(1, 2, 3)),
1448 "001002003"
1449 );
1450 assert_eq!(
1451 format!("{:03X}", Rgb::<Srgb, u32>::new(1, 2, 3)),
1452 "001002003"
1453 );
1454 assert_eq!(
1455 format!("{:03X}", Rgb::<Srgb, u64>::new(1, 2, 3)),
1456 "001002003"
1457 );
1458 }
1459
1460 #[test]
1461 fn rgb_hex_into_from() {
1462 let c1 = Rgb::<Srgb, u8>::from_u32::<channels::Argb>(0x1100_7FFF);
1463 let c2 = Rgb::<Srgb, u8>::new(0u8, 127, 255);
1464 assert_eq!(c1, c2);
1465 assert_eq!(Rgb::<Srgb, u8>::into_u32::<channels::Argb>(c1), 0xFF00_7FFF);
1466
1467 let c1 = Rgba::<Srgb, u8>::from_u32::<channels::Rgba>(0x007F_FF80);
1468 let c2 = Rgba::<Srgb, u8>::new(0u8, 127, 255, 128);
1469 assert_eq!(c1, c2);
1470 assert_eq!(
1471 Rgba::<Srgb, u8>::into_u32::<channels::Rgba>(c1),
1472 0x007F_FF80
1473 );
1474
1475 assert_eq!(
1476 Rgb::<Srgb, u8>::from(0x7FFF_FF80),
1477 Rgb::from((255u8, 255, 128))
1478 );
1479 assert_eq!(
1480 Rgba::<Srgb, u8>::from(0x7FFF_FF80),
1481 Rgba::from((127u8, 255, 255, 128))
1482 );
1483 }
1484
1485 #[cfg(feature = "serializing")]
1486 #[test]
1487 fn serialize() {
1488 let serialized = ::serde_json::to_string(&Rgb::<Srgb>::new(0.3, 0.8, 0.1)).unwrap();
1489
1490 assert_eq!(serialized, r#"{"red":0.3,"green":0.8,"blue":0.1}"#);
1491 }
1492
1493 #[cfg(feature = "serializing")]
1494 #[test]
1495 fn deserialize() {
1496 let deserialized: Rgb<Srgb> =
1497 ::serde_json::from_str(r#"{"red":0.3,"green":0.8,"blue":0.1}"#).unwrap();
1498
1499 assert_eq!(deserialized, Rgb::<Srgb>::new(0.3, 0.8, 0.1));
1500 }
1501
1502 #[test]
1503 fn from_str() {
1504 let c = Rgb::<Srgb, u8>::from_str("#ffffff");
1505 assert!(c.is_ok());
1506 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(255, 255, 255));
1507 let c = Rgb::<Srgb, u8>::from_str("#gggggg");
1508 assert!(c.is_err());
1509 let c = Rgb::<Srgb, u8>::from_str("#fff");
1510 assert!(c.is_ok());
1511 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(255, 255, 255));
1512 let c = Rgb::<Srgb, u8>::from_str("#000000");
1513 assert!(c.is_ok());
1514 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(0, 0, 0));
1515 let c = Rgb::<Srgb, u8>::from_str("");
1516 assert!(c.is_err());
1517 let c = Rgb::<Srgb, u8>::from_str("#123456");
1518 assert!(c.is_ok());
1519 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(18, 52, 86));
1520 let c = Rgb::<Srgb, u8>::from_str("#iii");
1521 assert!(c.is_err());
1522 assert_eq!(
1523 format!("{}", c.err().unwrap()),
1524 "invalid digit found in string"
1525 );
1526 let c = Rgb::<Srgb, u8>::from_str("#08f");
1527 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(0, 136, 255));
1528 let c = Rgb::<Srgb, u8>::from_str("08f");
1529 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(0, 136, 255));
1530 let c = Rgb::<Srgb, u8>::from_str("ffffff");
1531 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(255, 255, 255));
1532 let c = Rgb::<Srgb, u8>::from_str("#12");
1533 assert!(c.is_err());
1534 assert_eq!(
1535 format!("{}", c.err().unwrap()),
1536 "invalid hex code format, \
1537 please use format \'#fff\', \'fff\', \'#ffffff\' or \'ffffff\'."
1538 );
1539 let c = Rgb::<Srgb, u8>::from_str("da0bce");
1540 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(218, 11, 206));
1541 let c = Rgb::<Srgb, u8>::from_str("f034e6");
1542 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(240, 52, 230));
1543 let c = Rgb::<Srgb, u8>::from_str("abc");
1544 assert_eq!(c.unwrap(), Rgb::<Srgb, u8>::new(170, 187, 204));
1545 let c = Rgba::<Srgb, u8>::from_str("#08ff");
1546 assert_eq!(c.unwrap(), Rgba::<Srgb, u8>::new(0, 136, 255, 255));
1547 let c = Rgba::<Srgb, u8>::from_str("08f0");
1548 assert_eq!(c.unwrap(), Rgba::<Srgb, u8>::new(0, 136, 255, 0));
1549 let c = Rgba::<Srgb, u8>::from_str("#da0bce80");
1550 assert_eq!(c.unwrap(), Rgba::<Srgb, u8>::new(218, 11, 206, 128));
1551 let c = Rgba::<Srgb, u8>::from_str("f034e680");
1552 assert_eq!(c.unwrap(), Rgba::<Srgb, u8>::new(240, 52, 230, 128));
1553 let c = Rgba::<Srgb, u8>::from_str("#ffffffff");
1554 assert_eq!(c.unwrap(), Rgba::<Srgb, u8>::new(255, 255, 255, 255));
1555 let c = Rgba::<Srgb, u8>::from_str("#ffff");
1556 assert_eq!(c.unwrap(), Rgba::<Srgb, u8>::new(255, 255, 255, 255));
1557 let c = Rgba::<Srgb, u8>::from_str("#gggggggg");
1558 assert!(c.is_err());
1559 assert_eq!(
1560 format!("{}", c.err().unwrap()),
1561 "invalid digit found in string"
1562 );
1563 let c = Rgba::<Srgb, u8>::from_str("#fff");
1564 assert_eq!(
1565 format!("{}", c.err().unwrap()),
1566 "invalid hex code format, \
1567 please use format '#ffff', 'ffff', '#ffffffff' or 'ffffffff'."
1568 );
1569 let c = Rgba::<Srgb, u8>::from_str("#ffffff");
1570 assert_eq!(
1571 format!("{}", c.err().unwrap()),
1572 "invalid hex code format, \
1573 please use format '#ffff', 'ffff', '#ffffffff' or 'ffffffff'."
1574 );
1575 }
1576
1577 #[test]
1578 fn check_min_max_components() {
1579 assert_eq!(Rgb::<Srgb, f32>::min_red(), 0.0);
1580 assert_eq!(Rgb::<Srgb, f32>::min_green(), 0.0);
1581 assert_eq!(Rgb::<Srgb, f32>::min_blue(), 0.0);
1582 assert_eq!(Rgb::<Srgb, f32>::max_red(), 1.0);
1583 assert_eq!(Rgb::<Srgb, f32>::max_green(), 1.0);
1584 assert_eq!(Rgb::<Srgb, f32>::max_blue(), 1.0);
1585 }
1586
1587 struct_of_arrays_tests!(
1588 Rgb<Srgb>[red, green, blue] phantom: standard,
1589 Rgba::new(0.1f32, 0.2, 0.3, 0.4),
1590 Rgba::new(0.2, 0.3, 0.4, 0.5),
1591 Rgba::new(0.3, 0.4, 0.5, 0.6)
1592 );
1593
1594 test_uniform_distribution! {
1595 Rgb<Srgb, f32> {
1596 red: (0.0, 1.0),
1597 green: (0.0, 1.0),
1598 blue: (0.0, 1.0)
1599 },
1600 min: Rgb::new(0.0f32, 0.0, 0.0),
1601 max: Rgb::new(1.0, 1.0, 1.0)
1602 }
1603}