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