1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
//! A fast, iterative, correct approach to Stackblur, resulting in a very smooth
//! and high-quality output, with no edge bleeding.
//!
//! This crate implements a tweaked version of the Stackblur algorithm requiring
//! `radius * 2 + 2` elements of space rather than `radius * 2 + 1`, which is a
//! small tradeoff for much-increased visual quality.
//!
//! The algorithm is exposed as an iterator ([`StackBlur`]) that can wrap any
//! other iterator that yields elements of [`StackBlurrable`]. The [`StackBlur`]
//! will then yield elements blurred by the specified radius.
//!
//! ## Benefits of this crate
//!
//! Stackblur is essentially constant-time. Regardless of the radius, it always
//! performs only 1 scan over the input iterator and outputs exactly the same
//! amount of elements.
//!
//! Additionally, it produces results that are comparable to slow and expensive
//! Gaussian blurs. As opposed to box blur which uses a basic rolling average,
//! Stackblur uses a weighted average where each output pixel is affected more
//! strongly by the inputs that were closest to it.
//!
//! Despite that, Stackblur does not perform much worse compared to naive box
//! blurs, and is quite cheap compared to full Gaussian blurs, at least for the
//! CPU. The implementation in this crate will most likely beat most unoptimized
//! blurs you can find on crates.io, as well as some optimized ones, and it is
//! extremely flexible and generic.
//!
//! For a full explanation of the improvements made to the Stackblur algorithm,
//! see the [`iter`] module.
//!
//! ## Comparison to the `stackblur` crate
//!
//! `stackblur` suffers from edge bleeding and flexibility problems. For
//! example, it can only operate on buffers of 32-bit integers, and expects them
//! to be packed linear ARGB pixels. Additionally, it cannot operate on a 2D
//! subslice of a buffer (like `imgref` allows for this crate), and it does not
//! offer any streaming iterators or documentation. And it also only supports
//! a blur radius of up to 255.
//!
//! ## Usage
//!
//! Aside from [`StackBlurrable`] and [`StackBlur`] which host their own
//! documentation, there are helper functions like [`blur`] and [`blur_argb`]
//! that can be used to interact with 2D image buffers, due to the fact that
//! doing so manually involves unsafe code (if you want no-copy).
#![cfg_attr(test, feature(test))]
use std::collections::VecDeque;
pub extern crate imgref;
use imgref::ImgRefMut;
#[cfg(test)]
mod test;
pub mod traits;
pub mod iter;
mod color;
use traits::StackBlurrable;
use iter::StackBlur;
use color::Argb;
/// Blurs a buffer on the X axis.
///
/// The provided closures are used to convert from the buffer's native pixel
/// format to [`StackBlurrable`] values that can be consumed by [`StackBlur`].
///
/// This is the generic version. If you have a common buffer format (packed
/// 32-bit integers), you can use [`blur_horiz_argb`] (linear RGB) or
/// [`blur_horiz_srgb`] (for sRGB).
pub fn blur_horiz<T, B: StackBlurrable>(
buffer: &mut ImgRefMut<T>,
radius: usize,
mut to_blurrable: impl FnMut(&T) -> B,
mut to_pixel: impl FnMut(B) -> T
) {
let mut ops = VecDeque::new();
struct SlicePtrIter<T, B: StackBlurrable, F: FnMut(&T) -> B>(*const [T], F);
impl<T, B: StackBlurrable, F: FnMut(&T) -> B> Iterator for SlicePtrIter<T, B, F> {
type Item = B;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if let Some((first, rest)) = unsafe { (*self.0).split_first() } {
self.0 = rest as *const [T];
Some(self.1(first))
} else {
None
}
}
}
for row in buffer.rows_mut() {
let row = row as *mut [T];
let iter = SlicePtrIter(row, &mut to_blurrable);
let mut blur = StackBlur::new(iter, radius, ops);
let base = row.cast::<T>();
for offset in 0..unsafe { (*row).len() } {
unsafe {
*base.add(offset) = match blur.next().map(&mut to_pixel) {
Some(pixel) => pixel,
None => break
};
}
}
ops = blur.into_ops();
}
}
/// Blurs a buffer on the Y axis.
///
/// The provided closures are used to convert from the buffer's native pixel
/// format to [`StackBlurrable`] values that can be consumed by [`StackBlur`].
///
/// This is the generic version. If you have a common buffer format (packed
/// 32-bit integers), you can use [`blur_vert_argb`] (linear RGB) or
/// [`blur_vert_srgb`] (for sRGB).
pub fn blur_vert<T, B: StackBlurrable>(
buffer: &mut ImgRefMut<T>,
radius: usize,
mut to_blurrable: impl FnMut(&T) -> B,
mut to_pixel: impl FnMut(B) -> T
) {
let mut ops = VecDeque::new();
struct SlicePtrStrideIter<T, B: StackBlurrable, F: FnMut(&T) -> B>(*const [T], F, usize);
impl<T, B: StackBlurrable, F: FnMut(&T) -> B> Iterator for SlicePtrStrideIter<T, B, F> {
type Item = B;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let len = (*self.0).len();
if len > 0 {
let item = &*(self.0 as *mut T);
self.0 = (*self.0).get_unchecked(std::cmp::min(len, self.2)..) as *const [T];
Some(self.1(item))
} else {
None
}
}
}
}
let buf_mut_ptr = *buffer.buf_mut() as *mut [T];
let buf_ptr = buf_mut_ptr as *const [T];
let stride = buffer.stride();
for col in 0..buffer.width() {
let iter = SlicePtrStrideIter(unsafe { (*buf_ptr).get_unchecked(col..) as *const [T] }, &mut to_blurrable, stride);
let mut blur = StackBlur::new(iter, radius, ops);
let base = unsafe { (buf_mut_ptr.cast::<T>()).add(col) };
for row in 0..buffer.height() {
unsafe {
*base.add(row * stride) = match blur.next().map(&mut to_pixel) {
Some(pixel) => pixel,
None => break
};
}
}
ops = blur.into_ops();
}
}
/// Blurs a buffer on the X and Y axes.
///
/// The provided closures are used to convert from the buffer's native pixel
/// format to [`StackBlurrable`] values that can be consumed by [`StackBlur`].
///
/// This is the generic version. If you have a common buffer format (packed
/// 32-bit integers), you can use [`blur_argb`] (linear RGB) or [`blur_srgb`]
/// (for sRGB).
pub fn blur<T, B: StackBlurrable>(
buffer: &mut ImgRefMut<T>,
radius: usize,
mut to_blurrable: impl FnMut(&T) -> B,
mut to_pixel: impl FnMut(B) -> T
) {
blur_horiz(buffer, radius, &mut to_blurrable, &mut to_pixel);
blur_vert(buffer, radius, to_blurrable, to_pixel);
}
/// Blurs a buffer of 32-bit ARGB pixels on the X axis.
///
/// This is a version of [`blur_horiz`] with pre-filled conversion routines that
/// provide good results for blur radii <= 4096. Larger radii may overflow.
///
/// Note that this function is *linear*. For sRGB, see [`blur_horiz_srgb`].
pub fn blur_horiz_argb(buffer: &mut ImgRefMut<u32>, radius: usize) {
blur_horiz(buffer, radius, |i| Argb::from_u32(*i), Argb::to_u32);
}
/// Blurs a buffer of 32-bit ARGB pixels on the Y axis.
///
/// This is a version of [`blur_vert`] with pre-filled conversion routines that
/// provide good results for blur radii <= 4096. Larger radii may overflow.
///
/// Note that this function is *linear*. For sRGB, see [`blur_vert_srgb`].
pub fn blur_vert_argb(buffer: &mut ImgRefMut<u32>, radius: usize) {
blur_vert(buffer, radius, |i| Argb::from_u32(*i), Argb::to_u32);
}
/// Blurs a buffer of 32-bit ARGB pixels on both axes.
///
/// This is a version of [`blur`] with pre-filled conversion routines that
/// provide good results for blur radii <= 4096. Larger radii may overflow.
///
/// Note that this function is *linear*. For sRGB, see [`blur_srgb`].
pub fn blur_argb(buffer: &mut ImgRefMut<u32>, radius: usize) {
blur_horiz_argb(buffer, radius);
blur_vert_argb(buffer, radius);
}
/// Blurs a buffer of 32-bit sRGB pixels on the X axis.
///
/// This is a version of [`blur_horiz`] with pre-filled conversion routines that
/// provide good results for blur radii <= 1536. Larger radii may overflow.
///
/// Note that this function uses *sRGB*. For linear, see [`blur_horiz_argb`].
#[cfg(any(doc, feature = "blend-srgb"))]
pub fn blur_horiz_srgb(buffer: &mut ImgRefMut<u32>, radius: usize) {
blur_horiz(buffer, radius, |i| Argb::from_u32_srgb(*i), Argb::to_u32_srgb);
}
/// Blurs a buffer of 32-bit sRGB pixels on the Y axis.
///
/// This is a version of [`blur_vert`] with pre-filled conversion routines that
/// provide good results for blur radii <= 1536. Larger radii may overflow.
///
/// Note that this function uses *sRGB*. For linear, see [`blur_vert_argb`].
#[cfg(any(doc, feature = "blend-srgb"))]
pub fn blur_vert_srgb(buffer: &mut ImgRefMut<u32>, radius: usize) {
blur_vert(buffer, radius, |i| Argb::from_u32_srgb(*i), Argb::to_u32_srgb);
}
/// Blurs a buffer of 32-bit sRGB pixels on both axes.
///
/// This is a version of [`blur`] with pre-filled conversion routines that
/// provide good results for blur radii <= 1024. Larger radii may overflow.
///
/// Note that this function uses *sRGB*. For linear, see [`blur_argb`].
#[cfg(any(doc, feature = "blend-srgb"))]
pub fn blur_srgb(buffer: &mut ImgRefMut<u32>, radius: usize) {
blur_horiz_srgb(buffer, radius);
blur_vert_srgb(buffer, radius);
}