use std::{marker::PhantomData, num::NonZeroU32}; use crate::Image; /// A smaller part of a larger image. /// /// ```text /// ┏━━━━━━━━━━━━━━┓ hard borders represent the full image /// ┃ 1 2 3 1 ┃ vvvv the top left of the new image /// ┃ ┌──────┐ ┃ crop(2, 2).from(1, 1) /// ┃ 4 │ 5 6 │ 2 ┃ ^^^^ width and height /// ┃ │ │ ┃ /// ┃ 7 │ 8 9 │ 3 ┃ /// ┗━━━┷━━━━━━┷━━━┛ soft borders represent the new image /// ``` #[derive(Clone)] pub struct SubImage { inner: Image, /// in pixels offset_x: u32, real_width: NonZeroU32, real_height: NonZeroU32, } /// Trait for cropping a image. pub trait Cropper { /// # Panics /// /// if w - y == 0 fn from(self, x: u32, y: u32) -> SubImage; } impl Copy for SubImage where Image: Copy {} macro_rules! def { ($t:ty, $($what:ident)?) => { struct Crop<'a, T, const C: usize> { dimensions: (NonZeroU32, NonZeroU32), _d: PhantomData>, image: Image<$t, C>, } impl<'a, T, const C: usize> Cropper<$t, C> for Crop<'a, T, C> { fn from(self, x: u32, y: u32) -> SubImage<$t, C> { let w = self.image.width(); // SAFETY: ctor let i = unsafe { Image::new( self.image.width, NonZeroU32::new(self.image.height() - y).unwrap(), &$($what)?(self.image.take_buffer()[(y as usize * C) * w as usize..]), ) }; SubImage { offset_x: x, inner: i, real_width: self.dimensions.0, real_height: self.dimensions.1, } } } }; } impl Image { /// Crop a image. /// /// The signature looks something like: `i.crop(width, height).from(top_left_x, top_left_y)`, which gives you a [SubImage]<&\[T\], _> /// /// If you want a owned image, `i.crop(w, h).from(x, y).own()` gets you a [`Image`]<[Box]<\[T\], _>> back. /// /// ``` /// # use fimg::{Image, Cropper}; /// let mut i = Image::<_, 1>::build(4, 3).buf([ /// 1, 2, 3, 1, /// 4, 5, 6, 2, /// 7, 8, 9, 3, /// ]); /// let c = i.crop(2, 2).from(1, 1); /// # unsafe { /// assert_eq!(c.pixel(0, 0), [5]); /// assert_eq!(c.pixel(1, 1), [9]); /// assert_eq!( /// c.own().bytes(), /// &[5, 6, /// 8, 9] /// ); /// # } /// ``` /// /// # Panics /// /// if width == 0 || height == 0 pub fn crop<'a, U: 'a>(&'a self, width: u32, height: u32) -> impl Cropper<&'a [U], C> where T: AsRef<[U]>, { def!(&'a [T],); Crop { dimensions: ( NonZeroU32::new(width).expect("Image::crop panics when width == 0"), NonZeroU32::new(height).expect("Image::crop panics when height == 0"), ), _d: PhantomData, image: self.as_ref(), } } /// Like [`Image::crop`], but returns a mutable [`SubImage`]. pub fn crop_mut<'a, U: 'a>( &'a mut self, width: u32, height: u32, ) -> impl Cropper<&'a mut [U], C> where T: AsMut<[U]> + AsRef<[U]>, { def!(&'a mut [T], mut); Crop { dimensions: ( NonZeroU32::new(width).expect("Image::crop panics when width == 0"), NonZeroU32::new(height).expect("Image::crop panics when height == 0"), ), _d: PhantomData, image: self.as_mut(), } } } impl SubImage<&[T], C> { /// Clones this [`SubImage`] into its own [`Image`] pub fn own(&self) -> Image, C> { let mut out = Vec::with_capacity( self.real_width.get() as usize * self.real_height.get() as usize * C, ); for row in self.inner.rows().take(self.real_height.get() as usize) { out.extend_from_slice( &row[self.offset_x as usize ..self.offset_x as usize + self.real_width.get() as usize] .as_flattened(), ); } // SAFETY: ctor unsafe { Image::new(self.real_width, self.real_height, out.into()) } } } // TODO crop() impl SubImage { /// Get a pixel. /// /// # Safety /// /// this pixel must be in bounds. pub unsafe fn pixel(&self, x: u32, y: u32) -> &[U; C] where W: AsRef<[U]>, { // note: if you get a pixel, in release mode, that is in bounds of the outer image, but not the sub image, that would be library-ub. debug_assert!(x < self.real_width.get()); debug_assert!(y < self.real_height.get()); // SAFETY: caller unsafe { self.inner.pixel(x + self.offset_x, y) } } /// Get a pixel, mutably. /// /// # Safety /// /// this pixel must be in bounds. pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [U] where W: AsMut<[U]> + AsRef<[U]>, { debug_assert!(x < self.real_width.get()); debug_assert!(y < self.real_height.get()); // SAFETY: caller unsafe { self.inner.pixel_mut(x, y) } } }