use crate::definitions::{HasBlack, Image};
use crate::filter::filter3x3;
use crate::map::{ChannelMap, WithChannel};
use image::{GenericImage, GenericImageView, GrayImage, Luma, Pixel};
use itertools::multizip;
#[rustfmt::skip]
pub static VERTICAL_SOBEL: [i32; 9] = [
-1, -2, -1,
0, 0, 0,
1, 2, 1];
#[rustfmt::skip]
pub static HORIZONTAL_SOBEL: [i32; 9] = [
-1, 0, 1,
-2, 0, 2,
-1, 0, 1];
#[rustfmt::skip]
pub static VERTICAL_SCHARR: [i32; 9] = [
-3, -10, -3,
0, 0, 0,
3, 10, 3];
#[rustfmt::skip]
pub static HORIZONTAL_SCHARR: [i32; 9] = [
-3, 0, 3,
-10, 0, 10,
-3, 0, 3];
#[rustfmt::skip]
pub static VERTICAL_PREWITT: [i32; 9] = [
-1, -1, -1,
0, 0, 0,
1, 1, 1];
#[rustfmt::skip]
pub static HORIZONTAL_PREWITT: [i32; 9] = [
-1, 0, 1,
-1, 0, 1,
-1, 0, 1];
pub fn horizontal_sobel(image: &GrayImage) -> Image<Luma<i16>> {
filter3x3(image, &HORIZONTAL_SOBEL)
}
pub fn vertical_sobel(image: &GrayImage) -> Image<Luma<i16>> {
filter3x3(image, &VERTICAL_SOBEL)
}
pub fn horizontal_scharr(image: &GrayImage) -> Image<Luma<i16>> {
filter3x3(image, &HORIZONTAL_SCHARR)
}
pub fn vertical_scharr(image: &GrayImage) -> Image<Luma<i16>> {
filter3x3(image, &VERTICAL_SCHARR)
}
pub fn horizontal_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
filter3x3(image, &HORIZONTAL_PREWITT)
}
pub fn vertical_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
filter3x3(image, &VERTICAL_PREWITT)
}
pub fn sobel_gradients(image: &GrayImage) -> Image<Luma<u16>> {
gradients(image, &HORIZONTAL_SOBEL, &VERTICAL_SOBEL, |p| p)
}
pub fn sobel_gradient_map<P, F, Q>(image: &Image<P>, f: F) -> Image<Q>
where
P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16> + 'static,
Q: Pixel + 'static,
ChannelMap<P, u16>: HasBlack,
F: Fn(ChannelMap<P, u16>) -> Q,
{
gradients(image, &HORIZONTAL_SOBEL, &VERTICAL_SOBEL, f)
}
pub fn prewitt_gradients(image: &GrayImage) -> Image<Luma<u16>> {
gradients(image, &HORIZONTAL_PREWITT, &VERTICAL_PREWITT, |p| p)
}
fn gradients<P, F, Q>(
image: &Image<P>,
horizontal_kernel: &[i32; 9],
vertical_kernel: &[i32; 9],
f: F,
) -> Image<Q>
where
P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16> + 'static,
Q: Pixel + 'static,
ChannelMap<P, u16>: HasBlack,
F: Fn(ChannelMap<P, u16>) -> Q,
{
let horizontal: Image<ChannelMap<P, i16>> = filter3x3::<_, _, i16>(image, horizontal_kernel);
let vertical: Image<ChannelMap<P, i16>> = filter3x3::<_, _, 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
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::gray_bench_image;
use image::{ImageBuffer, Luma};
use test::{black_box, Bencher};
#[rustfmt::skip::macros(gray_image)]
#[test]
fn test_gradients_constant_image() {
let image = ImageBuffer::from_pixel(5, 5, Luma([15u8]));
let expected = ImageBuffer::from_pixel(5, 5, Luma([0i16]));
assert_pixels_eq!(horizontal_sobel(&image), expected);
assert_pixels_eq!(vertical_sobel(&image), expected);
assert_pixels_eq!(horizontal_scharr(&image), expected);
assert_pixels_eq!(vertical_scharr(&image), expected);
assert_pixels_eq!(horizontal_prewitt(&image), expected);
assert_pixels_eq!(vertical_prewitt(&image), 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 = horizontal_sobel(&image);
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 = vertical_sobel(&image);
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 = horizontal_scharr(&image);
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 = vertical_scharr(&image);
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 = horizontal_prewitt(&image);
assert_pixels_eq!(filtered, expected);
}
#[test]
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 = vertical_prewitt(&image);
assert_pixels_eq!(filtered, expected);
}
#[bench]
fn bench_sobel_gradients(b: &mut Bencher) {
let image = gray_bench_image(500, 500);
b.iter(|| {
let gradients = sobel_gradients(&image);
black_box(gradients);
});
}
}