fast image operations
-rw-r--r--Cargo.toml2
-rw-r--r--src/affine.rs140
-rw-r--r--src/cloner.rs39
-rw-r--r--src/lib.rs83
-rw-r--r--src/overlay.rs66
5 files changed, 259 insertions, 71 deletions
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 <[email protected]>"]
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<const CHANNELS: usize> Image<Vec<u8>, 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<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]);
+ /// ```
+ pub fn flip_v(&self) -> Image<Vec<u8>, 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<Vec<u8>, 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<const CHANNELS: usize> 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<const CHANNELS: usize> 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<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
}
impl<const CHANNELS: usize> Image<Vec<u8>, 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<const CHANNELS: usize> Image<Vec<u8>, 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<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
}
}
-impl<const CHANNELS: usize> 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) };
- }
+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]);
+ /// ```
+ 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) };
+ 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::<u8>(), s, c * CHANNELS) };
+ // SAFETY: s is w * h.
+ unsafe { Image::new(self.width, self.height, v) }
+ }
- if self.height() % 2 != 0 {
- let middle = self.height() / 2;
+ /// Rotate an image 90 degrees clockwise.
+ /// # Safety
+ ///
+ /// UB if the image is not square
+ pub unsafe fn rot_90(&self) -> Image<Vec<u8>, CHANNELS> {
+ let mut out = self.flip_v();
+ // SAFETY: sqar
+ unsafe { transpose(&mut out.as_mut()) };
+ out
+ }
- 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);
- }
- }
- }
+ /// 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<Vec<u8>, CHANNELS> {
+ let mut out = self.flip_h();
+ // SAFETY: sqar
+ unsafe { transpose(&mut out.as_mut()) };
+ out
+ }
+}
+
+impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
+ /// Rotate an image 180 degrees clockwise.
+ pub fn rot_180(&mut self) {
+ 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 +178,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
@@ -156,8 +213,7 @@ unsafe fn transpose<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>)
unsafe fn transpose_non_power_of_two<const CHANNELS: usize>(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::<CHANNELS>() };
+ 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<Vec<u8>, C> {
+ self.0.to_owned()
+ }
+
+ /// Create a [`ImageCloner`] from a <code>[Image]<&\[[u8]\]></code>
+ 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<Vec<u8>, 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<T, const CHANNELS: usize> {
/// column order 2d slice/vec
buffer: T,
@@ -91,6 +94,37 @@ pub struct Image<T, const CHANNELS: usize> {
height: NonZeroU32,
}
+impl<T: Clone, const CHANNELS: usize> Clone for Image<T, CHANNELS> {
+ /// Returns a duplicate of this image.
+ /// ```
+ /// # use fimg::Image;
+ /// # let i = Image::<Vec<_>, 1>::alloc(5,5);
+ /// let new_i = i.clone();
+ /// ```
+ /// If you find yourself in the pattern of
+ /// ```
+ /// # use fimg::Image;
+ /// # let i = Image::<Vec<_>, 1>::alloc(5,5);
+ /// let mut i = i.clone();
+ /// unsafe { i.rot_90() };
+ /// ```
+ /// STOP!
+ ///
+ /// Instead use
+ /// ```
+ /// # use fimg::Image;
+ /// # let i = Image::<Vec<_>, 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<T, const CHANNELS: usize> Image<T, CHANNELS> {
#[inline]
/// get the height as a [`u32`]
@@ -156,6 +190,8 @@ impl<const CHANNELS: usize, T: Clone> Image<&mut [T], CHANNELS> {
}
}
+impl<const CHANNELS: usize> Copy for Image<&[u8], CHANNELS> {}
+
impl<const CHANNELS: usize> Image<&[u8], CHANNELS> {
#[inline]
#[must_use]
@@ -209,9 +245,20 @@ impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS
index..unsafe { index.unchecked_add(CHANNELS) }
}
+ /// Procure a [`ImageCloner`].
+ pub fn cloner(&self) -> 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<Item = &[u8; CHANNELS]> {
+ pub fn chunked(&self) -> impl DoubleEndedIterator<Item = &[u8; CHANNELS]> {
// SAFETY: 0 sized images illegal
unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) };
// SAFETY: no half pixels!
@@ -266,6 +313,12 @@ impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANN
self.buffer.array_chunks_mut::<CHANNELS>()
}
+ /// 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<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANN
}
impl<const CHANNELS: usize> 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)]
@@ -304,26 +351,6 @@ impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
}
impl<const CHANNELS: usize> Image<Vec<u8>, 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<const CHANNELS: usize> Image<Vec<u8>, 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<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
/// Allocates a new image
///
/// # Panics
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<W> {
/// 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<const W: usize, const C: usize> {
+ /// 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<Vec<u8>, C>;
+}
+
/// Trait for layering images ontop of each other.
/// Think `magick a b -layers flatten a`
pub trait Overlay<W> {
@@ -22,6 +34,15 @@ pub trait Overlay<W> {
unsafe fn overlay(&mut self, with: &W) -> &mut Self;
}
+/// [`Overlay`] but owned
+pub trait ClonerOverlay<const W: usize, const C: usize> {
+ /// 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<Vec<u8>, C>;
+}
+
#[inline]
/// SIMD accelerated rgba => rgb overlay.
///
@@ -88,6 +109,16 @@ impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 4> {
}
}
+impl ClonerOverlay<4, 4> for ImageCloner<'_, 4> {
+ #[inline]
+ unsafe fn overlay(&self, with: &Image<&[u8], 4>) -> Image<Vec<u8>, 4> {
+ let mut out = self.dup();
+ // SAFETY: same
+ unsafe { out.as_mut().overlay(with) };
+ out
+ }
+}
+
impl OverlayAt<Image<&[u8], 4>> 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<Image<&[u8], 4>> 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<Vec<u8>, 3> {
+ let mut new = self.dup();
+ // SAFETY: same
+ unsafe { new.as_mut().overlay_at(with, x, y) };
+ new
+ }
+}
+
impl OverlayAt<Image<&[u8], 3>> 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<Image<&[u8], 4>> for Image<&mut [u8], 3> {
}
}
+impl ClonerOverlay<4, 3> for ImageCloner<'_, 3> {
+ #[inline]
+ unsafe fn overlay(&self, with: &Image<&[u8], 4>) -> Image<Vec<u8>, 3> {
+ let mut out = self.dup();
+ // SAFETY: same
+ unsafe { out.as_mut().overlay(with) };
+ out
+ }
+}
+
impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 4> {
#[inline]
/// Overlay with => self at coordinates x, y, without blending
@@ -210,3 +260,19 @@ impl OverlayAt<Image<&[u8], 4>> 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<Vec<u8>, 4> {
+ let mut out = self.dup();
+ // SAFETY: same
+ unsafe { out.as_mut().overlay_at(with, x, y) };
+ out
+ }
+}