//! Manages the affine image transformations. use crate::{Image, cloner::ImageCloner}; impl 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, 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 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, 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 + AsRef<[u8]>> Image { /// Flip an image vertically. pub 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)) } } } } /// Flip an image horizontally. pub 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)) } } } } } impl 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, 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 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, CHANNELS> { // SAFETY: yep let mut out = unsafe { transpose_out(self) }; // SAFETY: sqar unsafe { crev(out.as_mut()) }; out } /// 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, CHANNELS> { // SAFETY: yep let mut out = unsafe { transpose_out(self) }; out.flip_v(); out } } impl + AsRef<[u8]>> Image { /// Rotate an image 180 degrees clockwise. pub fn rot_180(&mut self) { self.flatten_mut().reverse(); } /// Rotate an image 90 degrees clockwise. /// # Safety /// /// UB if the image is not square #[inline] pub unsafe fn rot_90(&mut self) { // This is done by first flipping self.flip_v(); // Then transposing the image, as to not allocate. // 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) { 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 + AsRef<[u8]>>(mut img: Image) { 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( i: &ImageCloner<'_, CHANNELS>, ) -> Image, 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 + AsRef<[u8]>>( img: &mut Image, ) { 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 + AsRef<[u8]>>( img: &mut Image, ) { debug_assert_eq!(img.width(), img.height()); let size = img.width() as usize; 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) }; } } } /// break it down until const TILE: usize = 4; /// # Safety /// /// be careful unsafe fn transpose_tile + AsRef<[u8]>>( img: &mut Image, 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 + AsRef<[u8]>>( img: &mut Image, 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)) }; } } } } #[cfg(test)] mod tests { use super::*; 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] [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] ] ); } }