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}