From 829cfa680e8a11c33afc13293d90533c8d5660ca Mon Sep 17 00:00:00 2001 From: bendn Date: Mon, 25 Sep 2023 13:48:58 +0700 Subject: [PATCH] add a ImageCloner --- Cargo.toml | 2 +- src/affine.rs | 144 ++++++++++++++++++++++++++++++++++--------------- src/cloner.rs | 39 ++++++++++++++ src/lib.rs | 83 ++++++++++++++++++---------- src/overlay.rs | 66 +++++++++++++++++++++++ 5 files changed, 261 insertions(+), 73 deletions(-) create mode 100644 src/cloner.rs diff --git a/Cargo.toml b/Cargo.toml index c2a25cb..e020d1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fimg" -version = "0.4.3" +version = "0.4.4" authors = ["bend-n "] license = "MIT" edition = "2021" diff --git a/src/affine.rs b/src/affine.rs index 5ec795a..d980cb6 100644 --- a/src/affine.rs +++ b/src/affine.rs @@ -1,19 +1,58 @@ //! Manages the affine image transformations. -use crate::Image; +use crate::{cloner::ImageCloner, Image}; impl Image, CHANNELS> { - /// Flip a image horizontally. + /// Flip an image horizontally. pub fn flip_h(&mut self) { self.as_mut().flip_h(); } - /// Flip a image vertically. + /// Flip an image vertically. pub fn flip_v(&mut self) { self.as_mut().flip_v(); } } +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]); + /// ``` + pub fn flip_v(&self) -> Image, CHANNELS> { + let mut out = self.alloc(); + 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.set_pixel(x, self.height() - y - 1, p) }; + } + } + out + } + + /// 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]); + /// ``` + pub fn flip_h(&self) -> Image, CHANNELS> { + let mut out = self.alloc(); + 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.set_pixel(self.width() - x - 1, y, p) }; + } + } + out + } +} impl Image<&mut [u8], CHANNELS> { - /// Flip a image vertically. + /// Flip an image vertically. pub fn flip_v(&mut self) { for y in 0..self.height() / 2 { for x in 0..self.width() { @@ -30,7 +69,7 @@ impl Image<&mut [u8], CHANNELS> { } } - /// 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 { @@ -49,12 +88,12 @@ impl Image<&mut [u8], CHANNELS> { } impl Image, CHANNELS> { - /// Rotate a image 180 degrees clockwise. + /// Rotate an image 180 degrees clockwise. pub fn rot_180(&mut self) { self.as_mut().rot_180(); } - /// Rotate a image 90 degrees clockwise. + /// Rotate an image 90 degrees clockwise. /// # Safety /// /// UB if the image is not square @@ -63,7 +102,7 @@ impl Image, CHANNELS> { unsafe { self.as_mut().rot_90() } } - /// 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 @@ -73,42 +112,60 @@ impl Image, CHANNELS> { } } -impl Image<&mut [u8], CHANNELS> { - /// Rotate a 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); - } - } +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]); + /// ``` + 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) }; + let (v, _, c) = v.into_raw_parts(); + let s = s * CHANNELS; + // SAFETY: init with with_cap, set len to s, s is init amount, chunked returns nm, capacity handled, flatten vec + let v = unsafe { Vec::from_raw_parts(v.cast::(), s, c * CHANNELS) }; + // SAFETY: s is w * h. + unsafe { Image::new(self.width, self.height, v) } } - /// 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(&self) -> Image, CHANNELS> { + let mut out = self.flip_v(); + // SAFETY: sqar + unsafe { transpose(&mut out.as_mut()) }; + out + } + + /// Rotate an image 270 degrees clockwise, or 90 degrees anti clockwise. + /// # Safety + /// + /// UB if the image is not square + pub unsafe fn rot_270(&self) -> Image, CHANNELS> { + let mut out = self.flip_h(); + // SAFETY: sqar + unsafe { transpose(&mut out.as_mut()) }; + out + } +} + +impl Image<&mut [u8], CHANNELS> { + /// 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 @@ -121,7 +178,7 @@ impl 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 @@ -156,8 +213,7 @@ unsafe fn transpose(img: &mut Image<&mut [u8], CHANNELS>) unsafe fn transpose_non_power_of_two(img: &mut Image<&mut [u8], 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::() }; + let b = img.flatten_mut(); for i in 0..size { for j in i..size { // SAFETY: caller ensures squarity diff --git a/src/cloner.rs b/src/cloner.rs new file mode 100644 index 0000000..b38aedd --- /dev/null +++ b/src/cloner.rs @@ -0,0 +1,39 @@ +//! provides a [`ImageCloner`] +//! +//! ``` +//! # use fimg::Image; +//! # let i = Image::<_, 1>::alloc(5, 5); +//! unsafe { i.cloner().rot_270() }; +//! ``` +use crate::Image; + +/// A neat way to clone a image. +/// +/// Consider it a way to clone->apply a image operation, but better. +/// Please note that some methods may(although none at current) have different safety invariants from their in place counterparts. +pub struct ImageCloner<'a, const C: usize>(Image<&'a [u8], C>); + +impl<'a, const C: usize> ImageCloner<'a, C> { + /// duplicate the inner image. + pub(crate) fn dup(&self) -> Image, C> { + self.0.to_owned() + } + + /// Create a [`ImageCloner`] from a [Image]<&\[[u8]\]> + pub const fn from(i: Image<&'a [u8], C>) -> Self { + Self(i) + } + + /// Alloc a buffer the right size for use + pub(crate) fn alloc(&self) -> Image, C> { + Image::alloc(self.width(), self.height()) + } +} + +impl<'a, const C: usize> std::ops::Deref for ImageCloner<'a, C> { + type Target = Image<&'a [u8], C>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/src/lib.rs b/src/lib.rs index 9388a3f..b42f557 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ slice_swap_unchecked, stmt_expr_attributes, generic_const_exprs, + vec_into_raw_parts, slice_as_chunks, unchecked_math, portable_simd, @@ -28,9 +29,11 @@ use std::{num::NonZeroU32, slice::SliceIndex}; mod affine; pub mod builder; +pub mod cloner; mod drawing; mod overlay; pub mod scale; +use cloner::ImageCloner; pub use overlay::{Overlay, OverlayAt}; /// like assert!(), but causes undefined behaviour at runtime when the condition is not met. @@ -81,7 +84,7 @@ unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize { } /// A image with a variable number of channels, and a nonzero size. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct Image { /// column order 2d slice/vec buffer: T, @@ -91,6 +94,37 @@ pub struct Image { height: NonZeroU32, } +impl Clone for Image { + /// Returns a duplicate of this image. + /// ``` + /// # use fimg::Image; + /// # let i = Image::, 1>::alloc(5,5); + /// let new_i = i.clone(); + /// ``` + /// If you find yourself in the pattern of + /// ``` + /// # use fimg::Image; + /// # let i = Image::, 1>::alloc(5,5); + /// let mut i = i.clone(); + /// unsafe { i.rot_90() }; + /// ``` + /// STOP! + /// + /// Instead use + /// ``` + /// # use fimg::Image; + /// # let i = Image::, 1>::alloc(5,5); + /// let i = unsafe { i.cloner().rot_90() }; + /// ``` + fn clone(&self) -> Self { + Self { + buffer: self.buffer.clone(), + width: self.width, + height: self.height, + } + } +} + impl Image { #[inline] /// get the height as a [`u32`] @@ -156,6 +190,8 @@ impl Image<&mut [T], CHANNELS> { } } +impl Copy for Image<&[u8], CHANNELS> {} + impl Image<&[u8], CHANNELS> { #[inline] #[must_use] @@ -209,9 +245,20 @@ impl, const CHANNELS: usize> Image ImageCloner<'_, CHANNELS> { + ImageCloner::from(self.as_ref()) + } + + /// Reference this image. + pub fn as_ref(&self) -> Image<&[u8], CHANNELS> { + // SAFETY: we got constructed okay, parameters must be valid + unsafe { Image::new(self.width, self.height, &*self.buffer) } + } + #[inline] /// Returns a iterator over every pixel - pub fn chunked(&self) -> impl Iterator { + pub fn chunked(&self) -> impl DoubleEndedIterator { // SAFETY: 0 sized images illegal unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) }; // SAFETY: no half pixels! @@ -266,6 +313,12 @@ impl, const CHANNELS: usize> Image() } + /// Create a mutref to this image + pub fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> { + // SAFETY: construction went okay + unsafe { Image::new(self.width, self.height, &mut self.buffer) } + } + #[inline] /// Flatten the chunks of this image into a mutable slice of slices. pub fn flatten_mut(&mut self) -> &mut [[u8; CHANNELS]] { @@ -288,12 +341,6 @@ impl, const CHANNELS: usize> Image Image<&mut [u8], CHANNELS> { - /// Downcast the mutable reference - pub fn as_ref(&self) -> Image<&[u8], CHANNELS> { - // SAFETY: we got constructed okay, parameters must be valid - unsafe { Image::new(self.width, self.height, self.buffer) } - } - /// Copy this ref image pub fn copy(&mut self) -> Image<&mut [u8], CHANNELS> { #[allow(clippy::undocumented_unsafe_blocks)] @@ -303,26 +350,6 @@ impl Image<&mut [u8], CHANNELS> { } } -impl Image, CHANNELS> { - /// Create a reference to this owned image - pub fn as_ref(&self) -> Image<&[u8], CHANNELS> { - #[allow(clippy::undocumented_unsafe_blocks)] - unsafe { - Image::new(self.width, self.height, &self.buffer) - } - } -} - -impl Image, CHANNELS> { - /// Create a mutable reference to this owned image - pub fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> { - #[allow(clippy::undocumented_unsafe_blocks)] - unsafe { - Image::new(self.width, self.height, &mut self.buffer) - } - } -} - impl Image, CHANNELS> { /// Allocates a new image /// diff --git a/src/overlay.rs b/src/overlay.rs index 3dbc45e..eb7ac29 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -1,4 +1,6 @@ //! Handles image overlay +use crate::cloner::ImageCloner; + use super::{assert_unchecked, really_unsafe_index, Image}; use std::simd::SimdInt; use std::simd::SimdPartialOrd; @@ -12,6 +14,16 @@ pub trait OverlayAt { /// UB if x, y is out of bounds unsafe fn overlay_at(&mut self, with: &W, x: u32, y: u32) -> &mut Self; } + +/// [`OverlayAt`] but owned +pub trait ClonerOverlayAt { + /// Overlay with => self at coordinates x, y, without blending, and returning a new image. + /// # Safety + /// + /// UB if x, y is out of bounds + unsafe fn overlay_at(&self, with: &Image<&[u8], W>, x: u32, y: u32) -> Image, C>; +} + /// Trait for layering images ontop of each other. /// Think `magick a b -layers flatten a` pub trait Overlay { @@ -22,6 +34,15 @@ pub trait Overlay { unsafe fn overlay(&mut self, with: &W) -> &mut Self; } +/// [`Overlay`] but owned +pub trait ClonerOverlay { + /// Overlay with => self (does not blend) + /// # Safety + /// + /// UB if a.width != b.width || a.height != b.height + unsafe fn overlay(&self, with: &Image<&[u8], W>) -> Image, C>; +} + #[inline] /// SIMD accelerated rgba => rgb overlay. /// @@ -88,6 +109,16 @@ impl Overlay> for Image<&mut [u8], 4> { } } +impl ClonerOverlay<4, 4> for ImageCloner<'_, 4> { + #[inline] + unsafe fn overlay(&self, with: &Image<&[u8], 4>) -> Image, 4> { + let mut out = self.dup(); + // SAFETY: same + unsafe { out.as_mut().overlay(with) }; + out + } +} + impl OverlayAt> for Image<&mut [u8], 3> { #[inline] unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self { @@ -113,6 +144,15 @@ impl OverlayAt> for Image<&mut [u8], 3> { } } +impl ClonerOverlayAt<4, 3> for ImageCloner<'_, 3> { + unsafe fn overlay_at(&self, with: &Image<&[u8], 4>, x: u32, y: u32) -> Image, 3> { + let mut new = self.dup(); + // SAFETY: same + unsafe { new.as_mut().overlay_at(with, x, y) }; + new + } +} + impl OverlayAt> for Image<&mut [u8], 3> { /// Overlay a RGB image(with) => self at coordinates x, y. /// As this is a `RGBxRGB` operation, blending is unnecessary, @@ -175,6 +215,16 @@ impl Overlay> for Image<&mut [u8], 3> { } } +impl ClonerOverlay<4, 3> for ImageCloner<'_, 3> { + #[inline] + unsafe fn overlay(&self, with: &Image<&[u8], 4>) -> Image, 3> { + let mut out = self.dup(); + // SAFETY: same + unsafe { out.as_mut().overlay(with) }; + out + } +} + impl OverlayAt> for Image<&mut [u8], 4> { #[inline] /// Overlay with => self at coordinates x, y, without blending @@ -210,3 +260,19 @@ impl OverlayAt> for Image<&mut [u8], 4> { self } } + +impl ClonerOverlayAt<4, 4> for ImageCloner<'_, 4> { + #[inline] + /// Overlay with => self at coordinates x, y, without blending, returning a new Image + /// + /// # Safety + /// - UB if x, y is out of bounds + /// - UB if x + with.width() > [`u32::MAX`] + /// - UB if y + with.height() > [`u32::MAX`] + unsafe fn overlay_at(&self, with: &Image<&[u8], 4>, x: u32, y: u32) -> Image, 4> { + let mut out = self.dup(); + // SAFETY: same + unsafe { out.as_mut().overlay_at(with, x, y) }; + out + } +}