fast image operations
some changes
bendn 2025-01-19
parent 3e3ca7b · commit 193a7b4
-rw-r--r--Cargo.toml1
-rw-r--r--rust-toolchain.toml2
-rw-r--r--src/lib.rs90
3 files changed, 73 insertions, 20 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 8b3b403..a435f41 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,6 +27,7 @@ wgpu = { version = "0.19.1", default-features = false, optional = true }
atools = "0.1.4"
qwant = { version = "1.0.0", optional = true }
libc = "0.2.154"
+hinted = { version = "0.0.1", features = ["nightly"] }
[target.'cfg(windows)'.dependencies]
windows = { version = "0.53.0", features = [
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index 5d56faf..e5d982c 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,2 +1,2 @@
[toolchain]
-channel = "nightly"
+channel = "nightly-2024-06-20"
diff --git a/src/lib.rs b/src/lib.rs
index 355f4f0..ed1e884 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -64,6 +64,7 @@
doc_auto_cfg,
const_option,
array_chunks,
+ iter_chain,
let_chains,
try_blocks,
test
@@ -85,6 +86,7 @@
confusable_idents,
internal_features
)]
+use hinted::HintExt;
use std::{hint::assert_unchecked, intrinsics::transmute_unchecked, num::NonZeroU32, ops::Range};
mod affine;
@@ -195,7 +197,6 @@ impl Image<&[u8], 3> {
unsafe { img.copy_within(first.clone(), section) };
}
}
-
let first_row = 0..img.at(0, self.height());
for y in 1..(out_height / self.height()) {
let this_row = img.at(0, y * self.height());
@@ -514,6 +515,17 @@ impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
unsafe { Image::new(self.width, self.height, self.buffer().as_ref()) }
}
+ /// Get a pixel. Optionally. Yeah!
+ pub fn get_pixel<U: Copy>(&self, x: u32, y: u32) -> Option<[U; CHANNELS]>
+ where
+ T: AsRef<[U]>,
+ {
+ self.buffer()
+ .as_ref()
+ .get(self.slice(x, y))
+ .map(|x| unsafe { x.try_into().unwrap_unchecked() })
+ }
+
/// Return a pixel at (x, y).
/// # Safety
///
@@ -524,16 +536,8 @@ impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
where
T: AsRef<[U]>,
{
- // SAFETY: x and y in bounds, slice is okay
- let ptr = unsafe {
- self.buffer()
- .as_ref()
- .get_unchecked(self.slice(x, y))
- .as_ptr()
- .cast()
- };
- // SAFETY: slice always returns a length of `CHANNELS`, so we `cast()` it for convenience.
- unsafe { *ptr }
+ // SAFETY: x and y in bounds
+ unsafe { self.get_pixel(x, y).unwrap_unchecked() }
}
/// Returns a [`PixelEntry`]
@@ -567,15 +571,19 @@ impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
T: AsMut<[U]> + AsRef<[U]>,
{
// SAFETY: we have been told x, y is in bounds.
+ unsafe { self.get_pixel_mut(x, y).unwrap_unchecked() }
+ }
+
+ /// Returns a mutable reference to a pixel at (x, y), if (x, y) is in bounds.
+ pub fn get_pixel_mut<U>(&mut self, x: u32, y: u32) -> Option<&mut [U; CHANNELS]>
+ where
+ T: AsMut<[U]> + AsRef<[U]>,
+ {
let idx = self.slice(x, y);
- // SAFETY: slice should always return a valid index
- unsafe {
- self.buffer
- .as_mut()
- .get_unchecked_mut(idx)
- .try_into()
- .unwrap_unchecked()
- }
+ self.buffer
+ .as_mut()
+ .get_mut(idx)
+ .map(|x| x.try_into().unwrap())
}
/// iterator over columns
@@ -643,6 +651,50 @@ impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
{
self.flatten().chunks_exact(self.width() as usize)
}
+
+ /// Itearte the pixels of this image in parse order.
+ /// use [`Image::chunked`] if you just want the pixels.
+ pub fn ordered(&self) -> impl ExactSizeIterator + DoubleEndedIterator<Item = (u32, u32)> {
+ let w = self.width();
+ unsafe {
+ (0..self.height())
+ .flat_map(move |y| (0..w).map(move |x| (x, y)))
+ .has(self.width() as usize * self.height() as usize)
+ }
+ }
+
+ /// Iterate the pixels of this image in serpentine order.
+ ///
+ /// # Safety
+ ///
+ /// The points are guaranteed to be on the image.
+ pub fn serpent(&self) -> impl ExactSizeIterator + Iterator<Item = (u32, u32)> {
+ let w = self.width();
+ unsafe {
+ (0..self.height() / 2)
+ .flat_map(move |y| {
+ std::iter::chain(
+ (0..w).map(move |x| (x, y * 2)),
+ (0..w).rev().map(move |x| (x, (y * 2) + 1)),
+ )
+ })
+ .take(self.width() as usize * self.height() as usize)
+ .has(self.width() as usize * self.height() as usize)
+ }
+ }
+
+ /// Get the pixels from an iterator.
+ /// # Safety
+ /// the points must be on the image.
+ pub unsafe fn pixels_of<'l, U: Copy>(
+ &'l self,
+ iterator: impl ExactSizeIterator<Item = (u32, u32)> + 'l,
+ ) -> impl ExactSizeIterator<Item = [U; CHANNELS]> + 'l
+ where
+ T: AsRef<[U]>,
+ {
+ iterator.map(move |(x, y)| unsafe { self.pixel(x, y) })
+ }
}
impl<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {