fast image operations
Diffstat (limited to 'src/affine.rs')
| -rw-r--r-- | src/affine.rs | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/src/affine.rs b/src/affine.rs new file mode 100644 index 0000000..18f19ee --- /dev/null +++ b/src/affine.rs @@ -0,0 +1,255 @@ +use crate::{FromRefMut, Image}; + +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); +} + +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> 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; + // SAFETY: within bounds + 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) }; + } + } + } + + fn flip_h(&mut self) { + for y in 0..self.height() { + for x in 0..self.width() / 2 { + let x2 = self.width() - x - 1; + 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> Rotations for Image<Vec<u8>, CHANNELS> { + fn rot_180(&mut self) { + self.as_mut().rot_180(); + } + + unsafe fn rot_90(&mut self) { + unsafe { self.as_mut().rot_90() } + } + + unsafe fn rot_270(&mut self) { + unsafe { self.as_mut().rot_270() } + } +} + +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) }; + } + } + } + + #[inline] + unsafe fn rot_90(&mut self) { + // This is done by first flipping + self.flip_v(); + // Then transposing the image, to save allocations. + // SAFETY: caller ensures rectangularity + unsafe { transpose(self) }; + } + + #[inline] + unsafe fn rot_270(&mut self) { + self.flip_h(); + // SAFETY: caller ensures rectangularity + unsafe { transpose(self) }; + } +} + +/// # Safety +/// +/// UB if supplied image rectangular +unsafe fn transpose<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + debug_assert_eq!(img.width(), img.height()); + let size = img.width(); + for i in 0..size { + for j in i..size { + for c in 0..CHANNELS { + // SAFETY: caller gurantees rectangularity + unsafe { + img.buffer.swap_unchecked( + (i * size + j) as usize * CHANNELS + c, + (j * size + i) as usize * CHANNELS + c, + ); + }; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::img; + + #[test] + fn rotate_90() { + let mut from = img![ + [00, 01] + [02, 10] + ]; + unsafe { from.rot_90() }; + assert_eq!( + from, + img![ + [02, 00] + [10, 01] + ] + ); + } + + #[test] + fn rotate_180() { + let mut from = img![ + [00, 01] + [02, 10] + ]; + from.rot_180(); + assert_eq!( + from, + img![ + [10, 02] + [01, 00] + ] + ); + } + + #[test] + fn rotate_270() { + let mut from = img![ + [00, 01] + [20, 10] + ]; + unsafe { from.rot_270() }; + assert_eq!( + from, + img![ + [01, 10] + [00, 20] + ] + ); + } + + #[test] + fn flip_vertical() { + let mut from = img![ + [90, 01] + [21, 42] + ]; + from.flip_v(); + assert_eq!( + from, + img![ + [21, 42] + [90, 01] + ] + ); + } + #[test] + fn flip_horizontal() { + let mut from = img![ + [90, 01] + [21, 42] + ]; + from.flip_h(); + assert_eq!( + from, + img![ + [01, 90] + [42, 21] + ] + ); + } +} + +#[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() }); +} |