From df2c7d2436a84515ecdce55549ee7b7545fae9b5 Mon Sep 17 00:00:00 2001 From: bendn Date: Thu, 23 Nov 2023 21:00:09 +0700 Subject: [PATCH] optimize blur and provide in place options --- Cargo.toml | 6 +- src/blur.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 146 insertions(+), 26 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 "] 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 + AsRef<[u32]>> Image { 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 Image, N> -where - [u8; N]: crate::Pack, -{ +macro_rules! simd { + ($n:literal) => { + impl + AsRef<[u8]>> Image { + /// 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 + AsRef<[u8]>> Image { /// 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 + AsRef<[u8]>> Image { + /// 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::, 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, + ); } } -impl Image<&[u8], N> -where - [u8; N]: crate::Pack, -{ - /// Blur a image. - pub fn blur(self, radius: usize) -> Image, N> { - let mut argb = Image::, 1>::from(self); - argb.blur_argb(radius); - // SAFETY: ctor - unsafe { Image::new(argb.width, argb.height, &**argb.buffer()) }.into() +macro_rules! blur_packing { + ($n:literal) => { + impl + AsMut<[u8]>> Image { + /// Blur a image. This will allocate a [Image]<[Box]<[[u32]]>, 1>. + /// 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::, 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([u32; N]); + +impl Default for Px { + fn default() -> Self { + Self([0; N]) } } + +impl From<[u8; N]> for Px { + fn from(x: [u8; N]) -> Self { + Self(x.map(|x| x as u32)) + } +} + +impl From> for [u8; N] { + fn from(v: Px) -> Self { + v.0.map(|x| x as u8) + } +} + +macro_rules! op { + ($name:ident, $as:ident, $fn:ident, $ass_fn:ident, $meth:ident) => { + impl $name for Px { + type Output = Px; + + fn $fn(self, rhs: usize) -> Self::Output { + Self(self.0.map(|x| x.$meth(rhs as u32))) + } + } + + impl $name for Px { + type Output = Px; + fn $fn(self, rhs: Px) -> 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 $as for Px { + 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);