fast image operations
-rw-r--r--src/lib.rs78
-rw-r--r--src/span.rs34
-rw-r--r--src/uninit.rs120
3 files changed, 191 insertions, 41 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 07c3f67..6e7131f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -72,7 +72,7 @@
missing_docs
)]
#![allow(clippy::zero_prefixed_literal, incomplete_features)]
-use std::{mem::MaybeUninit, num::NonZeroU32, ops::Range};
+use std::{num::NonZeroU32, ops::Range};
mod affine;
#[cfg(feature = "blur")]
@@ -87,7 +87,9 @@ mod r#dyn;
pub(crate) mod math;
mod overlay;
mod pack;
+mod span;
mod sub;
+pub mod uninit;
pub use pack::Pack;
pub mod pixels;
#[cfg(feature = "scale")]
@@ -139,6 +141,26 @@ macro_rules! assert_unchecked {
}
use assert_unchecked;
+trait At {
+ fn at<const C: usize>(self, x: u32, y: u32) -> usize;
+}
+
+impl At for (u32, u32) {
+ fn at<const C: usize>(self, x: u32, y: u32) -> usize {
+ debug_assert!(x < self.0, "x out of bounds");
+ debug_assert!(y < self.1, "y out of bounds");
+ #[allow(clippy::multiple_unsafe_ops_per_block)]
+ // SAFETY: me when uncheck math: 😧 (FIXME)
+ let index = unsafe {
+ // y * w + x
+ let tmp = (y as usize).unchecked_mul(self.0 as usize);
+ tmp.unchecked_add(x as usize)
+ };
+ // SAFETY: 🧐 is unsound? 😖
+ unsafe { index.unchecked_mul(C) }
+ }
+}
+
impl Image<&[u8], 3> {
/// Tile self till it fills a new image of size x, y
/// # Safety
@@ -152,7 +174,10 @@ impl Image<&[u8], 3> {
/// ```
#[must_use = "function does not modify the original image"]
pub unsafe fn repeated(&self, out_width: u32, out_height: u32) -> Image<Vec<u8>, 3> {
- let mut img = Vec::with_capacity(3 * out_width as usize * out_height as usize);
+ let mut img = uninit::Image::new(
+ out_width.try_into().unwrap(),
+ out_height.try_into().unwrap(),
+ );
debug_assert!(out_width % self.width() == 0);
debug_assert!(out_height % self.height() == 0);
for y in 0..self.height() {
@@ -162,43 +187,25 @@ impl Image<&[u8], 3> {
.get_unchecked(self.at(0, y)..self.at(0, y) + (self.width() as usize * 3))
};
debug_assert_eq!(from.len(), self.width() as usize * 3);
- let first =
- ((y * out_width) as usize * 3)..((y * out_width + self.width()) as usize * 3);
- // SAFETY: put it in the output
- let to = unsafe { img.spare_capacity_mut().get_unchecked_mut(first.clone()) };
- // copy it in
- unsafe { assert_unchecked!(to.len() == from.len()) };
- MaybeUninit::write_slice(to, from);
+ let first = (0, y)..(self.width(), y);
+ // SAFETY: copy it in
+ unsafe { img.write(from, first.clone()) };
for x in 1..(out_width / self.width()) {
- let section = (y * out_width + x * self.width()) as usize * 3;
+ let section = img.at(x * self.width(), y);
// SAFETY: copy each row of the image one by one
- unsafe {
- img.spare_capacity_mut()
- .copy_within_unchecked(first.clone(), section)
- };
+ unsafe { img.copy_within(first.clone(), section) };
}
}
- let first_row = 0..(self.height() * out_width) as usize * 3;
+ let first_row = 0..img.at(0, self.height());
for y in 1..(out_height / self.height()) {
- let this_row = (y * self.height() * out_width) as usize * 3;
+ let this_row = img.at(0, y * self.height());
// SAFETY: copy entire blocks of image at a time
- unsafe {
- img.spare_capacity_mut()
- .copy_within_unchecked(first_row.clone(), this_row)
- };
+ unsafe { img.copy_within(first_row.clone(), this_row) };
}
// SAFETY: we init
- unsafe { img.set_len(3 * out_width as usize * out_height as usize) };
- // SAFETY: ok
- unsafe {
- Image::new(
- out_width.try_into().unwrap(),
- out_height.try_into().unwrap(),
- img,
- )
- }
+ unsafe { img.assume_init() }
}
}
@@ -297,18 +304,7 @@ impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
/// the output index is not guranteed to be in bounds
#[inline]
fn at(&self, x: u32, y: u32) -> usize {
- debug_assert!(x < self.width(), "x out of bounds");
- debug_assert!(y < self.height(), "y out of bounds");
- #[allow(clippy::multiple_unsafe_ops_per_block)]
- // SAFETY: me when uncheck math: 😧 (FIXME)
- let index = unsafe {
- let w = self.width();
- // y * w + x
- let tmp = (y as usize).unchecked_mul(w as usize);
- tmp.unchecked_add(x as usize)
- };
- // SAFETY: 🧐 is unsound? 😖
- unsafe { index.unchecked_mul(CHANNELS) }
+ (self.width(), self.height()).at::<CHANNELS>(x, y)
}
/// # Safety
diff --git a/src/span.rs b/src/span.rs
new file mode 100644
index 0000000..7ac76c9
--- /dev/null
+++ b/src/span.rs
@@ -0,0 +1,34 @@
+use crate::At;
+use std::ops::Range;
+
+mod sealer {
+ #[doc(hidden)]
+ pub trait Sealed {}
+}
+use sealer::Sealed;
+
+/// Trait for that which can be used to index a image.
+pub trait Span: Sealed {
+ #[doc(hidden)]
+ fn range<const C: usize>(self, i: (u32, u32)) -> Range<usize>;
+}
+
+impl Sealed for Range<usize> {}
+impl Span for Range<usize> {
+ #[inline(always)]
+ fn range<const C: usize>(self, _: (u32, u32)) -> Range<usize> {
+ self
+ }
+}
+
+impl Sealed for Range<(u32, u32)> {}
+impl Span for Range<(u32, u32)> {
+ #[inline(always)]
+ fn range<const C: usize>(self, i: (u32, u32)) -> Range<usize> {
+ let Self {
+ start: (sx, sy),
+ end: (ex, ey),
+ } = self;
+ i.at::<C>(sx, sy)..i.at::<C>(ex, ey)
+ }
+}
diff --git a/src/uninit.rs b/src/uninit.rs
new file mode 100644
index 0000000..3555beb
--- /dev/null
+++ b/src/uninit.rs
@@ -0,0 +1,120 @@
+//! the houser of uninitialized memory. €$@!0В𴬔!℡
+//!
+//! contains [`Image`], an uninitialized image.
+use std::{mem::MaybeUninit, num::NonZeroU32};
+
+use crate::CopyWithinUnchecked;
+
+/// A uninitialized image. Be sure to initialize it!
+pub struct Image<T: Copy, const CHANNELS: usize> {
+ /// Has capacity w * h * c
+ buffer: Vec<T>,
+ width: NonZeroU32,
+ height: NonZeroU32,
+}
+
+impl<T: Copy, const CHANNELS: usize> Image<T, CHANNELS> {
+ /// Create a new uninit image. This is not init.
+ pub fn new(width: NonZeroU32, height: NonZeroU32) -> Self {
+ Self {
+ buffer: Vec::with_capacity(width.get() as usize * height.get() as usize * CHANNELS),
+ width,
+ height,
+ }
+ }
+
+ /// Write to the image.
+ ///
+ /// # Safety
+ /// index must be in bounds.
+ pub unsafe fn write(&mut self, data: &[T], i: impl crate::span::Span) {
+ let range = i.range::<CHANNELS>((self.width(), self.height()));
+ // SAFETY: write
+ let dat = unsafe { self.buf().get_unchecked_mut(range) };
+ MaybeUninit::write_slice(dat, data);
+ }
+
+ /// Copy a range to a position.
+ ///
+ /// # Safety
+ ///
+ /// both parts must be in bounds.
+ pub unsafe fn copy_within(&mut self, i: impl crate::span::Span, to: usize) {
+ let range = i.range::<CHANNELS>((self.width(), self.height()));
+ // SAFETY: copy!
+ unsafe { self.buf().copy_within_unchecked(range, to) };
+ }
+
+ /// # Safety
+ ///
+ /// the output index is not guranteed to be in bounds
+ #[inline]
+ pub fn at(&self, x: u32, y: u32) -> usize {
+ crate::At::at::<CHANNELS>((self.width(), self.height()), x, y)
+ }
+
+ #[inline]
+ /// get the height as a [`u32`]
+ pub const fn height(&self) -> u32 {
+ self.height.get()
+ }
+
+ #[inline]
+ /// get the width as a [`u32`]
+ pub const fn width(&self) -> u32 {
+ self.width.get()
+ }
+
+ #[inline]
+ /// create a new image
+ ///
+ /// # Safety
+ ///
+ /// does not check that buffer.capacity() == w * h * C
+ ///
+ /// using this with invalid values may result in future UB
+ pub const unsafe fn with_buf(buffer: Vec<T>, width: NonZeroU32, height: NonZeroU32) -> Self {
+ Self {
+ buffer,
+ width,
+ height,
+ }
+ }
+
+ /// consumes the image, returning the image buffer
+ pub fn take_buffer(self) -> Vec<T> {
+ self.buffer
+ }
+
+ /// returns a immutable reference to the backing buffer
+ pub fn buffer(&self) -> &[T] {
+ &self.buffer
+ }
+
+ /// returns a mutable reference to the backing buffer
+ pub fn buf(&mut self) -> &mut [MaybeUninit<T>] {
+ self.buffer.spare_capacity_mut()
+ }
+
+ /// initializes this image, assuming you have done your job
+ /// # Safety
+ /// requires initialization
+ pub unsafe fn init(&mut self) {
+ // SAFETY: we have trust for our callers.
+ unsafe {
+ self.buffer
+ .set_len(self.width() as usize * self.height() as usize * CHANNELS)
+ };
+ }
+
+ /// initializes this image, mapping to a normal [`crate::Image`] type.
+ ///
+ /// # Safety
+ /// UB if you have not init the image
+ pub unsafe fn assume_init(mut self) -> crate::Image<Vec<T>, CHANNELS> {
+ // SAFETY: its apparently init
+ unsafe { self.init() };
+ // SAFETY: image all init, good to go
+ unsafe { crate::Image::new(self.width, self.height, self.buffer) }
+ }
+}