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 ImageBuffer { @@ -1419,6 +1480,27 @@ impl ImageBuffer> { 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`, this operation is free. Otherwise, a copy /// is returned.