fast image operations
-rw-r--r--src/lib.rs104
-rw-r--r--src/sub.rs183
2 files changed, 249 insertions, 38 deletions
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<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
+impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
/// 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<U>(&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<U>(&self, x: u32, y: u32) -> Range<usize>
+ 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<Item = &[u8; CHANNELS]> {
+ pub fn chunked<'a, U: 'a>(&'a self) -> impl DoubleEndedIterator<Item = &'a [U; CHANNELS]>
+ 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::<CHANNELS>()
+ self.buffer().as_ref().array_chunks::<CHANNELS>()
}
#[inline]
/// Flatten the chunks of this image into a slice of slices.
- pub fn flatten(&self) -> &[[u8; CHANNELS]] {
+ pub fn flatten<U>(&self) -> &[[U; CHANNELS]]
+ where
+ T: AsRef<[U]>,
+ {
// SAFETY: buffer cannot have half pixels
- unsafe { self.bytes().as_chunks_unchecked::<CHANNELS>() }
+ unsafe { self.buffer().as_ref().as_chunks_unchecked::<CHANNELS>() }
+ }
+
+ /// Create a mutref to this image
+ pub fn as_mut<U>(&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<U>(&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<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
/// - 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<U: Copy>(&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<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
// SAFETY: slice always returns a length of `CHANNELS`, so we `cast()` it for convenience.
unsafe { *ptr }
}
-}
-impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
/// 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<U: Copy>(&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<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
+ /// 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<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
#[inline]
/// Returns a iterator over every pixel, mutably
pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> {
@@ -479,12 +513,6 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
self.buffer.as_mut().array_chunks_mut::<CHANNELS>()
}
- /// 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<T, const CHANNELS: usize> {
+ inner: Image<T, CHANNELS>,
+ /// in pixels
+ offset_x: u32,
+ real_width: NonZeroU32,
+ real_height: NonZeroU32,
+}
+
+/// Trait for cropping a image.
+pub trait Cropper<T, const C: usize> {
+ /// # Panics
+ ///
+ /// if w - y == 0
+ fn from(self, x: u32, y: u32) -> SubImage<T, C>;
+}
+
+impl<T: Clone, const N: usize> Copy for SubImage<T, N> where Image<T, N>: Copy {}
+
+macro_rules! def {
+ ($t:ty, $($what:ident)?) => {
+ struct Crop<'a, T, const C: usize> {
+ dimensions: (NonZeroU32, NonZeroU32),
+ _d: PhantomData<SubImage<$t, C>>,
+ 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<T, const C: usize> Image<T, C> {
+ /// Crop a image.
+ ///
+ /// The signature looks something like: `i.crop(width, height).from(top_left_x, top_left_y)`, which gives you a <code>[SubImage]<&\[T\], _></code>
+ ///
+ /// If you want a owned image, `i.crop(w, h).from(x, y).own()` gets you a <code>[`Image`]<[Box]<\[T\], _>></code> 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<T: Clone, const C: usize> SubImage<&[T], C> {
+ /// Clones this [`SubImage`] into its own [`Image`]
+ pub fn own(&self) -> Image<Box<[T]>, 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<W, const C: usize> SubImage<W, C> {
+ /// Get a pixel.
+ ///
+ /// # Safety
+ ///
+ /// this pixel must be in bounds.
+ pub unsafe fn pixel<U: Copy>(&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<U: Copy>(&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) }
+ }
+}