fast image operations
bendn 2023-11-01
parent 24898eb · commit c02376b
-rw-r--r--Cargo.toml4
-rw-r--r--README.md3
-rw-r--r--src/blur.rs51
-rw-r--r--src/convert.rs54
-rw-r--r--src/lib.rs3
-rw-r--r--src/pixels/convert.rs2
-rw-r--r--tdata/blurred_pentagon.imgbufbin0 -> 90000 bytes
7 files changed, 114 insertions, 3 deletions
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 <[email protected]>"]
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<T: AsMut<[u32]> + AsRef<[u32]>> Image<T, 1> {
+ /// 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<const N: usize> Image<Box<[u8]>, N>
+where
+ [u8; 4]: PFrom<N>,
+ [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::<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;
+ }
+ }
+}
+
+impl<const N: usize> Image<&[u8], N>
+where
+ [u8; 4]: PFrom<N>,
+ [u8; N]: PFrom<4>,
+{
+ /// 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()
+ }
+}
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<const N: usize> From<Image<&[u8], N>> for Image<Box<[u32]>, 1>
+where
+ [u8; 4]: PFrom<N>,
+{
+ /// 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<const N: usize>(buffer: &[u32]) -> impl Iterator<Item = u8> + '_
+where
+ [u8; N]: PFrom<4>,
+{
+ buffer
+ .iter()
+ .copied()
+ .map(unpack)
+ .flat_map(<[u8; N] as PFrom<4>>::pfrom)
+}
+
+impl<const N: usize> From<Image<&[u32], 1>> for Image<Box<[u8]>, 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
--- /dev/null
+++ b/tdata/blurred_pentagon.imgbuf
Binary files differ