diff --git a/Cargo.toml b/Cargo.toml index 721b047..ed1b715 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fimg" -version = "0.4.22" +version = "0.4.23" authors = ["bend-n "] license = "MIT" edition = "2021" @@ -17,6 +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 } [dev-dependencies] iai = { git = "https://github.com/bend-n/iai.git" } @@ -50,6 +51,7 @@ harness = false scale = ["fr"] save = ["png"] text = ["fontdue"] +blur = ["stackblur-iter"] default = ["save", "scale"] [profile.release] diff --git a/README.md b/README.md index 7ad63a6..90be93f 100644 --- a/README.md +++ b/README.md @@ -14,4 +14,5 @@ quick simple image operations - [x] box drawing - [x] polygon drawing - [x] circle drawing -- [x] text drawing \ No newline at end of file +- [x] text drawing +- [x] blur \ No newline at end of file diff --git a/src/blur.rs b/src/blur.rs new file mode 100644 index 0000000..62ec9fa --- /dev/null +++ b/src/blur.rs @@ -0,0 +1,51 @@ +use stackblur_iter::imgref::ImgRefMut; + +use crate::{pixels::convert::PFrom, Image}; + +impl + AsRef<[u32]>> Image { + /// Blur a image of packed 32 bit integers, `[0xAARRGGBB]`. + 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) + } +} + +impl Image, N> +where + [u8; 4]: PFrom, + [u8; N]: PFrom<4>, +{ + /// Blur a image. + /// ``` + /// # use fimg::Image; + /// let mut i = Image::alloc(300, 300).boxed(); + /// // 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()) + /// ``` + 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; + } + } +} + +impl Image<&[u8], N> +where + [u8; 4]: PFrom, + [u8; N]: PFrom<4>, +{ + /// 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() + } +} diff --git a/src/convert.rs b/src/convert.rs index 5704f26..6f44116 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -62,3 +62,57 @@ boxconv!(3 => 4); boxconv!(4 => 1); boxconv!(4 => 2); boxconv!(4 => 3); + +#[inline] +fn pack([r, g, b, a]: [u8; 4]) -> u32 { + ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) +} + +#[inline] +fn unpack(n: u32) -> [u8; 4] { + [ + ((n >> 16) & 0xFF) as u8, + ((n >> 8) & 0xFF) as u8, + (n & 0xFF) as u8, + ((n >> 24) & 0xFF) as u8, + ] +} + +impl From> for Image, 1> +where + [u8; 4]: PFrom, +{ + /// Pack into ARGB. + fn from(value: Image<&[u8], N>) -> Self { + let buf = value + .chunked() + .copied() + .map(PFrom::pfrom) + .map(pack) + .collect(); + // SAFETY: ctor + unsafe { Self::new(value.width, value.height, buf) } + } +} + +pub fn unpack_all(buffer: &[u32]) -> impl Iterator + '_ +where + [u8; N]: PFrom<4>, +{ + buffer + .iter() + .copied() + .map(unpack) + .flat_map(<[u8; N] as PFrom<4>>::pfrom) +} + +impl From> for Image, N> +where + [u8; N]: PFrom<4>, +{ + fn from(value: Image<&[u32], 1>) -> Self { + let buf = unpack_all(value.buffer).collect(); + // SAFETY: ctor + unsafe { Self::new(value.width, value.height, buf) } + } +} diff --git a/src/lib.rs b/src/lib.rs index e325c6c..52bc7ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ //! Misc image ops: //! - [`Image::repeated`] //! - [`Image::overlay`](Overlay), [`Image::overlay_at`](OverlayAt), [`Image::overlay_blended`](BlendingOverlay) +//! - [`Image::blur`] #![feature( slice_swap_unchecked, generic_const_exprs, @@ -60,6 +61,8 @@ use std::{num::NonZeroU32, slice::SliceIndex}; mod affine; +#[cfg(feature = "blur")] +mod blur; #[doc(hidden)] pub mod builder; #[doc(hidden)] diff --git a/src/pixels/convert.rs b/src/pixels/convert.rs index 55c9338..13b7e62 100644 --- a/src/pixels/convert.rs +++ b/src/pixels/convert.rs @@ -24,7 +24,7 @@ impl PFrom<2> for Y { impl PFrom<3> for Y { fn pfrom([r, g, b]: RGB) -> Self { - [((2126 * r as u16 + 7152 * g as u16 + 722 * b as u16) / 10000) as u8] + [((2126 * r as u32 + 7152 * g as u32 + 722 * b as u32) / 10000) as u8] } } diff --git a/tdata/blurred_pentagon.imgbuf b/tdata/blurred_pentagon.imgbuf new file mode 100644 index 0000000..9580840 Binary files /dev/null and b/tdata/blurred_pentagon.imgbuf differ