1use std::ops::Deref;
2
3use image::{
4 flat::{View, ViewMut},
5 DynamicImage, GenericImageView, ImageBuffer, Pixel,
6};
7
8pub use crate::{coordinate::ImageCoordinate, index::ImageAxisIndex};
9
10pub trait ExtendedImageView: GenericImageView {
15 #[inline]
17 fn edges(&self) -> (u32, u32) {
18 let (width, height) = self.dimensions();
19 (width - 1, height - 1)
20 }
21
22 #[inline]
24 fn within_bounds<C>(&self, coords: C) -> bool
25 where
26 C: ImageCoordinate,
27 {
28 coords
29 .image_coordinate()
30 .map(|(x, y)| self.in_bounds(x, y))
31 .unwrap_or(false)
32 }
33
34 #[inline]
36 fn get_pixel_at<C>(&self, coords: C) -> Option<Self::Pixel>
37 where
38 C: ImageCoordinate,
39 {
40 coords
41 .image_coordinate()
42 .filter(|(x, y)| self.in_bounds(*x, *y))
43 .map(|(x, y)| unsafe { self.unsafe_get_pixel(x, y) })
44 }
45
46 #[inline]
48 fn get_pixel_clamped<C>(&self, coords: C) -> Self::Pixel
49 where
50 C: ImageCoordinate,
51 {
52 let (right, bottom) = self.edges();
53 let (x, y) = coords.image_coordinate_clamped(right, bottom);
54 unsafe { self.unsafe_get_pixel(x, y) }
55 }
56}
57
58impl ExtendedImageView for DynamicImage {}
59impl<P: Pixel, Container> ExtendedImageView for ImageBuffer<P, Container> where
60 Container: Deref<Target = [P::Subpixel]>
61{
62}
63impl<P: Pixel, Buffer> ExtendedImageView for View<Buffer, P> where Buffer: AsRef<[P::Subpixel]> {}
64impl<P: Pixel, Buffer> ExtendedImageView for ViewMut<Buffer, P> where
65 Buffer: AsRef<[P::Subpixel]> + AsMut<[P::Subpixel]>
66{
67}
68
69#[cfg(test)]
70mod tests {
71 use image::{GrayImage, Luma};
72
73 use super::*;
74
75 #[test]
76 fn in_bounds_for_empty_image() {
77 let image = GrayImage::new(0, 0);
78 for (x, y) in [(0, 0), (-1, -1), (1, 1), (1, 0), (0, 1), (-1, 0), (0, -1)] {
79 assert!(!image.within_bounds((x, y)));
80 }
81 }
82
83 #[test]
84 fn in_bounds_for_non_empty_image() {
85 let image = GrayImage::new(1, 1);
86
87 assert!(image.within_bounds((0, 0)));
88 for (x, y) in [(-1, -1), (1, 1), (1, 0), (0, 1), (-1, 0), (0, -1)] {
89 assert!(!image.within_bounds((x, y)));
90 }
91 }
92
93 #[test]
94 fn lookup_pixel_for_empty_image() {
95 let image = GrayImage::new(0, 0);
96 for (x, y) in [(0, 0), (-1, -1), (1, 1), (1, 0), (0, 1), (-1, 0), (0, -1)] {
97 assert!(image.get_pixel_at((x, y)).is_none());
98 }
99 }
100
101 #[test]
102 fn lookup_pixel_for_non_empty_image() {
103 let image = GrayImage::from_pixel(1, 1, [255].into());
104
105 assert!(image.get_pixel_at((-1, -1)).is_none());
106 assert!(image.get_pixel_at((1, 1)).is_none());
107 assert!(image.get_pixel_at((0, 0)).is_some());
108 assert_eq!(
109 image.get_pixel_at((0, 0)),
110 image.get_pixel_checked(0, 0).copied()
111 );
112 }
113
114 #[test]
115 #[should_panic]
116 fn clamp_pixel_for_empty_image() {
117 let image = GrayImage::new(0, 0);
118 image.get_pixel_clamped((0, 0));
119 }
120
121 #[test]
122 fn clamp_pixel_for_non_empty_image() {
123 let image = GrayImage::from_vec(2, 2, vec![32, 64, 128, 255]).unwrap();
124 let (w, h) = (image.width() as i32, image.height() as i32);
125 let (b, r) = (h - 1, w - 1);
126
127 assert_eq!(&image.get_pixel_clamped((-1, -1)), image.get_pixel(0, 0));
129 assert_eq!(&image.get_pixel_clamped((0, -1)), image.get_pixel(0, 0));
130 assert_eq!(&image.get_pixel_clamped((-1, 0)), image.get_pixel(0, 0));
131
132 assert_eq!(&image.get_pixel_clamped((w, b)), image.get_pixel(1, 1));
134 assert_eq!(&image.get_pixel_clamped((w, b)), image.get_pixel(1, 1));
135 assert_eq!(&image.get_pixel_clamped((r, h)), image.get_pixel(1, 1));
136
137 assert_eq!(&image.get_pixel_clamped((w, 0)), image.get_pixel(1, 0));
139 assert_eq!(&image.get_pixel_clamped((r, -1)), image.get_pixel(1, 0));
140 assert_eq!(&image.get_pixel_clamped((w, -1)), image.get_pixel(1, 0));
141
142 assert_eq!(&image.get_pixel_clamped((-1, b)), image.get_pixel(0, 1));
144 assert_eq!(&image.get_pixel_clamped((-1, h)), image.get_pixel(0, 1));
145 assert_eq!(&image.get_pixel_clamped((0, h)), image.get_pixel(0, 1));
146
147 assert_eq!(&image.get_pixel_clamped((0, 0)), image.get_pixel(0, 0));
149 assert_eq!(&image.get_pixel_clamped((r, 0)), image.get_pixel(1, 0));
150 assert_eq!(&image.get_pixel_clamped((0, b)), image.get_pixel(0, 1));
151 assert_eq!(&image.get_pixel_clamped((r, b)), image.get_pixel(1, 1));
152 }
153
154 #[test]
155 fn view_from_flat_samples() {
156 use image::flat::FlatSamples;
157
158 let samples = vec![32u8, 64, 128, 255];
160
161 let flat_samples = FlatSamples {
163 samples,
164 layout: image::flat::SampleLayout {
165 channels: 1,
166 channel_stride: 1,
167 width: 2,
168 height: 2,
169 width_stride: 1,
170 height_stride: 2,
171 },
172 color_hint: None,
173 };
174
175 let view = flat_samples
177 .as_view::<Luma<u8>>()
178 .expect("Failed to create view from flat samples");
179
180 let (w, h) = (view.width() as i32, view.height() as i32);
181 let (b, r) = (h - 1, w - 1);
182
183 assert!(view.within_bounds((0, 0)));
185 assert!(view.within_bounds((1, 1)));
186 assert!(!view.within_bounds((-1, 0)));
187 assert!(!view.within_bounds((2, 0)));
188
189 assert!(view.get_pixel_at((0, 0)).is_some());
191 assert_eq!(view.get_pixel_at((0, 0)).unwrap(), Luma([32]));
192 assert_eq!(view.get_pixel_at((1, 0)).unwrap(), Luma([64]));
193 assert_eq!(view.get_pixel_at((0, 1)).unwrap(), Luma([128]));
194 assert_eq!(view.get_pixel_at((1, 1)).unwrap(), Luma([255]));
195 assert!(view.get_pixel_at((-1, -1)).is_none());
196 assert!(view.get_pixel_at((2, 2)).is_none());
197
198 assert_eq!(&view.get_pixel_clamped((-1, -1)), &Luma([32]));
201 assert_eq!(&view.get_pixel_clamped((0, -1)), &Luma([32]));
202 assert_eq!(&view.get_pixel_clamped((-1, 0)), &Luma([32]));
203
204 assert_eq!(&view.get_pixel_clamped((w, b)), &Luma([255]));
206 assert_eq!(&view.get_pixel_clamped((r, h)), &Luma([255]));
207
208 assert_eq!(&view.get_pixel_clamped((w, 0)), &Luma([64]));
210 assert_eq!(&view.get_pixel_clamped((r, -1)), &Luma([64]));
211 assert_eq!(&view.get_pixel_clamped((w, -1)), &Luma([64]));
212
213 assert_eq!(&view.get_pixel_clamped((-1, b)), &Luma([128]));
215 assert_eq!(&view.get_pixel_clamped((-1, h)), &Luma([128]));
216 assert_eq!(&view.get_pixel_clamped((0, h)), &Luma([128]));
217
218 assert_eq!(&view.get_pixel_clamped((0, 0)), &Luma([32]));
220 assert_eq!(&view.get_pixel_clamped((r, 0)), &Luma([64]));
221 assert_eq!(&view.get_pixel_clamped((0, b)), &Luma([128]));
222 assert_eq!(&view.get_pixel_clamped((r, b)), &Luma([255]));
223 }
224
225 #[test]
226 fn coordinate_trait_usage() {
227 let image = GrayImage::from_vec(2, 2, vec![32, 64, 128, 255]).unwrap();
228
229 let tuple_coord = (0i32, 1i32);
231 assert!(image.within_bounds(tuple_coord));
232 assert_eq!(image.get_pixel_at(tuple_coord).unwrap(), Luma([128]));
233
234 let array_coord = [1i32, 0i32];
236 assert!(image.within_bounds(array_coord));
237 assert_eq!(image.get_pixel_at(array_coord).unwrap(), Luma([64]));
238
239 let out_of_bounds_tuple = (-1i32, -1i32);
241 assert_eq!(&image.get_pixel_clamped(out_of_bounds_tuple), &Luma([32]));
242
243 let out_of_bounds_array = [5i32, 5i32];
244 assert_eq!(&image.get_pixel_clamped(out_of_bounds_array), &Luma([255]));
245 }
246
247 #[cfg(feature = "nalgebra")]
248 #[test]
249 fn nalgebra_point_usage() {
250 use nalgebra::Point2;
251
252 let image = GrayImage::from_vec(2, 2, vec![32, 64, 128, 255]).unwrap();
253
254 let point = Point2::new(0i32, 1i32);
255 assert!(image.within_bounds(*point));
256 assert_eq!(image.get_pixel_at(*point).unwrap(), Luma([128]));
257
258 let out_of_bounds_point = Point2::new(-1i32, -1i32);
259 assert!(!image.within_bounds(out_of_bounds_point));
260 assert_eq!(image.get_pixel_clamped(out_of_bounds_point), Luma([32]));
261 }
262}