diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs
index 0686aa7a8a..25864cacf6 100644
--- a/src/imageops/sample.rs
+++ b/src/imageops/sample.rs
@@ -887,13 +887,17 @@ where
/// Perform a 3x3 box filter on the supplied image.
///
-/// # Arguments:
+/// # Arguments
///
/// * `image` - source image.
/// * `kernel` - is an array of the filter weights of length 9.
///
+/// # Notes
+///
/// This method typically assumes that the input is scene-linear light.
/// If it is not, color distortion may occur.
+///
+/// This operations uses the clamp/replicate abyss policy. I.e. `aaa|abcdef|fff`.
pub fn filter3x3(image: &I, kernel: &[f32; 9]) -> ImageBuffer
>
where
I: GenericImageView,
@@ -901,20 +905,29 @@ where
S: Primitive,
{
// The kernel's input positions relative to the current pixel.
- let taps: &[(isize, isize)] = &[
- (-1, -1),
- (0, -1),
- (1, -1),
- (-1, 0),
+ // Each coordinate -1, 0, 1 is offset by 1 to make them unsigned.
+ let taps: &[(u32, u32)] = &[
(0, 0),
(1, 0),
- (-1, 1),
+ (2, 0),
(0, 1),
(1, 1),
+ (2, 1),
+ (0, 2),
+ (1, 2),
+ (2, 2),
];
let (width, height) = image.dimensions();
let mut out = image.buffer_like();
+ if width == 0 || height == 0 {
+ return out;
+ }
+
+ assert!(
+ width < u32::MAX && height < u32::MAX,
+ "Image dimensions too large"
+ );
let max = S::DEFAULT_MAX_VALUE;
let max: f32 = NumCast::from(max).unwrap();
@@ -924,18 +937,20 @@ where
sum => 1.0 / sum,
};
- for y in 1..height - 1 {
- for x in 1..width - 1 {
+ for y in 0..height {
+ for x in 0..width {
let mut t = [0.0; MAX_CHANNEL];
// TODO: There is no need to recalculate the kernel for each pixel.
// Only a subtract and addition is needed for pixels after the first
// in each row.
for (&k, &(a, b)) in kernel.iter().zip(taps.iter()) {
- let x0 = x as isize + a;
- let y0 = y as isize + b;
+ // `x + a` won't overflow since x∈[0,u32::MAX-2] (because x < width < u32::MAX)
+ // and a∈[0,2]. Similar for `y + b`.
+ let x0 = (x + a).saturating_sub(1).min(width - 1);
+ let y0 = (y + b).saturating_sub(1).min(height - 1);
- let p = image.get_pixel(x0 as u32, y0 as u32);
+ let p = image.get_pixel(x0, y0);
for (tc, &c) in t.iter_mut().zip(p.channels()) {
*tc += ::from(c).unwrap() * k;
diff --git a/src/images/dynimage.rs b/src/images/dynimage.rs
index 862b999550..594325dd56 100644
--- a/src/images/dynimage.rs
+++ b/src/images/dynimage.rs
@@ -1284,9 +1284,13 @@ impl DynamicImage {
///
/// * `kernel` - array contains filter.
///
+ /// # Notes
+ ///
/// This method typically assumes that the input is scene-linear light. It operates on pixel
/// channel values directly without taking into account color space data. If it is not, color
/// distortion may occur.
+ ///
+ /// This operations uses the clamp/replicate abyss policy. I.e. `aaa|abcdef|fff`.
#[must_use]
pub fn filter3x3(&self, kernel: &[f32; 9]) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::filter3x3(p, kernel))