diff --git a/src/flat.rs b/src/flat.rs index d21a4f5eb0..4fd83ca5ef 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -1392,7 +1392,7 @@ where fn bounds(&self) -> (u32, u32, u32, u32) { let (w, h) = self.dimensions(); - (0, w, 0, h) + (0, 0, w, h) } fn in_bounds(&self, x: u32, y: u32) -> bool { diff --git a/src/image.rs b/src/image.rs index 7648850cf6..fa52c9c530 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1056,6 +1056,43 @@ pub trait GenericImageView { self.get_pixel(x, y) } + /// Returns the pixel located at (x, y) if the location is [`in_bounds`], + /// otherwise `None` is returned. + /// + /// Location coordinates can be negative. + /// + /// # Safety + /// + /// The [`in_bounds`] check must work as expected. + /// + /// [`in_bounds`]: #method.in_bounds + fn checked_get_pixel(&self, x: i64, y: i64) -> Option { + match (u32::try_from(x), u32::try_from(y)) { + (Ok(x), Ok(y)) => self + .in_bounds(x, y) + .then(|| unsafe { self.unsafe_get_pixel(x, y) }), + _ => None, + } + } + + /// Returns the pixel located at (x, y) if the location is [`in_bounds`], + /// otherwise the nearest bound pixel is returned. + /// + /// Location coordinates can be negative. + /// + /// [`in_bounds`]: #method.in_bounds + fn saturating_get_pixel(&self, x: i64, y: i64) -> Self::Pixel { + let (ix, iy, iw, ih) = self.bounds(); + + let x0 = ix as i64; + let x1 = (ix + iw - 1) as i64; + + let y0 = iy as i64; + let y1 = (iy + ih - 1) as i64; + + unsafe { self.unsafe_get_pixel(x.max(x0).min(x1) as u32, y.max(y0).min(y1) as u32) } + } + /// Returns an Iterator over the pixels of this image. /// The iterator yields the coordinates of each pixel /// along with their value @@ -1583,6 +1620,93 @@ mod tests { source.view(2, 2, 0, 0); } + #[test] + fn test_checked_get_pixel() { + let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255])); + + assert!(source.checked_get_pixel(0, 0).is_some()); + assert!(source.checked_get_pixel(2, 2).is_some()); + assert!(source.checked_get_pixel(0, 2).is_some()); + assert!(source.checked_get_pixel(2, 0).is_some()); + + assert!(source.checked_get_pixel(-1, -1).is_none()); + assert!(source.checked_get_pixel(3, 3).is_none()); + assert!(source.checked_get_pixel(0, 3).is_none()); + assert!(source.checked_get_pixel(3, 0).is_none()); + assert!(source.checked_get_pixel(-1, 0).is_none()); + assert!(source.checked_get_pixel(0, -1).is_none()); + } + + #[test] + fn test_saturating_get_pixel() { + #[rustfmt::skip] + let source = { + GrayImage::from_vec(3, 3, vec![ + 0u8, 1u8, 2u8, + 3u8, 4u8, 5u8, + 6u8, 7u8, 8u8 + ]).unwrap() + }; + + for x in 0..source.width() { + for y in 0..source.height() { + assert_eq!( + source.saturating_get_pixel(x as i64, y as i64).to_owned(), + *source.get_pixel(x, y) + ); + } + } + + assert_eq!( + source.saturating_get_pixel(-1, -1).to_owned(), + *source.get_pixel(0, 0) + ); + assert_eq!( + source.saturating_get_pixel(-1, 0).to_owned(), + *source.get_pixel(0, 0) + ); + assert_eq!( + source.saturating_get_pixel(0, -1).to_owned(), + *source.get_pixel(0, 0) + ); + assert_eq!( + source.saturating_get_pixel(3, 3).to_owned(), + *source.get_pixel(2, 2) + ); + assert_eq!( + source.saturating_get_pixel(3, 2).to_owned(), + *source.get_pixel(2, 2) + ); + assert_eq!( + source.saturating_get_pixel(2, 3).to_owned(), + *source.get_pixel(2, 2) + ); + assert_eq!( + source.saturating_get_pixel(3, -1).to_owned(), + *source.get_pixel(2, 0) + ); + assert_eq!( + source.saturating_get_pixel(2, -1).to_owned(), + *source.get_pixel(2, 0) + ); + assert_eq!( + source.saturating_get_pixel(3, 0).to_owned(), + *source.get_pixel(2, 0) + ); + assert_eq!( + source.saturating_get_pixel(-1, 2).to_owned(), + *source.get_pixel(0, 2) + ); + assert_eq!( + source.saturating_get_pixel(-1, 3).to_owned(), + *source.get_pixel(0, 2) + ); + assert_eq!( + source.saturating_get_pixel(0, 3).to_owned(), + *source.get_pixel(0, 2) + ); + } + #[test] fn test_copy_sub_image() { let source = ImageBuffer::from_pixel(3, 3, Rgba([255u8, 0, 0, 255]));