diff --git a/src/lib.rs b/src/lib.rs index 46dc7d6..dc99df2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ //! - [`Image::repeated`] //! - [`Image::overlay`](Overlay), [`Image::overlay_at`](OverlayAt), [`Image::overlay_blended`](BlendingOverlay) //! - [`Image::blur`] +//! - [`Image::crop`] //! //! ## feature flags //! @@ -70,12 +71,12 @@ missing_docs )] #![allow(clippy::zero_prefixed_literal, incomplete_features)] -use std::{num::NonZeroU32, slice::SliceIndex}; +use std::{num::NonZeroU32, ops::Range}; mod affine; #[cfg(feature = "blur")] mod blur; -#[doc(hidden)] +pub use sub::{Cropper, SubImage}; pub mod builder; #[doc(hidden)] pub mod cloner; @@ -85,6 +86,7 @@ mod r#dyn; pub(crate) mod math; mod overlay; mod pack; +mod sub; pub use pack::Pack; pub mod pixels; #[cfg(feature = "scale")] @@ -383,56 +385,69 @@ macro_rules! make { }; } -impl, const CHANNELS: usize> Image { +impl Image { /// The size of the underlying buffer. #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> usize { - self.bytes().len() - } - - /// Bytes of this image. - pub fn bytes(&self) -> &[u8] { - self.buffer.as_ref() + pub fn len(&self) -> usize + where + T: AsRef<[U]>, + { + self.buffer().as_ref().len() } /// # Safety /// /// the output index is not guranteed to be in bounds #[inline] - fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> { + fn slice(&self, x: u32, y: u32) -> Range + where + T: AsRef<[U]>, + { let index = self.at(x, y); debug_assert!(self.len() > index); // SAFETY: as long as the buffer isnt wrong, this is 😄 index..unsafe { index.unchecked_add(CHANNELS) } } - /// Procure a [`ImageCloner`]. - #[must_use = "function does not modify the original image"] - pub fn cloner(&self) -> ImageCloner<'_, CHANNELS> { - ImageCloner::from(self.as_ref()) - } - - /// Reference this image. - pub fn as_ref(&self) -> Image<&[u8], CHANNELS> { - // SAFETY: we got constructed okay, parameters must be valid - unsafe { Image::new(self.width, self.height, self.bytes()) } - } - #[inline] /// Returns a iterator over every pixel - pub fn chunked(&self) -> impl DoubleEndedIterator { + pub fn chunked<'a, U: 'a>(&'a self) -> impl DoubleEndedIterator + where + T: AsRef<[U]>, + { // SAFETY: 0 sized images illegal unsafe { assert_unchecked!(self.len() > CHANNELS) }; // SAFETY: no half pixels! unsafe { assert_unchecked!(self.len() % CHANNELS == 0) }; - self.bytes().array_chunks::() + self.buffer().as_ref().array_chunks::() } #[inline] /// Flatten the chunks of this image into a slice of slices. - pub fn flatten(&self) -> &[[u8; CHANNELS]] { + pub fn flatten(&self) -> &[[U; CHANNELS]] + where + T: AsRef<[U]>, + { // SAFETY: buffer cannot have half pixels - unsafe { self.bytes().as_chunks_unchecked::() } + unsafe { self.buffer().as_ref().as_chunks_unchecked::() } + } + + /// Create a mutref to this image + pub fn as_mut(&mut self) -> Image<&mut [U], CHANNELS> + where + T: AsMut<[U]>, + { + // SAFETY: construction went okay + unsafe { Image::new(self.width, self.height, self.buffer.as_mut()) } + } + + /// Reference this image. + pub fn as_ref(&self) -> Image<&[U], CHANNELS> + where + T: AsRef<[U]>, + { + // SAFETY: we got constructed okay, parameters must be valid + unsafe { Image::new(self.width, self.height, self.buffer().as_ref()) } } /// Return a pixel at (x, y). @@ -441,10 +456,13 @@ impl, const CHANNELS: usize> Image { /// - UB if x, y is out of bounds /// - UB if buffer is too small #[inline] - pub unsafe fn pixel(&self, x: u32, y: u32) -> [u8; CHANNELS] { + pub unsafe fn pixel(&self, x: u32, y: u32) -> [U; CHANNELS] + where + T: AsRef<[U]>, + { // SAFETY: x and y in bounds, slice is okay let ptr = unsafe { - self.buffer + self.buffer() .as_ref() .get_unchecked(self.slice(x, y)) .as_ptr() @@ -453,22 +471,38 @@ impl, const CHANNELS: usize> Image { // SAFETY: slice always returns a length of `CHANNELS`, so we `cast()` it for convenience. unsafe { *ptr } } -} -impl + AsRef<[u8]>, const CHANNELS: usize> Image { /// Return a mutable reference to a pixel at (x, y). /// # Safety /// /// - UB if x, y is out of bounds /// - UB if buffer is too small #[inline] - pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [u8] { + pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [U] + where + T: AsMut<[U]> + AsRef<[U]>, + { // SAFETY: we have been told x, y is in bounds. let idx = self.slice(x, y); // SAFETY: slice should always return a valid index unsafe { self.buffer.as_mut().get_unchecked_mut(idx) } } +} +impl, const CHANNELS: usize> Image { + /// Bytes of this image. + pub fn bytes(&self) -> &[u8] { + self.buffer.as_ref() + } + + /// Procure a [`ImageCloner`]. + #[must_use = "function does not modify the original image"] + pub fn cloner(&self) -> ImageCloner<'_, CHANNELS> { + ImageCloner::from(self.as_ref()) + } +} + +impl + AsRef<[u8]>, const CHANNELS: usize> Image { #[inline] /// Returns a iterator over every pixel, mutably pub fn chunked_mut(&mut self) -> impl Iterator { @@ -479,12 +513,6 @@ impl + AsRef<[u8]>, const CHANNELS: usize> Image { self.buffer.as_mut().array_chunks_mut::() } - /// Create a mutref to this image - pub fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> { - // SAFETY: construction went okay - unsafe { Image::new(self.width, self.height, self.buffer.as_mut()) } - } - #[inline] /// Flatten the chunks of this image into a mutable slice of slices. pub fn flatten_mut(&mut self) -> &mut [[u8; CHANNELS]] { diff --git a/src/sub.rs b/src/sub.rs new file mode 100644 index 0000000..f353e97 --- /dev/null +++ b/src/sub.rs @@ -0,0 +1,183 @@ +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.inner.height() as usize * C); + for row in self + .inner + .buffer + .chunks_exact(self.inner.width.get() as usize) + .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], + ); + } + // 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) } + } +}