use crate::definitions::{HasBlack, Image};
use crate::filter::filter_clamped;
use crate::kernel::{
self, Kernel, PREWITT_HORIZONTAL_3X3, PREWITT_VERTICAL_3X3, SCHARR_HORIZONTAL_3X3,
SCHARR_VERTICAL_3X3, SOBEL_HORIZONTAL_3X3, SOBEL_VERTICAL_3X3,
};
use crate::map::{ChannelMap, WithChannel};
use image::{GenericImage, GenericImageView, GrayImage, Luma, Pixel};
use itertools::multizip;
pub fn gradients_grayscale(
image: &GrayImage,
horizontal_kernel: Kernel<i32>,
vertical_kernel: Kernel<i32>,
) -> Image<Luma<u16>> {
gradients(image, horizontal_kernel, vertical_kernel, |p| p)
}
pub fn gradients<P, F, Q>(
image: &Image<P>,
horizontal_kernel: Kernel<i32>,
vertical_kernel: Kernel<i32>,
f: F,
) -> Image<Q>
where
P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16>,
Q: Pixel,
ChannelMap<P, u16>: HasBlack,
F: Fn(ChannelMap<P, u16>) -> Q,
{
let horizontal = filter_clamped::<_, _, i16>(image, horizontal_kernel);
let vertical = filter_clamped::<_, _, i16>(image, vertical_kernel);
let (width, height) = image.dimensions();
let mut out = Image::<Q>::new(width, height);
for y in 0..height {
for x in 0..width {
let (h, v) = unsafe {
(
horizontal.unsafe_get_pixel(x, y),
vertical.unsafe_get_pixel(x, y),
)
};
let mut p = ChannelMap::<P, u16>::black();
for (h, v, p) in multizip((h.channels(), v.channels(), p.channels_mut())) {
*p = gradient_magnitude(*h as f32, *v as f32);
}
unsafe {
out.unsafe_put_pixel(x, y, f(p));
}
}
}
out
}
#[inline]
fn gradient_magnitude(dx: f32, dy: f32) -> u16 {
(dx.powi(2) + dy.powi(2)).sqrt() as u16
}
pub fn horizontal_sobel(image: &GrayImage) -> Image<Luma<i16>> {
filter_clamped(image, SOBEL_HORIZONTAL_3X3)
}
pub fn vertical_sobel(image: &GrayImage) -> Image<Luma<i16>> {
filter_clamped(image, SOBEL_VERTICAL_3X3)
}
pub fn horizontal_scharr(image: &GrayImage) -> Image<Luma<i16>> {
filter_clamped(image, SCHARR_HORIZONTAL_3X3)
}
pub fn vertical_scharr(image: &GrayImage) -> Image<Luma<i16>> {
filter_clamped(image, SCHARR_VERTICAL_3X3)
}
pub fn horizontal_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
filter_clamped(image, PREWITT_HORIZONTAL_3X3)
}
pub fn vertical_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
filter_clamped(image, PREWITT_VERTICAL_3X3)
}
pub fn sobel_gradients(image: &GrayImage) -> Image<Luma<u16>> {
gradients_grayscale(
image,
kernel::SOBEL_HORIZONTAL_3X3,
kernel::SOBEL_VERTICAL_3X3,
)
}
pub fn prewitt_gradients(image: &GrayImage) -> Image<Luma<u16>> {
gradients_grayscale(
image,
kernel::PREWITT_HORIZONTAL_3X3,
kernel::PREWITT_VERTICAL_3X3,
)
}
pub fn sobel_gradient_map<P, F, Q>(image: &Image<P>, f: F) -> Image<Q>
where
P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16>,
Q: Pixel,
ChannelMap<P, u16>: HasBlack,
F: Fn(ChannelMap<P, u16>) -> Q,
{
gradients(
image,
kernel::SOBEL_HORIZONTAL_3X3,
kernel::SOBEL_VERTICAL_3X3,
f,
)
}
#[cfg(test)]
mod tests {
use crate::filter::filter_clamped;
#[cfg(feature = "rayon")]
use crate::filter::filter_clamped_parallel;
use super::*;
use image::Luma;
#[test]
fn test_gradients_constant_image_sobel() {
let image = Image::from_pixel(5, 5, Luma([15u8]));
let expected = Image::from_pixel(5, 5, Luma([0u16]));
assert_pixels_eq!(
gradients(
&image,
kernel::SOBEL_HORIZONTAL_3X3,
kernel::SOBEL_VERTICAL_3X3,
|p| p
),
expected
);
}
#[test]
fn test_gradients_constant_image_scharr() {
let image = Image::from_pixel(5, 5, Luma([15u8]));
let expected = Image::from_pixel(5, 5, Luma([0u16]));
assert_pixels_eq!(
gradients(
&image,
kernel::SCHARR_HORIZONTAL_3X3,
kernel::SCHARR_VERTICAL_3X3,
|p| p
),
expected
);
}
#[test]
fn test_gradients_constant_image_prewitt() {
let image = Image::from_pixel(5, 5, Luma([15u8]));
let expected = Image::from_pixel(5, 5, Luma([0u16]));
assert_pixels_eq!(
gradients(
&image,
kernel::PREWITT_HORIZONTAL_3X3,
kernel::PREWITT_VERTICAL_3X3,
|p| p
),
expected
);
}
#[test]
fn test_gradients_constant_image_roberts() {
let image = Image::from_pixel(5, 5, Luma([15u8]));
let expected = Image::from_pixel(5, 5, Luma([0u16]));
assert_pixels_eq!(
gradients(
&image,
kernel::ROBERTS_HORIZONTAL_2X2,
kernel::ROBERTS_VERTICAL_2X2,
|p| p
),
expected
);
}
#[test]
fn test_horizontal_sobel_gradient_image() {
let image = gray_image!(
3, 2, 1;
6, 5, 4;
9, 8, 7);
let expected = gray_image!(type: i16,
-4, -8, -4;
-4, -8, -4;
-4, -8, -4);
let filtered = filter_clamped(&image, kernel::SOBEL_HORIZONTAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
#[cfg(feature = "rayon")]
fn test_horizontal_sobel_gradient_image_parallel() {
let image = gray_image!(
3, 2, 1;
6, 5, 4;
9, 8, 7);
let expected = gray_image!(type: i16,
-4, -8, -4;
-4, -8, -4;
-4, -8, -4);
let filtered = filter_clamped_parallel(&image, kernel::SOBEL_HORIZONTAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
fn test_vertical_sobel_gradient_image() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
-4, -4, -4;
-8, -8, -8;
-4, -4, -4);
let filtered = filter_clamped(&image, kernel::SOBEL_VERTICAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
#[cfg(feature = "rayon")]
fn test_vertical_sobel_gradient_image_parallel() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
-4, -4, -4;
-8, -8, -8;
-4, -4, -4);
let filtered = filter_clamped_parallel(&image, kernel::SOBEL_VERTICAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
fn test_horizontal_scharr_gradient_image() {
let image = gray_image!(
3, 2, 1;
6, 5, 4;
9, 8, 7);
let expected = gray_image!(type: i16,
-16, -32, -16;
-16, -32, -16;
-16, -32, -16);
let filtered = filter_clamped(&image, kernel::SCHARR_HORIZONTAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
#[cfg(feature = "rayon")]
fn test_horizontal_scharr_gradient_image_parallel() {
let image = gray_image!(
3, 2, 1;
6, 5, 4;
9, 8, 7);
let expected = gray_image!(type: i16,
-16, -32, -16;
-16, -32, -16;
-16, -32, -16);
let filtered = filter_clamped_parallel(&image, kernel::SCHARR_HORIZONTAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
fn test_vertical_scharr_gradient_image() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
-16, -16, -16;
-32, -32, -32;
-16, -16, -16);
let filtered = filter_clamped(&image, kernel::SCHARR_VERTICAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
#[cfg(feature = "rayon")]
fn test_vertical_scharr_gradient_image_parallel() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
-16, -16, -16;
-32, -32, -32;
-16, -16, -16);
let filtered = filter_clamped_parallel(&image, kernel::SCHARR_VERTICAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
fn test_horizontal_prewitt_gradient_image() {
let image = gray_image!(
3, 2, 1;
6, 5, 4;
9, 8, 7);
let expected = gray_image!(type: i16,
-3, -6, -3;
-3, -6, -3;
-3, -6, -3);
let filtered = filter_clamped(&image, kernel::PREWITT_HORIZONTAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
#[cfg(feature = "rayon")]
fn test_horizontal_prewitt_gradient_image_parallel() {
let image = gray_image!(
3, 2, 1;
6, 5, 4;
9, 8, 7);
let expected = gray_image!(type: i16,
-3, -6, -3;
-3, -6, -3;
-3, -6, -3);
let filtered = filter_clamped_parallel(&image, kernel::PREWITT_HORIZONTAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
#[cfg(feature = "rayon")]
fn test_vertical_prewitt_gradient_image() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
-3, -3, -3;
-6, -6, -6;
-3, -3, -3);
let filtered = filter_clamped(&image, kernel::PREWITT_VERTICAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
#[cfg(feature = "rayon")]
fn test_vertical_prewitt_gradient_image_parallel() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
-3, -3, -3;
-6, -6, -6;
-3, -3, -3);
let filtered = filter_clamped_parallel(&image, kernel::PREWITT_VERTICAL_3X3);
assert_pixels_eq!(filtered, expected);
}
#[test]
fn test_horizontal_roberts_gradient_image() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
0, -3, -3;
1, -2, -2;
1, -2, -2);
let filtered = filter_clamped(&image, kernel::ROBERTS_HORIZONTAL_2X2);
assert_pixels_eq!(filtered, expected);
}
#[test]
#[cfg(feature = "rayon")]
fn test_horizontal_roberts_gradient_image_parallel() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
0, -3, -3;
1, -2, -2;
1, -2, -2);
let filtered = filter_clamped_parallel(&image, kernel::ROBERTS_HORIZONTAL_2X2);
assert_pixels_eq!(filtered, expected);
}
#[test]
fn test_vertical_roberts_gradient_image() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
0, 3, 3;
1, 4, 4;
1, 4, 4);
let filtered = filter_clamped(&image, kernel::ROBERTS_VERTICAL_2X2);
assert_pixels_eq!(filtered, expected);
}
#[test]
#[cfg(feature = "rayon")]
fn test_vertical_roberts_gradient_image_parallel() {
let image = gray_image!(
3, 6, 9;
2, 5, 8;
1, 4, 7);
let expected = gray_image!(type: i16,
0, 3, 3;
1, 4, 4;
1, 4, 4);
let filtered = filter_clamped_parallel(&image, kernel::ROBERTS_VERTICAL_2X2);
assert_pixels_eq!(filtered, expected);
}
}
#[cfg(not(miri))]
#[cfg(test)]
mod benches {
use super::*;
use crate::utils::gray_bench_image;
use test::{Bencher, black_box};
#[bench]
fn bench_sobel_gradients(b: &mut Bencher) {
let image = gray_bench_image(500, 500);
b.iter(|| {
let gradients = gradients(
&image,
kernel::SOBEL_HORIZONTAL_3X3,
kernel::SOBEL_VERTICAL_3X3,
|p| p,
);
black_box(gradients);
});
}
}