1use core::marker::PhantomData;
7
8use crate::color::Color;
9use crate::encoding::{Encoded, Encoding, Linear};
10use crate::fixed::{Q0_16, Q4_28};
11use crate::space::{ColorSpace, Perceptual};
12
13pub const F32_MAX_ERR_LSB: u16 = 4;
22
23const U16_MAX_F: f32 = 65535.0;
24
25pub struct ColorF32<S: ColorSpace, E: Encoding> {
32 pub ch: [f32; 3],
34 _pd: PhantomData<fn() -> (S, E)>,
35}
36
37impl<S: ColorSpace, E: Encoding> Copy for ColorF32<S, E> {}
38
39impl<S: ColorSpace, E: Encoding> Clone for ColorF32<S, E> {
40 fn clone(&self) -> Self {
41 *self
42 }
43}
44
45impl<S: ColorSpace, E: Encoding> PartialEq for ColorF32<S, E> {
46 fn eq(&self, other: &Self) -> bool {
47 let [a0, a1, a2] = self.ch;
48 let [b0, b1, b2] = other.ch;
49 a0.to_bits() == b0.to_bits() && a1.to_bits() == b1.to_bits() && a2.to_bits() == b2.to_bits()
50 }
51}
52
53impl<S: ColorSpace, E: Encoding> Eq for ColorF32<S, E> {}
54
55impl<S: ColorSpace, E: Encoding> core::fmt::Debug for ColorF32<S, E> {
56 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
57 f.debug_struct("ColorF32").field("ch", &self.ch).finish()
58 }
59}
60
61impl<S: ColorSpace, E: Encoding> ColorF32<S, E> {
62 #[must_use]
64 pub const fn new(ch: [f32; 3]) -> Self {
65 Self {
66 ch,
67 _pd: PhantomData,
68 }
69 }
70
71 #[must_use]
73 pub fn to_color(self) -> Color<S, E> {
74 let [c0, c1, c2] = self.ch;
75 Color::new([unit_to_q0_16(c0), unit_to_q0_16(c1), unit_to_q0_16(c2)])
76 }
77}
78
79impl<S: ColorSpace> ColorF32<S, Linear> {
80 #[must_use]
82 pub fn lerp(self, other: Self, t: f32) -> Self {
83 let [a0, a1, a2] = self.ch;
84 let [b0, b1, b2] = other.ch;
85 Self::new([
86 lerp_unit(a0, b0, t),
87 lerp_unit(a1, b1, t),
88 lerp_unit(a2, b2, t),
89 ])
90 }
91}
92
93impl<S: ColorSpace + Perceptual> ColorF32<S, Encoded> {
94 #[must_use]
96 pub fn lerp(self, other: Self, t: f32) -> Self {
97 let [a0, a1, a2] = self.ch;
98 let [b0, b1, b2] = other.ch;
99 Self::new([
100 lerp_unit(a0, b0, t),
101 lerp_unit(a1, b1, t),
102 lerp_unit(a2, b2, t),
103 ])
104 }
105}
106
107impl<S: ColorSpace, E: Encoding> From<Color<S, E>> for ColorF32<S, E> {
108 fn from(color: Color<S, E>) -> Self {
109 color.to_f32()
110 }
111}
112
113impl<S: ColorSpace, E: Encoding> From<ColorF32<S, E>> for Color<S, E> {
114 fn from(color: ColorF32<S, E>) -> Self {
115 color.to_color()
116 }
117}
118
119#[must_use]
134#[allow(clippy::manual_clamp)] pub(crate) fn sat_unit(x: f32) -> f32 {
136 x.max(0.0).min(1.0) + 0.0
137}
138
139#[must_use]
143pub(crate) fn unit_to_q0_16(x: f32) -> Q0_16 {
144 if x.is_nan() || x <= 0.0 {
145 return Q0_16::ZERO;
146 }
147 if x >= 1.0 {
148 return Q0_16::ONE;
149 }
150 let scaled = x * U16_MAX_F;
151 if scaled >= U16_MAX_F {
152 return Q0_16::ONE;
153 }
154 let trunc = scaled as u32;
156 let frac = scaled - (trunc as f32);
157 let rounded = if frac >= 0.5 {
158 trunc.saturating_add(1)
159 } else {
160 trunc
161 };
162 if rounded >= u32::from(u16::MAX) {
163 Q0_16::ONE
164 } else {
165 Q0_16::from_raw(rounded as u16)
167 }
168}
169
170#[must_use]
172pub(crate) fn lerp_unit(a: f32, b: f32, t: f32) -> f32 {
173 let a = sat_unit(a);
174 let b = sat_unit(b);
175 let t = sat_unit(t);
176 if t <= 0.0 {
177 return a;
178 }
179 if t >= 1.0 {
180 return b;
181 }
182 sat_unit(a + (b - a) * t)
183}
184
185#[must_use]
187pub(crate) fn apply_row_f32(coefs: [Q4_28; 3], ch: [f32; 3]) -> f32 {
188 let [k0, k1, k2] = coefs;
189 let [c0, c1, c2] = ch;
190 let c0 = sat_unit(c0);
191 let c1 = sat_unit(c1);
192 let c2 = sat_unit(c2);
193 sat_unit(k0.to_f32() * c0 + k1.to_f32() * c1 + k2.to_f32() * c2)
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use crate::color::Color;
200 use crate::encoding::{Encoded, Linear};
201 use crate::space::Srgb;
202
203 #[test]
204 fn sat_unit_matches_the_comparison_chain_bit_for_bit() {
205 fn reference(x: f32) -> f32 {
208 if x.is_nan() || x <= 0.0 {
209 0.0
210 } else if x >= 1.0 {
211 1.0
212 } else {
213 x
214 }
215 }
216 for x in [
217 f32::NAN,
218 -f32::NAN,
219 f32::INFINITY,
220 f32::NEG_INFINITY,
221 -0.0,
222 0.0,
223 f32::MIN_POSITIVE,
224 -f32::MIN_POSITIVE,
225 1.0,
226 1.0 - f32::EPSILON,
227 1.0 + f32::EPSILON,
228 0.5,
229 -1.0,
230 1e30,
231 -1e30,
232 ] {
233 assert_eq!(
234 sat_unit(x).to_bits(),
235 reference(x).to_bits(),
236 "x={x:?} bits={:08x}",
237 x.to_bits()
238 );
239 }
240 }
241
242 #[test]
243 fn positive_infinity_saturates_high() {
244 assert_eq!(sat_unit(f32::INFINITY), 1.0);
245 assert_eq!(sat_unit(f32::NEG_INFINITY), 0.0);
246 assert_eq!(sat_unit(f32::NAN), 0.0);
247 assert_eq!(unit_to_q0_16(f32::INFINITY), Q0_16::ONE);
248 assert_eq!(unit_to_q0_16(f32::NEG_INFINITY), Q0_16::ZERO);
249 assert_eq!(unit_to_q0_16(f32::NAN), Q0_16::ZERO);
250 let lut = crate::interp::InterpLut::<17>::from_knots(
251 Q0_16::array_from_raw([
252 0, 4096, 8192, 12288, 16384, 20480, 24576, 28672, 32768, 36864, 40960, 45056,
253 49152, 53248, 57344, 61440, 65535,
254 ]),
255 0,
256 );
257 assert_eq!(lut.lookup_f32(f32::INFINITY), 1.0);
258 assert_eq!(lut.lookup_f32(f32::NEG_INFINITY), 0.0);
259 let one = Q4_28::ONE;
260 let zero = Q4_28::ZERO;
261 let m = crate::matrix::Matrix3::<Srgb, Srgb>::from_q428([
262 [one, zero, zero],
263 [zero, one, zero],
264 [zero, zero, one],
265 ]);
266 let mixed = ColorF32::<Srgb, Linear>::new([0.5, f32::NAN, 0.5]);
267 assert_eq!(m.apply_f32(mixed).ch, [0.5, 0.0, 0.5]);
268 }
269
270 #[test]
271 fn endpoints_round_trip_to_u16() {
272 let z = Color::<Srgb, Linear>::new(Q0_16::array_from_raw([0, 0, 0]));
273 let f = Color::<Srgb, Linear>::new(Q0_16::array_from_raw([65535, 65535, 65535]));
274 assert_eq!(ColorF32::from(z).to_color(), z);
275 assert_eq!(ColorF32::from(f).to_color(), f);
276 }
277
278 #[test]
279 fn lerp_endpoints() {
280 let a = ColorF32::<Srgb, Linear>::new([0.0, 0.0, 0.0]);
281 let b = ColorF32::<Srgb, Linear>::new([1.0, 0.5, 0.0]);
282 assert_eq!(a.lerp(b, 0.0), a);
283 assert_eq!(a.lerp(b, 1.0), b);
284 let hi = ColorF32::<Srgb, Linear>::new([1.0, 1.0, 1.0]);
285 let lo = ColorF32::<Srgb, Linear>::new([1e-8, 1e-8, 1e-8]);
286 assert_eq!(hi.lerp(lo, 1.0), lo);
287 let nan = ColorF32::<Srgb, Linear>::new([f32::NAN, f32::NAN, f32::NAN]);
288 let one = ColorF32::<Srgb, Linear>::new([1.0, 1.0, 1.0]);
289 assert_eq!(nan.lerp(one, 0.5).ch, [0.5, 0.5, 0.5]);
290 }
291
292 fn assert_send_sync<T: Send + Sync>() {}
293
294 #[test]
295 fn color_f32_is_send_sync() {
296 assert_send_sync::<ColorF32<Srgb, Linear>>();
297 assert_send_sync::<ColorF32<Srgb, Encoded>>();
298 }
299}