pixelutil_image/
index.rs

1/// Provides methods for converting a type to image axis index
2/// used for locating pixels in an image.
3pub trait ImageAxisIndex: Copy {
4    /// Converts the value to an image axis index, returning [`None`] if the conversion fails.
5    fn to_image_axis_index(self) -> Option<u32>;
6    /// Clamps the value to a valid image axis index within the bounds of the image corresponding axis.
7    /// Lower bound is always `0`, upper bound is `max`.
8    fn clamp_image_axis_index(self, max: u32) -> u32;
9}
10
11impl ImageAxisIndex for u32 {
12    #[inline]
13    fn to_image_axis_index(self) -> Option<u32> {
14        Some(self)
15    }
16    #[inline]
17    fn clamp_image_axis_index(self, max: u32) -> u32 {
18        self.min(max)
19    }
20}
21
22macro_rules! impl_pixel_index {
23    (as_) => {
24        #[inline]
25        fn to_image_axis_index(self) -> Option<u32> {
26            Some(self as u32)
27        }
28    };
29    (try_from) => {
30        #[inline]
31        fn to_image_axis_index(self) -> Option<u32> {
32            u32::try_from(self).ok()
33        }
34    };
35    (unsigned inbound $($t:ty),+) => {
36        $(
37            impl ImageAxisIndex for $t {
38                impl_pixel_index!(as_);
39                #[inline]
40                fn clamp_image_axis_index(self, max: u32) -> u32 {
41                    (self as u32).min(max)
42                }
43            }
44        )+
45    };
46    (unsigned $($t:ty),+) => {
47        $(
48            impl ImageAxisIndex for $t {
49                impl_pixel_index!(try_from);
50                #[inline]
51                fn clamp_image_axis_index(self, max: u32) -> u32 {
52                    u32::try_from(self).ok().unwrap_or(max)
53                }
54            }
55        )+
56    };
57    (signed inbound $($t:ty),+) => {
58        $(
59            impl ImageAxisIndex for $t {
60                impl_pixel_index!(try_from);
61                #[inline]
62                fn clamp_image_axis_index(self, max: u32) -> u32 {
63                    (self.max(0) as u32).min(max)
64                }
65            }
66        )+
67    };
68    (signed $($t:ty),+) => {
69        $(
70            impl ImageAxisIndex for $t {
71                impl_pixel_index!(try_from);
72                #[inline]
73                fn clamp_image_axis_index(self, max: u32) -> u32 {
74                    (self.max(0).min(max as $t)) as u32
75                }
76            }
77        )+
78    };
79
80    (float $($t:ty),+) => {
81        $(
82            impl ImageAxisIndex for $t {
83                #[inline]
84                fn to_image_axis_index(self) -> Option<u32> {
85                    (self.is_finite() && self.is_sign_positive())
86                        .then(|| unsafe { self.to_int_unchecked::<u32>() })
87                }
88                #[inline]
89                fn clamp_image_axis_index(self, max: u32) -> u32 {
90                    if self.is_finite() {
91                        self.is_sign_positive()
92                            .then_some(unsafe { self.to_int_unchecked::<u32>() }.min(max))
93                            .unwrap_or(0)
94                    } else if !self.is_nan() {
95                        self.is_sign_positive().then_some(max).unwrap_or(0)
96                    } else {
97                        0
98                    }
99                }
100            }
101        )+
102    };
103}
104
105#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
106impl_pixel_index!(unsigned inbound u8, u16);
107#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
108impl_pixel_index!(unsigned inbound u8, u16, usize);
109#[cfg(target_pointer_width = "32")]
110impl_pixel_index!(unsigned u128);
111#[cfg(target_pointer_width = "64")]
112impl_pixel_index!(unsigned usize, u128);
113#[cfg(target_pointer_width = "64")]
114impl_pixel_index!(signed inbound i8, i16, i32);
115#[cfg(target_pointer_width = "32")]
116impl_pixel_index!(signed inbound i8, i16, i32, isize);
117#[cfg(target_pointer_width = "64")]
118impl_pixel_index!(signed isize, i64, i128);
119#[cfg(target_pointer_width = "32")]
120impl_pixel_index!(signed i64, i128);
121
122impl_pixel_index!(float f32, f64);
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn pixel_index_u32() {
130        // to_image_axis_index
131        assert_eq!(0u32.to_image_axis_index(), Some(0));
132        assert_eq!(42u32.to_image_axis_index(), Some(42));
133        assert_eq!(u32::MAX.to_image_axis_index(), Some(u32::MAX));
134
135        // clamp_image_axis_index
136        assert_eq!(0u32.clamp_image_axis_index(100), 0);
137        assert_eq!(50u32.clamp_image_axis_index(100), 50);
138        assert_eq!(100u32.clamp_image_axis_index(100), 100);
139        assert_eq!(150u32.clamp_image_axis_index(100), 100);
140    }
141
142    #[test]
143    fn pixel_index_u8() {
144        // to_image_axis_index - u8 is unsigned inbound, uses as_ conversion
145        assert_eq!(0u8.to_image_axis_index(), Some(0));
146        assert_eq!(42u8.to_image_axis_index(), Some(42));
147        assert_eq!(255u8.to_image_axis_index(), Some(255));
148
149        // clamp_image_axis_index
150        assert_eq!(0u8.clamp_image_axis_index(100), 0);
151        assert_eq!(50u8.clamp_image_axis_index(100), 50);
152        assert_eq!(100u8.clamp_image_axis_index(100), 100);
153        assert_eq!(200u8.clamp_image_axis_index(100), 100);
154    }
155
156    #[test]
157    fn pixel_index_u16() {
158        // to_image_axis_index - u16 is unsigned inbound, uses as_ conversion
159        assert_eq!(0u16.to_image_axis_index(), Some(0));
160        assert_eq!(42u16.to_image_axis_index(), Some(42));
161        assert_eq!(u16::MAX.to_image_axis_index(), Some(u16::MAX as u32));
162
163        // clamp_image_axis_index
164        assert_eq!(0u16.clamp_image_axis_index(100), 0);
165        assert_eq!(50u16.clamp_image_axis_index(100), 50);
166        assert_eq!(100u16.clamp_image_axis_index(100), 100);
167        assert_eq!(150u16.clamp_image_axis_index(100), 100);
168    }
169
170    #[test]
171    fn pixel_index_usize() {
172        // to_image_axis_index - usize is unsigned, uses try_from conversion
173        assert_eq!(0usize.to_image_axis_index(), Some(0));
174        assert_eq!(42usize.to_image_axis_index(), Some(42));
175
176        // Test edge case where usize might be larger than u32::MAX
177        if std::mem::size_of::<usize>() > std::mem::size_of::<u32>() {
178            assert_eq!((u32::MAX as usize + 1).to_image_axis_index(), None);
179        }
180
181        // clamp_image_axis_index
182        assert_eq!(0usize.clamp_image_axis_index(100), 0);
183        assert_eq!(50usize.clamp_image_axis_index(100), 50);
184        assert_eq!(100usize.clamp_image_axis_index(100), 100);
185
186        // Test with large values that exceed u32::MAX
187        if std::mem::size_of::<usize>() > std::mem::size_of::<u32>() {
188            assert_eq!((u32::MAX as usize + 1).clamp_image_axis_index(100), 100);
189        }
190    }
191
192    #[test]
193    fn pixel_index_u128() {
194        // to_image_axis_index - u128 is unsigned, uses try_from conversion
195        assert_eq!(0u128.to_image_axis_index(), Some(0));
196        assert_eq!(42u128.to_image_axis_index(), Some(42));
197
198        // Test edge case where u128 is larger than u32::MAX
199        let large_value = u32::MAX as u128 + 1;
200        assert_eq!(large_value.to_image_axis_index(), None);
201
202        // clamp_image_axis_index
203        assert_eq!(0u128.clamp_image_axis_index(100), 0);
204        assert_eq!(50u128.clamp_image_axis_index(100), 50);
205        assert_eq!(100u128.clamp_image_axis_index(100), 100);
206
207        // Test with large values that exceed u32::MAX
208        let large_value = u32::MAX as u128 + 1;
209        assert_eq!(large_value.clamp_image_axis_index(100), 100);
210    }
211
212    #[test]
213    fn pixel_index_i8() {
214        // to_image_axis_index - i8 is signed inbound, uses try_from conversion
215        assert_eq!(0i8.to_image_axis_index(), Some(0));
216        assert_eq!(42i8.to_image_axis_index(), Some(42));
217        assert_eq!(127i8.to_image_axis_index(), Some(127));
218        assert_eq!((-1i8).to_image_axis_index(), None);
219        assert_eq!((-128i8).to_image_axis_index(), None);
220
221        // clamp_image_axis_index
222        assert_eq!(0i8.clamp_image_axis_index(100), 0);
223        assert_eq!(50i8.clamp_image_axis_index(100), 50);
224        assert_eq!(100i8.clamp_image_axis_index(200), 100);
225        assert_eq!(127i8.clamp_image_axis_index(100), 100);
226        assert_eq!((-1i8).clamp_image_axis_index(100), 0);
227        assert_eq!((-128i8).clamp_image_axis_index(100), 0);
228    }
229
230    #[test]
231    fn pixel_index_i16() {
232        // to_image_axis_index - i16 is signed inbound, uses try_from conversion
233        assert_eq!(0i16.to_image_axis_index(), Some(0));
234        assert_eq!(42i16.to_image_axis_index(), Some(42));
235        assert_eq!(32767i16.to_image_axis_index(), Some(32767));
236        assert_eq!((-1i16).to_image_axis_index(), None);
237        assert_eq!((-32768i16).to_image_axis_index(), None);
238
239        // clamp_image_axis_index
240        assert_eq!(0i16.clamp_image_axis_index(100), 0);
241        assert_eq!(50i16.clamp_image_axis_index(100), 50);
242        assert_eq!(100i16.clamp_image_axis_index(200), 100);
243        assert_eq!(150i16.clamp_image_axis_index(100), 100);
244        assert_eq!((-1i16).clamp_image_axis_index(100), 0);
245        assert_eq!((-32768i16).clamp_image_axis_index(100), 0);
246    }
247
248    #[test]
249    fn pixel_index_i32() {
250        // to_image_axis_index - i32 is signed inbound, uses try_from conversion
251        assert_eq!(0i32.to_image_axis_index(), Some(0));
252        assert_eq!(42i32.to_image_axis_index(), Some(42));
253        assert_eq!(2147483647i32.to_image_axis_index(), Some(2147483647));
254        assert_eq!((-1i32).to_image_axis_index(), None);
255        assert_eq!((-2147483648i32).to_image_axis_index(), None);
256
257        // clamp_image_axis_index
258        assert_eq!(0i32.clamp_image_axis_index(100), 0);
259        assert_eq!(50i32.clamp_image_axis_index(100), 50);
260        assert_eq!(100i32.clamp_image_axis_index(200), 100);
261        assert_eq!(150i32.clamp_image_axis_index(100), 100);
262        assert_eq!((-1i32).clamp_image_axis_index(100), 0);
263        assert_eq!((-2147483648i32).clamp_image_axis_index(100), 0);
264    }
265
266    #[test]
267    fn pixel_index_isize() {
268        // to_image_axis_index - isize is signed, uses try_from conversion
269        assert_eq!(0isize.to_image_axis_index(), Some(0));
270        assert_eq!(42isize.to_image_axis_index(), Some(42));
271        assert_eq!((-1isize).to_image_axis_index(), None);
272
273        // clamp_image_axis_index
274        assert_eq!(0isize.clamp_image_axis_index(100), 0);
275        assert_eq!(50isize.clamp_image_axis_index(100), 50);
276        assert_eq!(100isize.clamp_image_axis_index(200), 100);
277        assert_eq!((-1isize).clamp_image_axis_index(100), 0);
278
279        // Test edge cases for isize vs u32 compatibility
280        if std::mem::size_of::<isize>() > std::mem::size_of::<u32>() {
281            let large_positive = u32::MAX as isize + 1;
282            assert_eq!(large_positive.clamp_image_axis_index(100), 100);
283        }
284    }
285
286    #[test]
287    fn pixel_index_i64() {
288        // to_image_axis_index - i64 is signed, uses try_from conversion
289        assert_eq!(0i64.to_image_axis_index(), Some(0));
290        assert_eq!(42i64.to_image_axis_index(), Some(42));
291        assert_eq!((-1i64).to_image_axis_index(), None);
292
293        // Test edge case where i64 exceeds u32::MAX
294        let large_positive = u32::MAX as i64 + 1;
295        assert_eq!(large_positive.to_image_axis_index(), None);
296
297        // clamp_image_axis_index
298        assert_eq!(0i64.clamp_image_axis_index(100), 0);
299        assert_eq!(50i64.clamp_image_axis_index(100), 50);
300        assert_eq!(100i64.clamp_image_axis_index(200), 100);
301        assert_eq!((-1i64).clamp_image_axis_index(100), 0);
302
303        // Test with large values
304        let large_positive = u32::MAX as i64 + 1;
305        assert_eq!(large_positive.clamp_image_axis_index(100), 100);
306    }
307
308    #[test]
309    fn pixel_index_i128() {
310        // to_image_axis_index - i128 is signed, uses try_from conversion
311        assert_eq!(0i128.to_image_axis_index(), Some(0));
312        assert_eq!(42i128.to_image_axis_index(), Some(42));
313        assert_eq!((-1i128).to_image_axis_index(), None);
314
315        // Test edge case where i128 exceeds u32::MAX
316        let large_positive = u32::MAX as i128 + 1;
317        assert_eq!(large_positive.to_image_axis_index(), None);
318
319        // clamp_image_axis_index
320        assert_eq!(0i128.clamp_image_axis_index(100), 0);
321        assert_eq!(50i128.clamp_image_axis_index(100), 50);
322        assert_eq!(100i128.clamp_image_axis_index(200), 100);
323        assert_eq!((-1i128).clamp_image_axis_index(100), 0);
324
325        // Test with large values
326        let large_positive = u32::MAX as i128 + 1;
327        assert_eq!(large_positive.clamp_image_axis_index(100), 100);
328    }
329
330    #[test]
331    fn clamp_image_axis_index_edge_cases() {
332        assert_eq!(u32::MAX.clamp_image_axis_index(u32::MAX), u32::MAX);
333        assert_eq!(0u32.clamp_image_axis_index(u32::MAX), 0);
334        assert_eq!(100u32.clamp_image_axis_index(150), 100);
335        assert_eq!(200u32.clamp_image_axis_index(150), 150);
336        assert_eq!(0u32.clamp_image_axis_index(0), 0);
337        assert_eq!(100u32.clamp_image_axis_index(0), 0);
338        assert_eq!((-100i32).clamp_image_axis_index(0), 0);
339        assert_eq!((-100i32).clamp_image_axis_index(100), 0);
340        assert_eq!((-1000i64).clamp_image_axis_index(0), 0);
341        assert_eq!((-1000i64).clamp_image_axis_index(100), 0);
342    }
343
344    #[test]
345    fn pixel_index_f32() {
346        // to_image_axis_index - positive finite values
347        assert_eq!(0.0f32.to_image_axis_index(), Some(0));
348        assert_eq!(1.0f32.to_image_axis_index(), Some(1));
349        assert_eq!(42.7f32.to_image_axis_index(), Some(42));
350        assert_eq!(100.9f32.to_image_axis_index(), Some(100));
351        assert_eq!(999.0f32.to_image_axis_index(), Some(999));
352
353        // to_image_axis_index - negative values should return None
354        assert_eq!((-1.0f32).to_image_axis_index(), None);
355        assert_eq!((-42.5f32).to_image_axis_index(), None);
356        assert_eq!((-0.1f32).to_image_axis_index(), None);
357
358        // to_image_axis_index - special values
359        assert_eq!(f32::NAN.to_image_axis_index(), None);
360        assert_eq!(f32::INFINITY.to_image_axis_index(), None);
361        assert_eq!(f32::NEG_INFINITY.to_image_axis_index(), None);
362
363        // clamp_image_axis_index - positive finite values
364        assert_eq!(0.0f32.clamp_image_axis_index(100), 0);
365        assert_eq!(50.5f32.clamp_image_axis_index(100), 50);
366        assert_eq!(75.9f32.clamp_image_axis_index(100), 75);
367        assert_eq!(150.0f32.clamp_image_axis_index(100), 100);
368
369        // clamp_image_axis_index - negative values should return 0
370        assert_eq!((-1.0f32).clamp_image_axis_index(100), 0);
371        assert_eq!((-50.5f32).clamp_image_axis_index(100), 0);
372
373        // clamp_image_axis_index - special values
374        assert_eq!(f32::NAN.clamp_image_axis_index(100), 0);
375        assert_eq!(f32::INFINITY.clamp_image_axis_index(100), 100);
376        assert_eq!(f32::NEG_INFINITY.clamp_image_axis_index(100), 0);
377
378        // clamp_image_axis_index - edge cases with max = 0
379        assert_eq!(10.0f32.clamp_image_axis_index(0), 0);
380        assert_eq!((-10.0f32).clamp_image_axis_index(0), 0);
381        assert_eq!(f32::INFINITY.clamp_image_axis_index(0), 0);
382    }
383
384    #[test]
385    fn pixel_index_f64() {
386        // to_image_axis_index - positive finite values
387        assert_eq!(0.0f64.to_image_axis_index(), Some(0));
388        assert_eq!(1.0f64.to_image_axis_index(), Some(1));
389        assert_eq!(42.7f64.to_image_axis_index(), Some(42));
390        assert_eq!(100.9f64.to_image_axis_index(), Some(100));
391        assert_eq!(999.0f64.to_image_axis_index(), Some(999));
392        assert_eq!(1000000.0f64.to_image_axis_index(), Some(1000000));
393
394        // to_image_axis_index - negative values should return None
395        assert_eq!((-1.0f64).to_image_axis_index(), None);
396        assert_eq!((-42.5f64).to_image_axis_index(), None);
397        assert_eq!((-0.1f64).to_image_axis_index(), None);
398
399        // to_image_axis_index - special values
400        assert_eq!(f64::NAN.to_image_axis_index(), None);
401        assert_eq!(f64::INFINITY.to_image_axis_index(), None);
402        assert_eq!(f64::NEG_INFINITY.to_image_axis_index(), None);
403
404        // clamp_image_axis_index - positive finite values
405        assert_eq!(0.0f64.clamp_image_axis_index(100), 0);
406        assert_eq!(50.5f64.clamp_image_axis_index(100), 50);
407        assert_eq!(75.9f64.clamp_image_axis_index(100), 75);
408        assert_eq!(150.0f64.clamp_image_axis_index(100), 100);
409
410        // clamp_image_axis_index - negative values should return 0
411        assert_eq!((-1.0f64).clamp_image_axis_index(100), 0);
412        assert_eq!((-50.5f64).clamp_image_axis_index(100), 0);
413
414        // clamp_image_axis_index - special values
415        assert_eq!(f64::NAN.clamp_image_axis_index(100), 0);
416        assert_eq!(f64::INFINITY.clamp_image_axis_index(100), 100);
417        assert_eq!(f64::NEG_INFINITY.clamp_image_axis_index(100), 0);
418
419        // clamp_image_axis_index - edge cases with max = 0
420        assert_eq!(10.0f64.clamp_image_axis_index(0), 0);
421        assert_eq!((-10.0f64).clamp_image_axis_index(0), 0);
422        assert_eq!(f64::INFINITY.clamp_image_axis_index(0), 0);
423
424        // clamp_image_axis_index - large values
425        assert_eq!(5000000000.0f64.clamp_image_axis_index(100), 100);
426    }
427}