pixelutil_image/
lib.rs

1use image::GenericImageView;
2
3/// Returns `true` if the given coordinates are within the bounds of the image.
4#[inline]
5pub fn in_bounds<I: GenericImageView>(image: &I, x: i32, y: i32) -> bool {
6    x >= 0 && y >= 0 && x < image.width() as i32 && y < image.height() as i32
7}
8
9/// Returns the pixel at the given coordinates if it is within the bounds of the image.
10#[inline]
11pub fn get_pixel<I: GenericImageView>(image: &I, x: i32, y: i32) -> Option<I::Pixel> {
12    in_bounds(image, x, y).then(|| unsafe { image.unsafe_get_pixel(x as u32, y as u32) })
13}
14
15/// Returns the pixel at the given coordinates, clamping the coordinates to the image bounds.
16#[inline]
17pub fn clamp_pixel<I: GenericImageView>(image: &I, x: i32, y: i32) -> I::Pixel {
18    unsafe {
19        image.unsafe_get_pixel(
20            x.clamp(0, image.width() as i32 - 1) as u32,
21            y.clamp(0, image.height() as i32 - 1) as u32,
22        )
23    }
24}
25
26/// Returns the pixel at the given coordinates, without checking for empty image.
27#[inline]
28pub unsafe fn clamp_pixel_unchecked<I: GenericImageView>(image: &I, x: i32, y: i32) -> I::Pixel {
29    image.unsafe_get_pixel(
30        (x.max(0) as u32).min(image.width() - 1),
31        (y.max(0) as u32).min(image.height() - 1),
32    )
33}
34
35#[cfg(test)]
36mod tests {
37    use image::GrayImage;
38
39    use super::*;
40
41    #[test]
42    fn in_bounds_for_empty_image() {
43        let image = GrayImage::new(0, 0);
44        for (x, y) in [(0, 0), (-1, -1), (1, 1), (1, 0), (0, 1), (-1, 0), (0, -1)] {
45            assert!(!in_bounds(&image, x, y));
46        }
47    }
48
49    #[test]
50    fn in_bounds_for_non_empty_image() {
51        let image = GrayImage::new(1, 1);
52
53        assert!(in_bounds(&image, 0, 0));
54        for (x, y) in [(-1, -1), (1, 1), (1, 0), (0, 1), (-1, 0), (0, -1)] {
55            assert!(!in_bounds(&image, x, y));
56        }
57    }
58
59    #[test]
60    fn lookup_pixel_for_empty_image() {
61        let image = GrayImage::new(0, 0);
62        for (x, y) in [(0, 0), (-1, -1), (1, 1), (1, 0), (0, 1), (-1, 0), (0, -1)] {
63            assert!(get_pixel(&image, x, y).is_none());
64        }
65    }
66
67    #[test]
68    fn lookup_pixel_for_non_empty_image() {
69        let image = GrayImage::from_pixel(1, 1, [255].into());
70
71        assert!(get_pixel(&image, -1, -1).is_none());
72        assert!(get_pixel(&image, 1, 1).is_none());
73        assert!(get_pixel(&image, 0, 0).is_some());
74        assert_eq!(
75            get_pixel(&image, 0, 0),
76            image.get_pixel_checked(0, 0).copied()
77        );
78    }
79
80    #[test]
81    #[should_panic]
82    fn clamp_pixel_for_empty_image() {
83        let image = GrayImage::new(0, 0);
84        clamp_pixel(&image, 0, 0);
85    }
86
87    #[test]
88    fn clamp_pixel_for_non_empty_image() {
89        let image = GrayImage::from_vec(2, 2, vec![32, 64, 128, 255]).unwrap();
90        let (w, h) = (image.width() as i32, image.height() as i32);
91        let (b, r) = (h - 1, w - 1);
92
93        // near top-left corner
94        assert_eq!(&clamp_pixel(&image, -1, -1), image.get_pixel(0, 0));
95        assert_eq!(&clamp_pixel(&image, 0, -1), image.get_pixel(0, 0));
96        assert_eq!(&clamp_pixel(&image, -1, 0), image.get_pixel(0, 0));
97
98        // near bottom-right corner
99        assert_eq!(&clamp_pixel(&image, w, b), image.get_pixel(1, 1));
100        assert_eq!(&clamp_pixel(&image, w, b), image.get_pixel(1, 1));
101        assert_eq!(&clamp_pixel(&image, r, h), image.get_pixel(1, 1));
102
103        // near top-right corner
104        assert_eq!(&clamp_pixel(&image, w, 0), image.get_pixel(1, 0));
105        assert_eq!(&clamp_pixel(&image, r, -1), image.get_pixel(1, 0));
106        assert_eq!(&clamp_pixel(&image, w, -1), image.get_pixel(1, 0));
107
108        // near bottom-left corner
109        assert_eq!(&clamp_pixel(&image, -1, b), image.get_pixel(0, 1));
110        assert_eq!(&clamp_pixel(&image, -1, h), image.get_pixel(0, 1));
111        assert_eq!(&clamp_pixel(&image, 0, h), image.get_pixel(0, 1));
112
113        // corners of the image
114        assert_eq!(&clamp_pixel(&image, 0, 0), image.get_pixel(0, 0));
115        assert_eq!(&clamp_pixel(&image, r, 0), image.get_pixel(1, 0));
116        assert_eq!(&clamp_pixel(&image, 0, b), image.get_pixel(0, 1));
117        assert_eq!(&clamp_pixel(&image, r, b), image.get_pixel(1, 1));
118    }
119
120    #[test]
121    fn clamp_pixel_for_non_empty_image_unsafe() {
122        let image = GrayImage::from_vec(2, 2, vec![32, 64, 128, 255]).unwrap();
123        let (w, h) = (image.width() as i32, image.height() as i32);
124        let (b, r) = (h - 1, w - 1);
125
126        unsafe {
127            // near top-left corner
128            assert_eq!(
129                &clamp_pixel_unchecked(&image, -1, -1),
130                image.get_pixel(0, 0)
131            );
132            assert_eq!(&clamp_pixel_unchecked(&image, 0, -1), image.get_pixel(0, 0));
133            assert_eq!(&clamp_pixel_unchecked(&image, -1, 0), image.get_pixel(0, 0));
134
135            // near bottom-right corner
136            assert_eq!(&clamp_pixel_unchecked(&image, w, b), image.get_pixel(1, 1));
137            assert_eq!(&clamp_pixel_unchecked(&image, w, b), image.get_pixel(1, 1));
138            assert_eq!(&clamp_pixel_unchecked(&image, r, h), image.get_pixel(1, 1));
139
140            // near top-right corner
141            assert_eq!(&clamp_pixel_unchecked(&image, w, 0), image.get_pixel(1, 0));
142            assert_eq!(&clamp_pixel_unchecked(&image, r, -1), image.get_pixel(1, 0));
143            assert_eq!(&clamp_pixel_unchecked(&image, w, -1), image.get_pixel(1, 0));
144
145            // near bottom-left corner
146            assert_eq!(&clamp_pixel_unchecked(&image, -1, b), image.get_pixel(0, 1));
147            assert_eq!(&clamp_pixel_unchecked(&image, -1, h), image.get_pixel(0, 1));
148            assert_eq!(&clamp_pixel_unchecked(&image, 0, h), image.get_pixel(0, 1));
149
150            // corners of the image
151            assert_eq!(&clamp_pixel_unchecked(&image, 0, 0), image.get_pixel(0, 0));
152            assert_eq!(&clamp_pixel_unchecked(&image, r, 0), image.get_pixel(1, 0));
153            assert_eq!(&clamp_pixel_unchecked(&image, 0, b), image.get_pixel(0, 1));
154            assert_eq!(&clamp_pixel_unchecked(&image, r, b), image.get_pixel(1, 1));
155        }
156    }
157}