fast image operations
Diffstat (limited to 'src/affine.rs')
-rw-r--r--src/affine.rs342
1 files changed, 271 insertions, 71 deletions
diff --git a/src/affine.rs b/src/affine.rs
index 362a746..fe3656d 100644
--- a/src/affine.rs
+++ b/src/affine.rs
@@ -1,19 +1,52 @@
//! Manages the affine image transformations.
-use crate::Image;
+use crate::{Image, cloner::ImageCloner};
-impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
- /// Flip a image horizontally.
- pub fn flip_h(&mut self) {
- self.as_mut().flip_h();
+impl<const CHANNELS: usize> ImageCloner<'_, CHANNELS> {
+ /// Flip an image vertically.
+ /// ```
+ /// # use fimg::Image;
+ /// let a = Image::<_, 1>::build(2,2).buf(vec![21,42,90,01]);
+ /// assert_eq!(a.cloner().flip_v().take_buffer(), [90,01,21,42]);
+ /// ```
+ #[must_use = "function does not modify the original image"]
+ pub fn flip_v(&self) -> Image<Vec<u8>, CHANNELS> {
+ 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.write(p, (x, self.height() - y - 1)) };
+ }
+ }
+ // SAFETY: init
+ unsafe { out.assume_init() }
}
- /// Flip a image vertically.
- pub fn flip_v(&mut self) {
- self.as_mut().flip_v();
+
+ /// Flip an image horizontally
+ /// ```
+ /// # use fimg::Image;
+ /// let a = Image::<_,1>::build(2,2).buf(vec![90,01,21,42]);
+ /// assert_eq!(a.cloner().flip_h().take_buffer(), [01,90,42,21]);
+ /// ```
+ #[must_use = "function does not modify the original image"]
+ pub fn flip_h(&self) -> Image<Vec<u8>, CHANNELS> {
+ 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.write(p, (self.width() - x - 1, y)) };
+ }
+ }
+ // SAFETY: init
+ unsafe { out.assume_init() }
}
}
-impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
- /// Flip a image vertically.
+impl<const CHANNELS: usize, T: AsMut<[u8]> + AsRef<[u8]>> Image<T, CHANNELS> {
+ /// Flip an image vertically.
pub fn flip_v(&mut self) {
for y in 0..self.height() / 2 {
for x in 0..self.width() {
@@ -21,16 +54,13 @@ impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
#[allow(clippy::multiple_unsafe_ops_per_block)]
// SAFETY: within bounds
unsafe {
- let p2 = self.pixel(x, y2);
- let p = self.pixel(x, y);
- self.set_pixel(x, y2, p);
- self.set_pixel(x, y, p2);
+ self.swap_pixel((x, y2), (x, y))
}
}
}
}
- /// Flip a image horizontally.
+ /// Flip an image horizontally.
pub fn flip_h(&mut self) {
for y in 0..self.height() {
for x in 0..self.width() / 2 {
@@ -38,77 +68,66 @@ impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
#[allow(clippy::multiple_unsafe_ops_per_block)]
// SAFETY: bounded
unsafe {
- let p2 = self.pixel(x2, y);
- let p = self.pixel(x, y);
- self.set_pixel(x2, y, p);
- self.set_pixel(x, y, p2);
+ self.swap_pixel((x2, y), (x, y))
}
}
}
}
}
-impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
- /// Rotate a image 180 degrees clockwise.
- pub fn rot_180(&mut self) {
- self.as_mut().rot_180();
+impl<const CHANNELS: usize> ImageCloner<'_, CHANNELS> {
+ /// Rotate an image 180 degrees clockwise.
+ ///
+ /// ```
+ /// # use fimg::Image;
+ /// let a = Image::<_,1>::build(2,2).buf(vec![00,01,02,10]);
+ /// assert_eq!(a.cloner().rot_180().take_buffer(), vec![10,02,01,00]);
+ /// ```
+ #[must_use = "function does not modify the original image"]
+ pub fn rot_180(&self) -> Image<Vec<u8>, CHANNELS> {
+ let s = (self.width() * self.height()) as usize;
+ let mut v: Vec<[u8; CHANNELS]> = Vec::with_capacity(s);
+ for (x, y) in self.chunked().rev().zip(&mut v.spare_capacity_mut()[..]) {
+ y.write(*x);
+ }
+ // SAFETY: we just wrote the right amount
+ unsafe { v.set_len(s) };
+ Image::build(self.width(), self.height()).buf(v.into_flattened())
}
- /// Rotate a image 90 degrees clockwise.
+ /// Rotate an image 90 degrees clockwise.
/// # Safety
///
/// UB if the image is not square
- pub unsafe fn rot_90(&mut self) {
- // SAFETY: make sure to keep the safety docs linked
- unsafe { self.as_mut().rot_90() }
+ #[must_use = "function does not modify the original image"]
+ pub unsafe fn rot_90(&self) -> Image<Vec<u8>, CHANNELS> {
+ // SAFETY: yep
+ let mut out = unsafe { transpose_out(self) };
+ // SAFETY: sqar
+ unsafe { crev(out.as_mut()) };
+ out
}
- /// Rotate a image 270 degrees clockwise, or 90 degrees anti clockwise.
+ /// Rotate an image 270 degrees clockwise, or 90 degrees anti clockwise.
/// # Safety
///
/// UB if the image is not square
- pub unsafe fn rot_270(&mut self) {
- // SAFETY: idk this is just a convenience impl
- unsafe { self.as_mut().rot_270() }
+ #[must_use = "function does not modify the original image"]
+ pub unsafe fn rot_270(&self) -> Image<Vec<u8>, CHANNELS> {
+ // SAFETY: yep
+ let mut out = unsafe { transpose_out(self) };
+ out.flip_v();
+ out
}
}
-impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
- /// Rotate a image 180 degrees clockwise.
+impl<const CHANNELS: usize, T: AsMut<[u8]> + AsRef<[u8]>> Image<T, CHANNELS> {
+ /// Rotate an image 180 degrees clockwise.
pub fn rot_180(&mut self) {
- for y in 0..self.height() / 2 {
- for x in 0..self.width() {
- // SAFETY: x, y come from the loop, must be ok
- let p = unsafe { self.pixel(x, y) };
- let x2 = self.width() - x - 1;
- let y2 = self.height() - y - 1;
- // SAFETY: values are good
- let p2 = unsafe { self.pixel(x2, y2) };
- // SAFETY: swapping would be cool, alas.
- unsafe { self.set_pixel(x, y, p2) };
- // SAFETY: although maybe i can cast it to a `[[u8; CHANNELS]]` and swap that 🤔
- unsafe { self.set_pixel(x2, y2, p) };
- }
- }
-
- if self.height() % 2 != 0 {
- let middle = self.height() / 2;
-
- for x in 0..self.width() / 2 {
- let x2 = self.width() - x - 1;
- #[allow(clippy::multiple_unsafe_ops_per_block)]
- // SAFETY: its just doing the swappy
- unsafe {
- let p = self.pixel(x, middle);
- let p2 = self.pixel(x2, middle);
- self.set_pixel(x, middle, p2);
- self.set_pixel(x2, middle, p);
- }
- }
- }
+ self.flatten_mut().reverse();
}
- /// Rotate a image 90 degrees clockwise.
+ /// Rotate an image 90 degrees clockwise.
/// # Safety
///
/// UB if the image is not square
@@ -121,7 +140,7 @@ impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
unsafe { transpose(self) };
}
- /// Rotate a image 270 degrees clockwise, or 90 degrees anti clockwise.
+ /// Rotate an image 270 degrees clockwise, or 90 degrees anti clockwise.
/// # Safety
///
/// UB if the image is not square
@@ -133,21 +152,146 @@ impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
}
}
+/// Reverse columns of square image
+/// # Safety
+///
+/// UB if supplied image not square
+unsafe fn crev<const CHANNELS: usize, T: AsMut<[u8]> + AsRef<[u8]>>(mut img: Image<T, CHANNELS>) {
+ debug_assert_eq!(img.width(), img.height());
+ let size = img.width() as usize;
+ let b = img.flatten_mut();
+ for i in 0..size {
+ let mut start = 0;
+ let mut end = size - 1;
+ while start < end {
+ // SAFETY: hmm
+ unsafe { b.swap_unchecked(i * size + start, i * size + end) };
+ start += 1;
+ end -= 1;
+ }
+ }
+}
+
+/// Transpose a square image out of place
+/// # Safety
+///
+/// UB if provided image rectangular
+unsafe fn transpose_out<const CHANNELS: usize>(
+ i: &ImageCloner<'_, CHANNELS>,
+) -> Image<Vec<u8>, CHANNELS> {
+ let mut out = i.alloc();
+ // SAFETY: yep
+ unsafe {
+ mattr::transpose(
+ i.flatten(),
+ out.flatten_mut(),
+ i.height() as usize,
+ i.width() as usize,
+ )
+ };
+ out
+}
+
/// Transpose a square image
/// # Safety
///
/// UB if supplied image rectangular
-unsafe fn transpose<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
+unsafe fn transpose<const CHANNELS: usize, T: AsMut<[u8]> + AsRef<[u8]>>(
+ img: &mut Image<T, CHANNELS>,
+) {
+ debug_assert_eq!(img.width(), img.height());
+ if img.width().is_power_of_two() {
+ // SAFETY: caller guarantees
+ unsafe { transpose_diag(img, 0, img.width() as usize) };
+ } else {
+ // SAFETY: caller guarantees
+ unsafe { transpose_non_power_of_two(img) };
+ }
+}
+
+/// Transpose a square (non power of two) image.
+///
+/// # Safety
+///
+/// UB if image not square
+unsafe fn transpose_non_power_of_two<const CHANNELS: usize, T: AsMut<[u8]> + AsRef<[u8]>>(
+ img: &mut Image<T, CHANNELS>,
+) {
debug_assert_eq!(img.width(), img.height());
let size = img.width() as usize;
- // SAFETY: no half pixels
- let b = unsafe { img.buffer.as_chunks_unchecked_mut::<CHANNELS>() };
+ let b = img.flatten_mut();
for i in 0..size {
for j in i..size {
// SAFETY: caller ensures squarity
- unsafe {
- b.swap_unchecked(i * size + j, j * size + i);
- };
+ unsafe { b.swap_unchecked(i * size + j, j * size + i) };
+ }
+ }
+}
+
+/// break it down until
+const TILE: usize = 4;
+/// # Safety
+///
+/// be careful
+unsafe fn transpose_tile<const CHANNELS: usize, T: AsMut<[u8]> + AsRef<[u8]>>(
+ img: &mut Image<T, CHANNELS>,
+ row: usize,
+ col: usize,
+ size: usize,
+) {
+ if size > TILE {
+ #[allow(
+ clippy::multiple_unsafe_ops_per_block,
+ clippy::undocumented_unsafe_blocks
+ )]
+ unsafe {
+ // top left
+ transpose_tile(img, row, col, size / 2);
+ // top right
+ transpose_tile(img, row, col + size / 2, size / 2);
+ // bottom left
+ transpose_tile(img, row + size / 2, col, size / 2);
+ // bottom right
+ transpose_tile(img, row + size / 2, col + size / 2, size / 2);
+ }
+ } else {
+ let s = img.width() as usize;
+ let b = img.flatten_mut();
+ for i in 0..size {
+ for j in 0..size {
+ // SAFETY: this should be okay if we careful
+ unsafe { b.swap_unchecked((row + i) * s + (col + j), (col + j) * s + (row + i)) };
+ }
+ }
+ }
+}
+
+/// # Safety
+///
+/// be careful
+unsafe fn transpose_diag<const CHANNELS: usize, T: AsMut<[u8]> + AsRef<[u8]>>(
+ img: &mut Image<T, CHANNELS>,
+ pos: usize,
+ size: usize,
+) {
+ if size > TILE {
+ #[allow(
+ clippy::multiple_unsafe_ops_per_block,
+ clippy::undocumented_unsafe_blocks
+ )]
+ unsafe {
+ transpose_diag(img, pos, size / 2);
+ transpose_tile(img, pos, pos + size / 2, size / 2);
+ transpose_diag(img, pos + size / 2, size / 2);
+ }
+ } else {
+ let s = img.width() as usize;
+ let b = img.flatten_mut();
+ for i in 1..size {
+ for j in 0..i {
+ // SAFETY: this is fine unless pos / size is out of bounds, which it cant be
+ unsafe { b.swap_unchecked((pos + i) * s + (pos + j), (pos + j) * s + (pos + i)) };
+ }
}
}
}
@@ -158,6 +302,62 @@ mod tests {
use crate::img;
#[test]
+ fn transp() {
+ #[rustfmt::skip]
+ let mut i = Image::<_, 1>::build(8, 8).buf(vec![
+ 0, 0, 1, 1, 0, 0, 1, 1,
+ 0, 1, 0, 1, 1, 0, 1, 1,
+ 0, 1, 1, 0, 1, 0, 1, 1,
+ 0, 1, 1, 1, 0, 0, 1, 1,
+ 0, 1, 1, 1, 1, 0, 1, 1,
+ 0, 1, 1, 1, 1, 0, 0, 1,
+ 0, 1, 1, 1, 1, 0, 1, 0,
+ 0, 0, 1, 1, 1, 0, 1, 1,
+ ]);
+ unsafe { transpose(&mut i.as_mut()) };
+ #[rustfmt::skip]
+ assert_eq!(i.take_buffer(), vec![
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 0,
+ 1, 0, 1, 1, 1, 1, 1, 1,
+ 1, 1, 0, 1, 1, 1, 1, 1,
+ 0, 1, 1, 0, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 0, 1, 1,
+ 1, 1, 1, 1, 1, 1, 0, 1
+ ]);
+ }
+
+ #[test]
+ fn transp9() {
+ #[rustfmt::skip]
+ let mut i = Image::<_, 1>::build(9, 9).buf(vec![
+ 0, 0, 1, 1, 0, 0, 1, 1, 0,
+ 0, 1, 0, 1, 1, 0, 1, 1, 1,
+ 0, 1, 1, 0, 1, 0, 1, 1, 0,
+ 0, 1, 1, 1, 0, 0, 1, 1, 0,
+ 0, 1, 1, 1, 1, 0, 1, 1, 1,
+ 0, 1, 1, 1, 1, 0, 0, 1, 1,
+ 0, 1, 1, 1, 1, 0, 1, 0, 1,
+ 0, 0, 1, 1, 1, 0, 1, 1, 0,
+ 1, 1, 1, 0, 1, 1, 0, 1, 0,
+ ]);
+ unsafe { transpose(&mut i.as_mut()) };
+ #[rustfmt::skip]
+ assert_eq!(i.take_buffer(), vec![
+ 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1, 1, 0, 1,
+ 1, 0, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 0, 1, 1, 1, 1, 1, 0,
+ 0, 1, 1, 0, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 0, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 0, 1, 1,
+ 0, 1, 0, 0, 1, 1, 1, 0, 0
+ ]);
+ }
+
+ #[test]
fn rotate_90() {
let mut from = img![
[00, 01]