use derive_more::{Deref, DerefMut};
use image::{imageops, DynamicImage, ImageBuffer, Luma};
use log::*;
use ndarray::{Array2, ArrayView2, ArrayViewMut2};
use nshare::{MutNdarray2, RefNdarray2};
use std::f32;
#[derive(Debug, Clone, Deref, DerefMut)]
pub struct GrayFloatImage(pub ImageBuffer<Luma<f32>, Vec<f32>>);
impl GrayFloatImage {
pub fn from_dynamic(input_image: &DynamicImage) -> Self {
Self(match input_image.grayscale() {
DynamicImage::ImageLuma8(gray_image) => {
info!("Loaded an 8-bit image");
ImageBuffer::from_fn(gray_image.width(), gray_image.height(), |x, y| {
Luma([f32::from(gray_image[(x, y)][0]) / 255f32])
})
}
DynamicImage::ImageLuma16(gray_image) => {
info!("Loaded a 16-bit image");
ImageBuffer::from_fn(gray_image.width(), gray_image.height(), |x, y| {
Luma([f32::from(gray_image[(x, y)][0]) / 65535f32])
})
}
_ => unreachable!(),
})
}
pub fn from_array2(arr: Array2<f32>) -> Self {
Self(
ImageBuffer::from_raw(arr.dim().1 as u32, arr.dim().0 as u32, arr.into_raw_vec())
.expect("raw vector didn't have enough pixels for the image"),
)
}
pub fn ref_array2(&self) -> ArrayView2<f32> {
self.0.ref_ndarray2()
}
pub fn mut_array2(&mut self) -> ArrayViewMut2<f32> {
self.0.mut_ndarray2()
}
pub fn zero_array(&self) -> Array2<f32> {
Array2::zeros((self.height(), self.width()))
}
pub fn width(&self) -> usize {
self.0.width() as usize
}
pub fn height(&self) -> usize {
self.0.height() as usize
}
pub fn new(width: usize, height: usize) -> Self {
Self(ImageBuffer::from_pixel(
width as u32,
height as u32,
Luma([0.0]),
))
}
pub fn get(&self, x: usize, y: usize) -> f32 {
self.get_pixel(x as u32, y as u32)[0]
}
pub fn put(&mut self, x: usize, y: usize, pixel_value: f32) {
self.put_pixel(x as u32, y as u32, Luma([pixel_value]));
}
pub fn half_size(&self) -> Self {
let width = self.width() / 2;
let height = self.height() / 2;
Self(imageops::resize(
&self.0,
width as u32,
height as u32,
imageops::FilterType::Nearest,
))
}
}
pub fn fill_border(output: &mut GrayFloatImage, half_width: usize) {
for x in 0..output.width() {
let plus = output.get(x, half_width);
let minus = output.get(x, output.height() - half_width - 1);
for y in 0..half_width {
output.put(x, y, plus);
}
for y in (output.height() - half_width)..output.height() {
output.put(x, y, minus);
}
}
for y in 0..output.height() {
let plus = output.get(half_width, y);
let minus = output.get(output.width() - half_width - 1, y);
for x in 0..half_width {
output.put(x, y, plus);
}
for x in (output.width() - half_width)..output.width() {
output.put(x, y, minus);
}
}
}
#[inline(always)]
pub fn horizontal_filter(image: &GrayFloatImage, kernel: &[f32]) -> GrayFloatImage {
debug_assert!(kernel.len() % 2 == 1);
let half_width = (kernel.len() / 2) as i32;
let w = image.width() as i32;
let h = image.height() as i32;
let mut output = GrayFloatImage::new(image.width(), image.height());
for k in -half_width..=half_width {
let mut out_itr = output.iter_mut();
let mut image_itr = image.iter();
let mut out_ptr = out_itr.nth(half_width as usize).unwrap();
let mut image_val = image_itr.nth((half_width + k) as usize).unwrap();
let kernel_value = kernel[(k + half_width) as usize];
for _ in half_width..(w * h - half_width - 1) {
*out_ptr += kernel_value * image_val;
out_ptr = out_itr.next().unwrap();
image_val = image_itr.next().unwrap();
}
}
fill_border(&mut output, half_width as usize);
output
}
#[inline(always)]
pub fn vertical_filter(image: &GrayFloatImage, kernel: &[f32]) -> GrayFloatImage {
debug_assert!(kernel.len() % 2 == 1);
let half_width = (kernel.len() / 2) as i32;
let w = image.width() as i32;
let h = image.height() as i32;
let mut output = GrayFloatImage::new(image.width(), image.height());
for k in -half_width..=half_width {
let mut out_itr = output.iter_mut();
let mut image_itr = image.iter();
let mut out_ptr = out_itr.nth((half_width * w) as usize).unwrap();
let mut image_val = image_itr
.nth(((half_width * w) + (k * w)) as usize)
.unwrap();
let kernel_value = kernel[(k + half_width) as usize];
for _ in (half_width * w)..(w * h - (half_width * w) - 1) {
*out_ptr += kernel_value * image_val;
out_ptr = out_itr.next().unwrap();
image_val = image_itr.next().unwrap();
}
}
fill_border(&mut output, half_width as usize);
output
}
fn gaussian(x: f32, r: f32) -> f32 {
((2.0 * f32::consts::PI).sqrt() * r).recip() * (-x.powi(2) / (2.0 * r.powi(2))).exp()
}
fn gaussian_kernel(r: f32, kernel_size: usize) -> Vec<f32> {
let mut kernel = vec![0f32; kernel_size];
let half_width = (kernel_size / 2) as i32;
let mut sum = 0f32;
for i in -half_width..=half_width {
let val = gaussian(i as f32, r);
kernel[(i + half_width) as usize] = val;
sum += val;
}
for val in kernel.iter_mut() {
*val /= sum;
}
kernel
}
pub fn gaussian_blur(image: &GrayFloatImage, r: f32) -> GrayFloatImage {
let kernel_size = (f32::ceil(r) as usize) * 2 + 1usize;
let kernel = gaussian_kernel(r, kernel_size);
let img_horizontal = horizontal_filter(&image, &kernel);
vertical_filter(&img_horizontal, &kernel)
}
#[cfg(test)]
mod tests {
use super::gaussian_kernel;
#[test]
fn gaussian_kernel_correct() {
let kernel = gaussian_kernel(3.0, 7);
let known_correct_kernel = vec![
0.1062_8852,
0.1403_2133,
0.1657_7007,
0.1752_4014,
0.1657_7007,
0.1403_2133,
0.1062_8852,
];
for it in kernel.iter().zip(known_correct_kernel.iter()) {
let (i, j) = it;
assert!(f32::abs(*i - *j) < 0.0001);
}
}
}