diff --git a/src/images/buffer.rs b/src/images/buffer.rs index 006c97fbaa..abf0c653ff 100644 --- a/src/images/buffer.rs +++ b/src/images/buffer.rs @@ -989,6 +989,67 @@ where pub fn put_pixel(&mut self, x: u32, y: u32, pixel: P) { *self.get_pixel_mut(x, y) = pixel; } + + /// Crop this image in place, removing pixels outside of the bounding rectangle. + /// + /// This behaves similar to [`imageops::crop`](crate::imageops::crop) except no additional + /// allocation takes place. The width and height of this buffer are adjusted as part of the + /// operation. The selection is shrunk to the overlap with this image if it is out of bounds. + /// + /// The pixel buffer is *not* shrunk and will continue to occupy the same amount of memory as + /// before. See [`ImageBuffer::shrink_to_fit`] if the container is a [`Vec`]. + /// + /// # Examples + /// + /// ``` + /// use image::{RgbImage, math::Rect}; + /// + /// let mut img = RgbImage::new(128, 128); + /// img.put_pixel(64, 64, image::Rgb([255, 0, 0])); + /// + /// let selection = Rect::from_xy_ranges(64..128, 64..128); + /// img.crop_in_place(selection); + /// + /// assert_eq!(img.dimensions(), (64, 64)); + /// assert_eq!(img.get_pixel(0, 0), &image::Rgb([255, 0, 0])); + /// ``` + /// + /// Selections beyond the image bounds are clamped. + /// + /// ``` + /// use image::{RgbImage, math::Rect}; + /// + /// let mut img = RgbImage::new(32, 32); + /// let selection = Rect::from_xy_ranges(16..40, 16..24); + /// # use image::GenericImageView as _; + /// # assert_eq!(image::imageops::crop_imm(&img, selection).dimensions(), (16, 8)); + /// + /// img.crop_in_place(selection); + /// assert_eq!(img.dimensions(), (16, 8)); + /// ``` + pub fn crop_in_place(&mut self, selection: Rect) { + let selection = selection.crop_dimms(self); + // We're now running essentially `copy_within` with differing source and destination row + // pitches. The above ensures that the target row pitch is smaller than our current one and + // we copy to offset `0` so always all data backwards. + // + // Since `selection` describes a smaller layout than our own, all indices that are computed + // from the pitches as type `usize` are within the bounds of the type and the underlying + // storage and we assume them to be valid. (If the `DerefMut` is malicious you'll get + // panics at runtime but that is not our problem, violating the contract of the container + // type for channels). + let rowlen = (selection.width as usize) * usize::from(
::CHANNEL_COUNT);
+
+ for y in 0..selection.height {
+ let sy = selection.y + y;
+ let source = self.pixel_indices_unchecked(selection.x, sy).start;
+ let dst = y as usize * rowlen;
+ self.data.copy_within(source..source + rowlen, dst);
+ }
+
+ self.width = selection.width;
+ self.height = selection.height;
+ }
}
impl {
@@ -1419,6 +1480,27 @@ impl > {
self.into_raw()
}
+ /// Shrink the length and capacity of the data buffer to fit the image size.
+ ///
+ /// This is useful after shrinking an image in-place, e.g. via cropping, to free unused memory
+ /// or in case the image was created from a buffer with excess capacity.
+ ///
+ /// ```
+ /// use image::RgbImage;
+ ///
+ /// let data = vec![0u8; 10000];
+ /// // `from_raw` allows excess data
+ /// let mut img = RgbImage::from_raw(16, 16, data).unwrap();
+ /// img.shrink_to_fit();
+ ///
+ /// assert_eq!(img.into_vec().len(), 16 * 16 * 3);
+ /// ```
+ pub fn shrink_to_fit(&mut self) {
+ let need = self.inner_pixels().len();
+ self.data.truncate(need);
+ self.data.shrink_to_fit();
+ }
+
/// Transfer the meta data, not the pixel values.
///
/// This will reinterpret all the pixels.
diff --git a/src/images/dynimage.rs b/src/images/dynimage.rs
index aa0de3b395..cbcdc876d0 100644
--- a/src/images/dynimage.rs
+++ b/src/images/dynimage.rs
@@ -500,6 +500,15 @@ impl DynamicImage {
dynamic_map!(*self, ref p => imageops::crop_imm(p, selection).to_image())
}
+ /// Crop this image in place, removing pixels outside of the bounding rectangle.
+ ///
+ /// See [`ImageBuffer::crop_in_place`] for more details. This changes the image with its
+ /// current color type. The pixel buffer is *not* shrunk and will continue to occupy the same
+ /// amount of memory as before. See [`Self::shrink_to_fit`].
+ pub fn crop_in_place(&mut self, selection: Rect) {
+ dynamic_map!(self, ref mut p, p.crop_in_place(selection))
+ }
+
/// Return a reference to an 8bit RGB image
#[must_use]
pub fn as_rgb8(&self) -> Option<&RgbImage> {
@@ -715,6 +724,14 @@ impl DynamicImage {
)
}
+ /// Shrink the capacity of the underlying [`Vec`] buffer to fit its length.
+ ///
+ /// The data may have excess capacity or padding for a number of reasons, depending on how it
+ /// was created or from in-place manipulation such as [`Self::crop_in_place`].
+ pub fn shrink_to_fit(&mut self) {
+ dynamic_map!(self, ref mut p, p.shrink_to_fit());
+ }
+
/// Return this image's pixels as a byte vector. If the `ImageBuffer`
/// container is `Vec