fast image operations
-rw-r--r--Cargo.toml6
-rw-r--r--src/blur.rs164
2 files changed, 145 insertions, 25 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 6594107..a84d271 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "fimg"
-version = "0.4.26"
+version = "0.4.28"
authors = ["bend-n <[email protected]>"]
license = "MIT"
edition = "2021"
@@ -17,7 +17,7 @@ fontdue = { version = "0.7.3", optional = true }
vecto = "0.1.0"
umath = "0.0.7"
fr = { version = "0.1.1", package = "fer", optional = true }
-stackblur-iter = { version = "0.2.0", features = ["simd"], optional = true }
+slur = { version = "0.1.0", optional = true }
clipline = "0.1.1"
minifb = { version = "0.25.0", default-features = false, features = [
"x11",
@@ -56,7 +56,7 @@ harness = false
scale = ["fr"]
save = ["png"]
text = ["fontdue"]
-blur = ["stackblur-iter"]
+blur = ["slur"]
real-show = ["minifb", "text"]
default = ["save", "scale"]
diff --git a/src/blur.rs b/src/blur.rs
index 7f00b8f..a2ad92e 100644
--- a/src/blur.rs
+++ b/src/blur.rs
@@ -1,4 +1,11 @@
-use stackblur_iter::imgref::ImgRefMut;
+use slur::{
+ color::{u32xN, BlurU32},
+ imgref::ImgRefMut,
+};
+use std::{
+ ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
+ simd::Simd,
+};
use crate::Image;
@@ -7,43 +14,156 @@ impl<T: AsMut<[u32]> + AsRef<[u32]>> Image<T, 1> {
pub fn blur_argb(&mut self, radius: usize) {
let w = self.width() as usize;
let h = self.height() as usize;
- stackblur_iter::simd_blur_argb::<4>(&mut ImgRefMut::new(self.buffer.as_mut(), w, h), radius)
+ slur::simd_blur_argb::<4>(&mut ImgRefMut::new(self.buffer.as_mut(), w, h), radius)
}
}
-impl<const N: usize> Image<Box<[u8]>, N>
-where
- [u8; N]: crate::Pack,
-{
+macro_rules! simd {
+ ($n:literal) => {
+ impl<T: AsMut<[u8]> + AsRef<[u8]>> Image<T, $n> {
+ /// Blur a image.
+ pub fn blur_in(&mut self, radius: usize) {
+ let (w, h) = (self.width() as usize, self.height() as usize);
+ let px = self.flatten_mut();
+ slur::blur(
+ &mut ImgRefMut::new(px, w, h),
+ radius,
+ |x| slur::color::u32xN(std::simd::Simd::from_array(x.map(|x| x as u32))),
+ |x| x.0.to_array().map(|x| x as u8),
+ );
+ }
+ }
+ };
+}
+
+simd!(4);
+simd!(2);
+
+impl<T: AsMut<[u8]> + AsRef<[u8]>> Image<T, 3> {
/// Blur a image.
/// ```
/// # use fimg::Image;
/// let mut i = Image::alloc(300, 300).boxed();
+ /// // draw a trongle
+ /// i.poly((150., 150.), 3, 100.0, 0.0, [255, 255, 255]);
+ /// // give it some blur
+ /// i.blur_in(25);
+ /// ```
+ pub fn blur_in(&mut self, radius: usize) {
+ let (w, h) = (self.width() as usize, self.height() as usize);
+ let px = self.flatten_mut();
+ slur::blur(
+ &mut ImgRefMut::new(px, w, h),
+ radius,
+ |x| Px::from(*x),
+ |x| x.into(),
+ );
+ }
+}
+
+impl<T: AsMut<[u8]> + AsRef<[u8]>> Image<T, 1> {
+ /// Blur a image. No copy.
+ /// ```
+ /// # use fimg::Image;
+ /// let mut i = Image::alloc(300, 300);
/// // draw a lil pentagon
/// i.poly((150., 150.), 5, 100.0, 0.0, [255]);
/// // give it some blur
/// i.blur(25);
- /// assert_eq!(include_bytes!("../tdata/blurred_pentagon.imgbuf"), i.bytes())
+ /// # assert_eq!(include_bytes!("../tdata/blurred_pentagon.imgbuf"), i.bytes())
/// ```
pub fn blur(&mut self, radius: usize) {
- // you know, i optimized blurslice a fair bit, and yet, despite all the extra bit twiddling stackblur-iter is faster.
- let mut argb = Image::<Box<[u32]>, 1>::from(self.as_ref());
- argb.blur_argb(radius);
- for (i, n) in crate::convert::unpack_all(&argb.buffer).enumerate() {
- *unsafe { self.buffer.get_unchecked_mut(i) } = n;
+ let (w, h) = (self.width() as usize, self.height() as usize);
+ slur::simd_blur::<_, _, _, 8>(
+ &mut ImgRefMut::new(self.buffer.as_mut(), w, h),
+ radius,
+ |x| u32xN(Simd::from_array(x.map(|&x| x as u32))),
+ |x| x.0.as_array().map(|x| x as u8),
+ |&x| BlurU32(x as u32),
+ |x| x.0 as u8,
+ );
+ }
+}
+
+macro_rules! blur_packing {
+ ($n:literal) => {
+ impl<T: AsRef<[u8]> + AsMut<[u8]>> Image<T, $n> {
+ /// Blur a image. This will allocate a <code>[Image]<[Box]<[[u32]]>, 1></code>.
+ /// If you want no copy, but slower if you dont have a simd-able cpu, check out [`Image::blur_in`].
+ /// ```
+ /// # use fimg::Image;
+ /// let mut i = Image::alloc(300, 300);
+ /// // draw a sqar
+ /// i.poly((150., 150.), 4, 100.0, 0.0, [255]);
+ /// // give it some blur
+ /// i.blur(25);
+ /// ```
+ pub fn blur(&mut self, radius: usize) {
+ // the bit twiddling lets it simd better
+ let mut argb = Image::<Box<[u32]>, 1>::from(self.as_ref());
+ argb.blur_argb(radius);
+ for (i, n) in crate::convert::unpack_all::<$n>(&argb.buffer).enumerate() {
+ *unsafe { self.buffer.as_mut().get_unchecked_mut(i) } = n;
+ }
+ }
}
+ };
+}
+blur_packing!(2);
+blur_packing!(3);
+blur_packing!(4);
+
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+struct Px<const N: usize>([u32; N]);
+
+impl<const N: usize> Default for Px<N> {
+ fn default() -> Self {
+ Self([0; N])
}
}
-impl<const N: usize> Image<&[u8], N>
-where
- [u8; N]: crate::Pack,
-{
- /// Blur a image.
- pub fn blur(self, radius: usize) -> Image<Box<[u8]>, N> {
- let mut argb = Image::<Box<[u32]>, 1>::from(self);
- argb.blur_argb(radius);
- // SAFETY: ctor
- unsafe { Image::new(argb.width, argb.height, &**argb.buffer()) }.into()
+impl<const N: usize> From<[u8; N]> for Px<N> {
+ fn from(x: [u8; N]) -> Self {
+ Self(x.map(|x| x as u32))
}
}
+
+impl<const N: usize> From<Px<N>> for [u8; N] {
+ fn from(v: Px<N>) -> Self {
+ v.0.map(|x| x as u8)
+ }
+}
+
+macro_rules! op {
+ ($name:ident, $as:ident, $fn:ident, $ass_fn:ident, $meth:ident) => {
+ impl<const N: usize> $name<usize> for Px<N> {
+ type Output = Px<N>;
+
+ fn $fn(self, rhs: usize) -> Self::Output {
+ Self(self.0.map(|x| x.$meth(rhs as u32)))
+ }
+ }
+
+ impl<const N: usize> $name for Px<N> {
+ type Output = Px<N>;
+ fn $fn(self, rhs: Px<N>) -> Self::Output {
+ let mut out = [0; N];
+ for ((a, b), x) in self.0.iter().zip(rhs.0.iter()).zip(out.iter_mut()) {
+ *x = a.$meth(*b);
+ }
+ Self(out)
+ }
+ }
+
+ impl<const N: usize> $as for Px<N> {
+ fn $ass_fn(&mut self, rhs: Self) {
+ *self = self.$fn(rhs);
+ }
+ }
+ };
+}
+op!(Mul, MulAssign, mul, mul_assign, wrapping_mul);
+op!(Sub, SubAssign, sub, sub_assign, wrapping_sub);
+op!(Add, AddAssign, add, add_assign, wrapping_add);
+op!(Div, DivAssign, div, div_assign, wrapping_div);