fast image operations
uninit flips
bendn 2024-01-23
parent d56ce34 · commit 698f8d4
-rw-r--r--src/affine.rs14
-rw-r--r--src/cloner.rs7
-rw-r--r--src/lib.rs30
-rw-r--r--src/overlay.rs2
-rw-r--r--src/span.rs8
-rw-r--r--src/uninit.rs39
6 files changed, 61 insertions, 39 deletions
diff --git a/src/affine.rs b/src/affine.rs
index b0152a5..9fc7b3c 100644
--- a/src/affine.rs
+++ b/src/affine.rs
@@ -10,16 +10,17 @@ impl<const CHANNELS: usize> ImageCloner<'_, CHANNELS> {
/// ```
#[must_use = "function does not modify the original image"]
pub fn flip_v(&self) -> Image<Vec<u8>, CHANNELS> {
- let mut out = self.alloc();
+ let mut out = self.uninit();
for y in 0..self.height() {
for x in 0..self.width() {
// SAFETY: looping over self, all ok (could be safe versions, bounds would be elided)
let p = unsafe { self.pixel(x, y) };
// SAFETY: looping over self.
- unsafe { out.set_pixel(x, self.height() - y - 1, p) };
+ unsafe { out.write(&p, (x, self.height() - y - 1)) };
}
}
- out
+ // SAFETY: init
+ unsafe { out.assume_init() }
}
/// Flip an image horizontally
@@ -30,16 +31,17 @@ impl<const CHANNELS: usize> ImageCloner<'_, CHANNELS> {
/// ```
#[must_use = "function does not modify the original image"]
pub fn flip_h(&self) -> Image<Vec<u8>, CHANNELS> {
- let mut out = self.alloc();
+ let mut out = self.uninit();
for y in 0..self.height() {
for x in 0..self.width() {
// SAFETY: looping over self, all ok
let p = unsafe { self.pixel(x, y) };
// SAFETY: looping over self, all ok
- unsafe { out.set_pixel(self.width() - x - 1, y, p) };
+ unsafe { out.write(&p, (self.width() - x - 1, y)) };
}
}
- out
+ // SAFETY: init
+ unsafe { out.assume_init() }
}
}
diff --git a/src/cloner.rs b/src/cloner.rs
index b38aedd..f110c1c 100644
--- a/src/cloner.rs
+++ b/src/cloner.rs
@@ -5,7 +5,7 @@
//! # let i = Image::<_, 1>::alloc(5, 5);
//! unsafe { i.cloner().rot_270() };
//! ```
-use crate::Image;
+use crate::{uninit, Image};
/// A neat way to clone a image.
///
@@ -19,6 +19,11 @@ impl<'a, const C: usize> ImageCloner<'a, C> {
self.0.to_owned()
}
+ /// create a new uninit image the right size for use
+ pub(crate) fn uninit(&self) -> uninit::Image<u8, C> {
+ uninit::Image::new(self.width, self.height)
+ }
+
/// Create a [`ImageCloner`] from a <code>[Image]<&\[[u8]\]></code>
pub const fn from(i: Image<&'a [u8], C>) -> Self {
Self(i)
diff --git a/src/lib.rs b/src/lib.rs
index 6e7131f..098fc03 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -49,6 +49,7 @@
//! - `default`: \[`save`, `scale`\].
#![feature(
maybe_uninit_write_slice,
+ hint_assert_unchecked,
slice_swap_unchecked,
generic_const_exprs,
slice_as_chunks,
@@ -72,7 +73,7 @@
missing_docs
)]
#![allow(clippy::zero_prefixed_literal, incomplete_features)]
-use std::{num::NonZeroU32, ops::Range};
+use std::{hint::assert_unchecked, num::NonZeroU32, ops::Range};
mod affine;
#[cfg(feature = "blur")]
@@ -122,25 +123,6 @@ impl<T> CopyWithinUnchecked for [T] {
}
}
-/// like assert!(), but causes undefined behaviour at runtime when the condition is not met.
-///
-/// # Safety
-///
-/// UB if condition is false.
-macro_rules! assert_unchecked {
- ($cond:expr) => {{
- if !$cond {
- #[cfg(debug_assertions)]
- let _ = ::core::ptr::NonNull::<()>::dangling().as_ref(); // force unsafe wrapping block
- #[cfg(debug_assertions)]
- panic!("assertion failed: {} returned false", stringify!($cond));
- #[cfg(not(debug_assertions))]
- std::hint::unreachable_unchecked()
- }
- }};
-}
-use assert_unchecked;
-
trait At {
fn at<const C: usize>(self, x: u32, y: u32) -> usize;
}
@@ -455,9 +437,9 @@ impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
T: AsRef<[U]>,
{
// SAFETY: 0 sized images illegal
- unsafe { assert_unchecked!(self.len() > CHANNELS) };
+ unsafe { assert_unchecked(self.len() > CHANNELS) };
// SAFETY: no half pixels!
- unsafe { assert_unchecked!(self.len() % CHANNELS == 0) };
+ unsafe { assert_unchecked(self.len() % CHANNELS == 0) };
self.buffer().as_ref().array_chunks::<CHANNELS>()
}
@@ -546,9 +528,9 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
/// Returns a iterator over every pixel, mutably
pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> {
// SAFETY: 0 sized images are not allowed
- unsafe { assert_unchecked!(self.len() > CHANNELS) };
+ unsafe { assert_unchecked(self.len() > CHANNELS) };
// SAFETY: buffer cannot have half pixels
- unsafe { assert_unchecked!(self.len() % CHANNELS == 0) };
+ unsafe { assert_unchecked(self.len() % CHANNELS == 0) };
self.buffer.as_mut().array_chunks_mut::<CHANNELS>()
}
diff --git a/src/overlay.rs b/src/overlay.rs
index dbb7a0a..eef4272 100644
--- a/src/overlay.rs
+++ b/src/overlay.rs
@@ -160,7 +160,7 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, U: AsRef<[u8]>> OverlayAt<Image<U, 4>> for Im
#[inline]
unsafe fn overlay_at(&mut self, with: &Image<U, 4>, x: u32, y: u32) -> &mut Self {
// SAFETY: caller upholds this
- unsafe { assert_unchecked!(x + with.width() <= self.width()) };
+ unsafe { assert_unchecked(x + with.width() <= self.width()) };
debug_assert!(y + with.height() <= self.height());
for j in 0..with.height() {
let i_x = j as usize * with.width() as usize * 4
diff --git a/src/span.rs b/src/span.rs
index 7ac76c9..69a01ad 100644
--- a/src/span.rs
+++ b/src/span.rs
@@ -32,3 +32,11 @@ impl Span for Range<(u32, u32)> {
i.at::<C>(sx, sy)..i.at::<C>(ex, ey)
}
}
+
+impl Sealed for (u32, u32) {}
+impl Span for (u32, u32) {
+ #[inline(always)]
+ fn range<const C: usize>(self, i: (u32, u32)) -> Range<usize> {
+ i.at::<C>(self.0, self.1)..i.at::<C>(self.0, self.1) + C
+ }
+}
diff --git a/src/uninit.rs b/src/uninit.rs
index 3555beb..c4c341b 100644
--- a/src/uninit.rs
+++ b/src/uninit.rs
@@ -1,9 +1,14 @@
//! the houser of uninitialized memory. €$@!0В𴬔!℡
//!
//! contains [`Image`], an uninitialized image.
-use std::{mem::MaybeUninit, num::NonZeroU32};
+use std::{
+ hint::assert_unchecked,
+ mem::MaybeUninit,
+ num::NonZeroU32,
+ ops::{Index, IndexMut},
+};
-use crate::CopyWithinUnchecked;
+use crate::{span::Span, CopyWithinUnchecked};
/// A uninitialized image. Be sure to initialize it!
pub struct Image<T: Copy, const CHANNELS: usize> {
@@ -13,6 +18,14 @@ pub struct Image<T: Copy, const CHANNELS: usize> {
height: NonZeroU32,
}
+impl<I: Span, T: Copy, const C: usize> Index<I> for Image<T, C> {
+ type Output = [T];
+
+ fn index(&self, index: I) -> &Self::Output {
+ &self.buffer()[index.range::<C>((self.width(), self.height()))]
+ }
+}
+
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 {
@@ -27,19 +40,31 @@ impl<T: Copy, const CHANNELS: usize> Image<T, CHANNELS> {
///
/// # 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) };
+ /// data and indexed range must have same len.
+ pub unsafe fn write(&mut self, data: &[T], i: impl Span) {
+ // SAFETY: caller
+ let dat = unsafe { self.slice(i) };
+ // SAFETY: caller
+ unsafe { assert_unchecked(dat.len() == data.len()) };
MaybeUninit::write_slice(dat, data);
}
+ /// Slice the image.
+ ///
+ /// # Safety
+ /// index must be in bounds.
+ pub unsafe fn slice(&mut self, i: impl Span) -> &mut [MaybeUninit<T>] {
+ let range = i.range::<CHANNELS>((self.width(), self.height()));
+ // SAFETY: assured
+ unsafe { self.buf().get_unchecked_mut(range) }
+ }
+
/// 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) {
+ pub unsafe fn copy_within(&mut self, i: impl Span, to: usize) {
let range = i.range::<CHANNELS>((self.width(), self.height()));
// SAFETY: copy!
unsafe { self.buf().copy_within_unchecked(range, to) };