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);
}