Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/images/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,64 @@ 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.
///
/// # 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(<P as Pixel>::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;
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: we can't shrink the container here as this is generic over it. We could do it in DynamicImage on top of the original transform to avoid issues as rust-lang/rust#120091 even though additional conditions may be necessary for the behavior to be considered particular surprising (i.e. the linked issues is because an internal use of vec reserve/truncate leaves it with a lot of capacity, an analogy would thus be an operation that internally crops, but for instance ImageReader #2672 could truncate itself quite well).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's at least introduce the necessary method for this in the same patch.

}

impl<P: Pixel, Container> ImageBuffer<P, Container> {
Expand Down
8 changes: 8 additions & 0 deletions src/images/dynimage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,14 @@ 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.
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> {
Expand Down
Loading