fast image operations
Diffstat (limited to 'src/affine.rs')
| -rw-r--r-- | src/affine.rs | 342 |
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] |