1use crate::{
4    clamp,
5    num::{One, Real, Round, Zero},
6};
7
8pub trait Stimulus: Zero {
30    #[must_use]
35    fn max_intensity() -> Self;
36}
37
38impl<T> Stimulus for T
39where
40    T: Real + One + Zero,
41{
42    #[inline]
43    fn max_intensity() -> Self {
44        Self::one()
45    }
46}
47
48macro_rules! impl_uint_components {
49    ($($ty: ident),+) => {
50        $(
51            impl Stimulus for $ty {
52                #[inline]
53                fn max_intensity() -> Self {
54                    $ty::MAX
55                }
56            }
57        )*
58    };
59}
60
61impl_uint_components!(u8, u16, u32, u64, u128);
62
63pub trait StimulusColor {}
67
68pub trait FromStimulus<T> {
79    #[must_use]
82    fn from_stimulus(other: T) -> Self;
83}
84
85impl<T, U: IntoStimulus<T>> FromStimulus<U> for T {
86    #[inline]
87    fn from_stimulus(other: U) -> T {
88        other.into_stimulus()
89    }
90}
91
92pub trait IntoStimulus<T> {
103    #[must_use]
106    fn into_stimulus(self) -> T;
107}
108
109impl<T> IntoStimulus<T> for T {
110    #[inline]
111    fn into_stimulus(self) -> T {
112        self
113    }
114}
115
116const C23: u32 = 0x4b00_0000;
119const C52: u64 = 0x4330_0000_0000_0000;
120
121macro_rules! convert_float_to_uint {
132    ($float: ident; direct ($($direct_target: ident),+); $(via $temporary: ident ($($target: ident),+);)*) => {
133        $(
134            impl IntoStimulus<$direct_target> for $float {
135                #[inline]
136                fn into_stimulus(self) -> $direct_target {
137                    let max = $direct_target::max_intensity() as $float;
138                    let scaled = (self * max).min(max);
139                    let f = scaled + f32::from_bits(C23);
140                    (f.to_bits().saturating_sub(C23)) as $direct_target
141                }
142            }
143        )+
144
145        $(
146            $(
147                impl IntoStimulus<$target> for $float {
148                    #[inline]
149                    fn into_stimulus(self) -> $target {
150                        let max = $target::max_intensity() as $temporary;
151                        let scaled = (self as $temporary * max).min(max);
152                        let f = scaled + f64::from_bits(C52);
153                        (f.to_bits().saturating_sub(C52)) as  $target
154                    }
155                }
156            )+
157        )*
158    };
159}
160
161macro_rules! convert_double_to_uint {
164    ($double: ident; direct ($($direct_target: ident),+);) => {
165        $(
166            impl IntoStimulus<$direct_target> for $double {
167                #[inline]
168                fn into_stimulus(self) -> $direct_target {
169                    let max = $direct_target::max_intensity() as $double;
170                    let scaled = (self * max).min(max);
171                    let f = scaled + f64::from_bits(C52);
172                    (f.to_bits().saturating_sub(C52)) as $direct_target
173                }
174            }
175        )+
176    };
177}
178
179impl IntoStimulus<f32> for u8 {
184    #[inline]
185    fn into_stimulus(self) -> f32 {
186        let comp_u = u32::from(self) + C23;
187        let comp_f = f32::from_bits(comp_u) - f32::from_bits(C23);
188        let max_u = u32::from(u8::MAX) + C23;
189        let max_f = (f32::from_bits(max_u) - f32::from_bits(C23)).recip();
190        comp_f * max_f
191    }
192}
193
194impl IntoStimulus<f64> for u8 {
196    #[inline]
197    fn into_stimulus(self) -> f64 {
198        let comp_u = u64::from(self) + C52;
199        let comp_f = f64::from_bits(comp_u) - f64::from_bits(C52);
200        let max_u = u64::from(u8::MAX) + C52;
201        let max_f = (f64::from_bits(max_u) - f64::from_bits(C52)).recip();
202        comp_f * max_f
203    }
204}
205
206macro_rules! convert_uint_to_float {
207    ($uint: ident; $(via $temporary: ident ($($target: ident),+);)*) => {
208        $(
209            $(
210                impl IntoStimulus<$target> for $uint {
211                    #[inline]
212                    fn into_stimulus(self) -> $target {
213                        let max = $uint::max_intensity() as $temporary;
214                        let scaled = self as $temporary / max;
215                        scaled as $target
216                    }
217                }
218            )+
219        )*
220    };
221}
222
223macro_rules! convert_uint_to_uint {
224    ($uint: ident; $(via $temporary: ident ($($target: ident),+);)*) => {
225        $(
226            $(
227                impl IntoStimulus<$target> for $uint {
228                    #[inline]
229                    fn into_stimulus(self) -> $target {
230                        let target_max = $target::max_intensity() as $temporary;
231                        let own_max = $uint::max_intensity() as $temporary;
232                        let scaled = (self as $temporary / own_max) * target_max;
233                        clamp(Round::round(scaled), 0.0, target_max) as $target
234                    }
235                }
236            )+
237        )*
238    };
239}
240
241impl IntoStimulus<f64> for f32 {
242    #[inline]
243    fn into_stimulus(self) -> f64 {
244        f64::from(self)
245    }
246}
247convert_float_to_uint!(f32; direct (u8, u16); via f64 (u32, u64, u128););
248
249impl IntoStimulus<f32> for f64 {
250    #[inline]
251    fn into_stimulus(self) -> f32 {
252        self as f32
253    }
254}
255convert_double_to_uint!(f64; direct (u8, u16, u32, u64, u128););
256
257convert_uint_to_uint!(u8; via f32 (u16); via f64 (u32, u64, u128););
258
259convert_uint_to_float!(u16; via f32 (f32); via f64 (f64););
260convert_uint_to_uint!(u16; via f32 (u8); via f64 (u32, u64, u128););
261
262convert_uint_to_float!(u32; via f64 (f32, f64););
263convert_uint_to_uint!(u32; via f64 (u8, u16, u64, u128););
264
265convert_uint_to_float!(u64; via f64 (f32, f64););
266convert_uint_to_uint!(u64; via f64 (u8, u16, u32, u128););
267
268convert_uint_to_float!(u128; via f64 (f32, f64););
269convert_uint_to_uint!(u128; via f64 (u8, u16, u32, u64););
270
271#[cfg(test)]
272mod test {
273    use crate::stimulus::IntoStimulus;
274
275    #[test]
276    fn float_to_uint() {
277        let data = vec![
278            -800.0,
279            -0.3,
280            0.0,
281            0.005,
282            0.024983,
283            0.01,
284            0.15,
285            0.3,
286            0.5,
287            0.6,
288            0.7,
289            0.8,
290            0.8444,
291            0.9,
292            0.955,
293            0.999,
294            1.0,
295            1.4,
296            f32::from_bits(0x4b44_0000),
297            core::f32::MAX,
298            core::f32::MIN,
299            core::f32::NAN,
300            core::f32::INFINITY,
301            core::f32::NEG_INFINITY,
302        ];
303
304        let expected = vec![
305            0u8, 0, 0, 1, 6, 3, 38, 76, 128, 153, 178, 204, 215, 230, 244, 255, 255, 255, 255, 255,
306            0, 255, 255, 0,
307        ];
308
309        for (d, e) in data.into_iter().zip(expected) {
310            assert_eq!(IntoStimulus::<u8>::into_stimulus(d), e);
311        }
312    }
313
314    #[test]
315    fn double_to_uint() {
316        let data = vec![
317            -800.0,
318            -0.3,
319            0.0,
320            0.005,
321            0.024983,
322            0.01,
323            0.15,
324            0.3,
325            0.5,
326            0.6,
327            0.7,
328            0.8,
329            0.8444,
330            0.9,
331            0.955,
332            0.999,
333            1.0,
334            1.4,
335            f64::from_bits(0x4334_0000_0000_0000),
336            core::f64::MAX,
337            core::f64::MIN,
338            core::f64::NAN,
339            core::f64::INFINITY,
340            core::f64::NEG_INFINITY,
341        ];
342
343        let expected = vec![
344            0u8, 0, 0, 1, 6, 3, 38, 76, 128, 153, 178, 204, 215, 230, 244, 255, 255, 255, 255, 255,
345            0, 255, 255, 0,
346        ];
347
348        for (d, e) in data.into_iter().zip(expected) {
349            assert_eq!(IntoStimulus::<u8>::into_stimulus(d), e);
350        }
351    }
352
353    #[cfg(feature = "approx")]
354    #[test]
355    fn uint_to_float() {
356        fn into_stimulus_old(n: u8) -> f32 {
357            let max = u8::MAX as f32;
358            n as f32 / max
359        }
360
361        for n in (0..=255).step_by(5) {
362            assert_relative_eq!(IntoStimulus::<f32>::into_stimulus(n), into_stimulus_old(n))
363        }
364    }
365
366    #[cfg(feature = "approx")]
367    #[test]
368    fn uint_to_double() {
369        fn into_stimulus_old(n: u8) -> f64 {
370            let max = u8::MAX as f64;
371            n as f64 / max
372        }
373
374        for n in (0..=255).step_by(5) {
375            assert_relative_eq!(IntoStimulus::<f64>::into_stimulus(n), into_stimulus_old(n))
376        }
377    }
378}