fast image operations
Diffstat (limited to 'src/affine.rs')
| -rw-r--r-- | src/affine.rs | 409 |
1 files changed, 112 insertions, 297 deletions
diff --git a/src/affine.rs b/src/affine.rs index fe3656d..393b571 100644 --- a/src/affine.rs +++ b/src/affine.rs @@ -1,297 +1,136 @@ -//! Manages the affine image transformations. -use crate::{Image, cloner::ImageCloner}; +use crate::{FromRefMut, Image}; -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() } - } +pub trait Rotations { + /// Rotate a image 180 degrees clockwise. + fn rot_180(&mut self); + /// Rotate a image 90 degrees clockwise. + /// # Safety + /// + /// UB if the image is not square + unsafe fn rot_90(&mut self); + /// Rotate a image 270 degrees clockwise, or 90 degrees anti clockwise. + /// # Safety + /// + /// UB if the image is not square + unsafe fn rot_270(&mut self); +} - /// 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() } +pub trait Flips { + /// Flip a image vertically. + fn flip_v(&mut self); + + /// Flip a image horizontally. + fn flip_h(&mut self); +} + +impl<const CHANNELS: usize> Flips for Image<Vec<u8>, CHANNELS> { + fn flip_h(&mut self) { + self.as_mut().flip_h(); + } + fn flip_v(&mut self) { + self.as_mut().flip_v(); } } -impl<const CHANNELS: usize, T: AsMut<[u8]> + AsRef<[u8]>> Image<T, CHANNELS> { - /// Flip an image vertically. - pub fn flip_v(&mut self) { +impl<const CHANNELS: usize> Flips for Image<&mut [u8], CHANNELS> { + fn flip_v(&mut self) { for y in 0..self.height() / 2 { for x in 0..self.width() { let y2 = self.height() - y - 1; - #[allow(clippy::multiple_unsafe_ops_per_block)] // SAFETY: within bounds - unsafe { - self.swap_pixel((x, y2), (x, y)) - } + let p2 = unsafe { self.pixel(x, y2) }; + let p = unsafe { self.pixel(x, y) }; + unsafe { self.set_pixel(x, y2, p) }; + unsafe { self.set_pixel(x, y, p2) }; } } } - /// Flip an image horizontally. - pub fn flip_h(&mut self) { + fn flip_h(&mut self) { for y in 0..self.height() { for x in 0..self.width() / 2 { let x2 = self.width() - x - 1; - #[allow(clippy::multiple_unsafe_ops_per_block)] - // SAFETY: bounded - unsafe { - self.swap_pixel((x2, y), (x, y)) - } + let p2 = unsafe { self.pixel(x2, y) }; + let p = unsafe { self.pixel(x, y) }; + unsafe { self.set_pixel(x2, y, p) }; + unsafe { self.set_pixel(x, y, p2) }; } } } } -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()) +impl<const CHANNELS: usize> Rotations for Image<Vec<u8>, CHANNELS> { + fn rot_180(&mut self) { + self.as_mut().rot_180(); } - /// Rotate an image 90 degrees clockwise. - /// # Safety - /// - /// UB if the image is not square - #[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 + unsafe fn rot_90(&mut self) { + unsafe { self.as_mut().rot_90() } } - /// Rotate an image 270 degrees clockwise, or 90 degrees anti clockwise. - /// # Safety - /// - /// UB if the image is not square - #[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 + unsafe fn rot_270(&mut self) { + unsafe { self.as_mut().rot_270() } } } -impl<const CHANNELS: usize, T: AsMut<[u8]> + AsRef<[u8]>> Image<T, CHANNELS> { - /// Rotate an image 180 degrees clockwise. - pub fn rot_180(&mut self) { - self.flatten_mut().reverse(); +impl<const CHANNELS: usize> Rotations for Image<&mut [u8], CHANNELS> { + fn rot_180(&mut self) { + for y in 0..self.height() / 2 { + for x in 0..self.width() { + let p = unsafe { self.pixel(x, y) }; + let x2 = self.width() - x - 1; + let y2 = self.height() - y - 1; + let p2 = unsafe { self.pixel(x2, y2) }; + unsafe { self.set_pixel(x, y, p2) }; + 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 p = unsafe { self.pixel(x, middle) }; + let x2 = self.width() - x - 1; + let p2 = unsafe { self.pixel(x2, middle) }; + unsafe { self.set_pixel(x, middle, p2) }; + unsafe { self.set_pixel(x2, middle, p) }; + } + } } - /// Rotate an image 90 degrees clockwise. - /// # Safety - /// - /// UB if the image is not square #[inline] - pub unsafe fn rot_90(&mut self) { + unsafe fn rot_90(&mut self) { // This is done by first flipping self.flip_v(); - // Then transposing the image, as to not allocate. + // Then transposing the image, to save allocations. // SAFETY: caller ensures square unsafe { transpose(self) }; } - /// Rotate an image 270 degrees clockwise, or 90 degrees anti clockwise. - /// # Safety - /// - /// UB if the image is not square #[inline] - pub unsafe fn rot_270(&mut self) { + unsafe fn rot_270(&mut self) { self.flip_h(); // SAFETY: caller ensures squareness unsafe { transpose(self) }; } } -/// 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, 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>, -) { +unsafe fn transpose<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { debug_assert_eq!(img.width(), img.height()); let size = img.width() as usize; - let b = img.flatten_mut(); + // SAFETY: no half pixels + let b = unsafe { img.buffer.as_chunks_unchecked_mut::<CHANNELS>() }; for i in 0..size { for j in i..size { // SAFETY: caller ensures squarity - 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)) }; - } + unsafe { + b.swap_unchecked(i * size + j, j * size + i); + }; } } } @@ -302,62 +141,6 @@ 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] @@ -436,3 +219,35 @@ mod tests { ); } } + +#[cfg(test)] +mod bench { + use super::*; + extern crate test; + use crate::Image; + use test::Bencher; + + macro_rules! bench { + (fn $name: ident() { run $fn: ident() }) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut img: Image<_, 4> = Image::new( + 64.try_into().unwrap(), + 64.try_into().unwrap(), + include_bytes!("../test_data/4_180x180.imgbuf").to_vec(), + ); + b.iter(|| { + for _ in 0..256 { + img.flip_h(); + } + }); + } + }; + } + + bench!(fn flip_h() { run flip_h() }); + bench!(fn flip_v() { run flip_v() }); + bench!(fn rotate_90() { run rot_90() }); + bench!(fn rotate_180() { run rot_180() }); + bench!(fn rotate_270() { run rot_270() }); +} |