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(self, x: u32, y: u32) -> usize; +} + +impl At for (u32, u32) { + fn at(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, 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 Image { /// 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::(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(self, i: (u32, u32)) -> Range; +} + +impl Sealed for Range {} +impl Span for Range { + #[inline(always)] + fn range(self, _: (u32, u32)) -> Range { + self + } +} + +impl Sealed for Range<(u32, u32)> {} +impl Span for Range<(u32, u32)> { + #[inline(always)] + fn range(self, i: (u32, u32)) -> Range { + let Self { + start: (sx, sy), + end: (ex, ey), + } = self; + i.at::(sx, sy)..i.at::(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 { + /// Has capacity w * h * c + buffer: Vec, + width: NonZeroU32, + height: NonZeroU32, +} + +impl Image { + /// 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::((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::((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::((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, width: NonZeroU32, height: NonZeroU32) -> Self { + Self { + buffer, + width, + height, + } + } + + /// consumes the image, returning the image buffer + pub fn take_buffer(self) -> Vec { + 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] { + 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, 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) } + } +}