diff --git a/src/filter/mod.rs b/src/filter/mod.rs index f04abbce..04b4a6d1 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -12,6 +12,7 @@ mod box_filter; pub use self::box_filter::box_filter; use image::{GenericImageView, GrayImage, Luma, Pixel, Primitive}; +use itertools::Itertools; use crate::definitions::{Clamp, Image}; use crate::kernel::{self, Kernel}; @@ -21,107 +22,131 @@ use num::Num; use std::cmp::{max, min}; use std::f32; -/// Returns 2d correlation of an image. Intermediate calculations are performed -/// at type K, and the results converted to pixel Q via f. Pads by continuity. +/// Calculates the new pixel value for a particular pixel and kernel. /// /// # Panics +/// /// If `P::CHANNEL_COUNT != Q::CHANNEL_COUNT` -pub fn filter
(image: &Image
, kernel: Kernel (x: u32, y: u32, kernel: Kernel ) -> Q
where
P: Pixel,
- ::Subpixel: Into (image: &Image , kernel: Kernel (image: &Image , kernel: Kernel (image: &Image , kernel: Kernel ::Subpixel>,
S: Clamp ::Subpixel>,
{
filter(image, kernel, S::clamp)
}
-
#[cfg(feature = "rayon")]
#[doc = generate_parallel_doc_comment!("filter_clamped")]
pub fn filter_clamped_parallel (
@@ -218,11 +242,12 @@ pub fn filter_clamped_parallel (
kernel: Kernel >::Pixel: Send,
- K: Num + Copy + From< ::Subpixel> + Sync,
- S: Clamp >::Pixel: Send + Sync,
+ S: Clamp (acc: &mut [K], pixel: &P, weight: K)
where
P: Pixel,
- P::Subpixel: Into ::Subpixel: Into (acc: &mut [K], out_channels: &mut [P::Subpixel], zero: K)
@@ -442,7 +467,6 @@ where
pub fn laplacian_filter(image: &GrayImage) -> Image (
/// # }
pub fn gradients (
image: &Image ,
- horizontal_kernel: Kernel : HasBlack,
F: Fn(ChannelMap ) -> Q,
{
- let horizontal = filter_clamped::<_, _, i16>(image, horizontal_kernel);
- let vertical = filter_clamped::<_, _, i16>(image, vertical_kernel);
+ let pass1: Image ::black();
for (h, v, p) in multizip((h.channels(), v.channels(), p.channels_mut())) {
diff --git a/src/kernel.rs b/src/kernel.rs
index 70a2c389..aa940054 100644
--- a/src/kernel.rs
+++ b/src/kernel.rs
@@ -2,19 +2,13 @@
/// An borrowed 2D kernel, used to filter images via convolution.
#[derive(Debug, Copy, Clone)]
-pub struct Kernel<'a, K>
-where
- K: Copy,
-{
+pub struct Kernel<'a, K> {
pub(crate) data: &'a [K],
pub(crate) width: u32,
pub(crate) height: u32,
}
-impl<'a, K> Kernel<'a, K>
-where
- K: Copy,
-{
+impl<'a, K> Kernel<'a, K> {
/// Construct a kernel from a slice and its dimensions. The input slice is
/// in row-major order.
///
@@ -35,28 +29,10 @@ where
///
/// # Panics
///
- /// If the `y * kernel.width + x` is outside of the kernel data.
+ /// If the `x` or `y` is outside of the width or height of the kernel.
#[inline]
- pub fn get(&self, x: u32, y: u32) -> K {
- debug_assert!(x < self.width);
- debug_assert!(y < self.height);
- let at = usize::try_from(y * self.width + x).unwrap();
- self.data[at]
- }
-
- /// Get the value in the kernel at the given `x` and `y` position, without
- /// doing bounds checking.
- ///
- /// # Safety
- /// The caller must ensure that `y * self.width + x` is in bounds of the
- /// kernel data.
- #[inline]
- pub unsafe fn get_unchecked(&self, x: u32, y: u32) -> K {
- debug_assert!(x < self.width);
- debug_assert!(y < self.height);
- let at = usize::try_from(y * self.width + x).unwrap();
- debug_assert!(at < self.data.len());
- *self.data.get_unchecked(at)
+ pub fn at(&self, x: u32, y: u32) -> &K {
+ &self.data[(y * self.width + x) as usize]
}
}
+fn filter_pixel
+where
+ P: Pixel,
+ Q: Pixel,
+ F: Fn(K) -> Q::Subpixel,
+ K: num::Num + Copy + From
::new(width, height);
- let mut acc = vec![zero; num_channels];
- let (k_width, k_height) = (kernel.width as i64, kernel.height as i64);
- let (width, height) = (width as i64, height as i64);
for y in 0..height {
for x in 0..width {
- for k_y in 0..k_height {
- let y_p = min(height - 1, max(0, y + k_y - k_height / 2)) as u32;
- for k_x in 0..k_width {
- let x_p = min(width - 1, max(0, x + k_x - k_width / 2)) as u32;
-
- debug_assert!(image.in_bounds(x_p, y_p));
- accumulate(
- &mut acc,
- unsafe { &image.unsafe_get_pixel(x_p, y_p) },
- unsafe { kernel.get_unchecked(k_x as u32, k_y as u32) },
- );
- }
- }
- let out_channels = out.get_pixel_mut(x as u32, y as u32).channels_mut();
- for (a, c) in acc.iter_mut().zip(out_channels) {
- *c = f(*a);
- *a = zero;
- }
+ out.put_pixel(x, y, filter_pixel(x, y, kernel, &f, image));
}
}
+
out
}
-
#[cfg(feature = "rayon")]
#[doc = generate_parallel_doc_comment!("filter")]
pub fn filter_parallel
where
P: Pixel + Sync,
- P::Subpixel: Into
= Image::new(width, height);
- for x in 0..width {
- for k_y in 0..k_height {
- let y_p = min(height - 1, max(0, y + k_y - k_height / 2)) as u32;
- for k_x in 0..k_width {
- let x_p = min(width - 1, max(0, x + k_x - k_width / 2)) as u32;
-
- debug_assert!(image.in_bounds(x_p, y_p));
- accumulate(
- &mut acc,
- unsafe { &image.unsafe_get_pixel(x_p, y_p) },
- unsafe { kernel.get_unchecked(k_x as u32, k_y as u32) },
- );
- }
- }
- for (a, c) in acc.iter_mut().zip(out_pixel.iter_mut()) {
- *c = f(*a);
- *a = K::zero();
- }
- out_row.push(*Q::from_slice(&out_pixel));
- }
- out_row
- })
- .collect();
+ image
+ .par_enumerate_pixels()
+ .zip_eq(out.par_pixels_mut())
+ .for_each(move |((x, y, _), output_pixel)| {
+ *output_pixel = filter_pixel(x, y, kernel, &f, image);
+ });
- Image::from_fn(image.width(), image.height(), |x, y| {
- out_rows[y as usize][x as usize]
- })
+ out
}
#[inline]
@@ -203,14 +228,13 @@ where
/// the crate `rayon` feature is enabled.
pub fn filter_clamped
,
P::Subpixel: Into,
+ K: Num + Copy + From<,
- P::Subpixel: Into,
+ K: Num + Copy + Send + Sync + From
where
@@ -105,8 +105,8 @@ where
ChannelMap
::new(width, height);
@@ -122,12 +122,7 @@ where
// x and y are in bounds for image by construction,
// vertical and horizontal are the result of calling filter_clamped on image,
// and filter_clamped returns an image of the same size as its input
- let (h, v) = unsafe {
- (
- horizontal.unsafe_get_pixel(x, y),
- vertical.unsafe_get_pixel(x, y),
- )
- };
+ let (h, v) = unsafe { (pass1.unsafe_get_pixel(x, y), pass2.unsafe_get_pixel(x, y)) };
let mut p = ChannelMap::