palette/encoding/
srgb.rs
1use crate::{
4 bool_mask::LazySelect,
5 encoding::{FromLinear, IntoLinear},
6 luma::LumaStandard,
7 num::{Arithmetics, MulAdd, MulSub, PartialCmp, Powf, Real},
8 rgb::{Primaries, RgbSpace, RgbStandard},
9 white_point::{Any, D65},
10 Mat3, Yxy,
11};
12
13use lookup_tables::*;
14
15mod lookup_tables;
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
33pub struct Srgb;
34
35impl<T: Real> Primaries<T> for Srgb {
36 fn red() -> Yxy<Any, T> {
37 Yxy::new(
38 T::from_f64(0.6400),
39 T::from_f64(0.3300),
40 T::from_f64(0.212656),
41 )
42 }
43 fn green() -> Yxy<Any, T> {
44 Yxy::new(
45 T::from_f64(0.3000),
46 T::from_f64(0.6000),
47 T::from_f64(0.715158),
48 )
49 }
50 fn blue() -> Yxy<Any, T> {
51 Yxy::new(
52 T::from_f64(0.1500),
53 T::from_f64(0.0600),
54 T::from_f64(0.072186),
55 )
56 }
57}
58
59impl RgbSpace for Srgb {
60 type Primaries = Srgb;
61 type WhitePoint = D65;
62
63 #[rustfmt::skip]
64 #[inline(always)]
65 fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
66 Some([
68 0.4124564, 0.3575761, 0.1804375,
69 0.2126729, 0.7151522, 0.0721750,
70 0.0193339, 0.1191920, 0.9503041,
71 ])
72 }
73
74 #[rustfmt::skip]
75 #[inline(always)]
76 fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
77 Some([
79 3.2404542, -1.5371385, -0.4985314,
80 -0.9692660, 1.8760108, 0.0415560,
81 0.0556434, -0.2040259, 1.0572252,
82 ])
83 }
84}
85
86impl RgbStandard for Srgb {
87 type Space = Srgb;
88 type TransferFn = Srgb;
89}
90
91impl LumaStandard for Srgb {
92 type WhitePoint = D65;
93 type TransferFn = Srgb;
94}
95
96impl<T> IntoLinear<T, T> for Srgb
97where
98 T: Real + Powf + MulAdd + Arithmetics + PartialCmp + Clone,
99 T::Mask: LazySelect<T>,
100{
101 #[inline]
102 fn into_linear(x: T) -> T {
103 lazy_select! {
105 if x.lt_eq(&T::from_f64(0.04045)) => T::from_f64(1.0 / 12.92) * &x,
106 else => x.clone().mul_add(T::from_f64(1.0 / 1.055), T::from_f64(0.055 / 1.055)).powf(T::from_f64(2.4)),
107 }
108 }
109}
110
111impl<T> FromLinear<T, T> for Srgb
112where
113 T: Real + Powf + MulSub + Arithmetics + PartialCmp + Clone,
114 T::Mask: LazySelect<T>,
115{
116 #[inline]
117 fn from_linear(x: T) -> T {
118 lazy_select! {
119 if x.lt_eq(&T::from_f64(0.0031308)) => T::from_f64(12.92) * &x,
120 else => x.clone().powf(T::from_f64(1.0 / 2.4)).mul_sub(T::from_f64(1.055), T::from_f64(0.055)),
121 }
122 }
123}
124
125impl IntoLinear<f32, u8> for Srgb {
126 #[inline]
127 fn into_linear(encoded: u8) -> f32 {
128 fast_srgb8::srgb8_to_f32(encoded)
129 }
130}
131
132impl FromLinear<f32, u8> for Srgb {
133 #[inline]
134 fn from_linear(linear: f32) -> u8 {
135 fast_srgb8::f32_to_srgb8(linear)
136 }
137}
138
139impl IntoLinear<f64, u8> for Srgb {
140 #[inline]
141 fn into_linear(encoded: u8) -> f64 {
142 SRGB_U8_TO_F64[encoded as usize]
143 }
144}
145
146impl FromLinear<f64, u8> for Srgb {
147 #[inline]
148 fn from_linear(linear: f64) -> u8 {
149 Srgb::from_linear(linear as f32)
150 }
151}
152
153#[cfg(test)]
154mod test {
155 use crate::encoding::{FromLinear, IntoLinear, Srgb};
156
157 #[cfg(feature = "approx")]
158 mod conversion {
159 use crate::{
160 encoding::Srgb,
161 matrix::{matrix_inverse, rgb_to_xyz_matrix},
162 rgb::RgbSpace,
163 };
164
165 #[test]
166 fn rgb_to_xyz() {
167 let dynamic = rgb_to_xyz_matrix::<Srgb, f64>();
168 let constant = Srgb::rgb_to_xyz_matrix().unwrap();
169 assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
170 }
171
172 #[test]
173 fn xyz_to_rgb() {
174 let dynamic = matrix_inverse(rgb_to_xyz_matrix::<Srgb, f64>());
175 let constant = Srgb::xyz_to_rgb_matrix().unwrap();
176 assert_relative_eq!(dynamic[..], constant[..], epsilon = 0.0000001);
177 }
178 }
179
180 #[test]
181 fn u8_to_f32_to_u8() {
182 for expected in 0u8..=255u8 {
183 let linear: f32 = Srgb::into_linear(expected);
184 let result: u8 = Srgb::from_linear(linear);
185 assert_eq!(result, expected);
186 }
187 }
188
189 #[test]
190 fn u8_to_f64_to_u8() {
191 for expected in 0u8..=255u8 {
192 let linear: f64 = Srgb::into_linear(expected);
193 let result: u8 = Srgb::from_linear(linear);
194 assert_eq!(result, expected);
195 }
196 }
197}