From 415f79094b1e933953fffff49029d6a6636fa726 Mon Sep 17 00:00:00 2001 From: tiffany1618 Date: Tue, 2 Mar 2021 22:56:59 -0800 Subject: [PATCH 1/6] Added parallel feature and more tests --- Cargo.toml | 8 ++- README.md | 34 +++++++++- src/filter/bilateral.rs | 7 +- src/filter/mod.rs | 113 +++------------------------------ src/image/mod.rs | 2 - src/image/pixel_iter.rs | 134 --------------------------------------- src/transform.rs | 46 +++++++++----- src/util/mod.rs | 10 +-- tests/colorspace_test.rs | 101 +++++++++++++++++++++++++++++ tests/common.rs | 8 +-- tests/filter_test.rs | 66 +++++++++---------- tests/morphology_test.rs | 0 tests/tone_test.rs | 0 tests/transform_test.rs | 40 ++++++------ 14 files changed, 245 insertions(+), 324 deletions(-) delete mode 100644 src/image/pixel_iter.rs create mode 100644 tests/colorspace_test.rs create mode 100644 tests/morphology_test.rs create mode 100644 tests/tone_test.rs diff --git a/Cargo.toml b/Cargo.toml index 885d294..16bb8de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "imgproc-rs" -version = "0.2.0" +version = "0.2.1" edition = "2018" license = "MIT" description = "Image processing library for Rust" @@ -18,5 +18,9 @@ categories = ["multimedia::images"] [dependencies] image = "0.23.12" +rayon = { version = "1.5.0", optional = true } rulinalg = "0.4.2" -rayon = "1.5.0" \ No newline at end of file + +[features] +# Enables multithreading +parallel = ["rayon"] \ No newline at end of file diff --git a/README.md b/README.md index dc94a33..5892cca 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ A Rust image processing library. ## Features -* Multithreading support for some functions (indicated by the `_par` suffix in the function name) via - [rayon](https://github.com/rayon-rs/rayon) +* Multithreading support for some functions via [rayon](https://github.com/rayon-rs/rayon) (see + [Enabling Multithreading](#enabling-multithreading) for more information) ## Supported Image Formats @@ -114,4 +114,32 @@ fn main() { // Get an image pixel using 2D coordinates let pixel_2d = img.get_pixel(1, 1); } -``` \ No newline at end of file +``` + +## Enabling Multithreading + +To enable multithreading, include the `parallel` feature in your `Cargo.toml`: + +```toml +[dependencies.imgproc-rs] +version = "0.2.1" +default-features = false +features = ["parallel"] +``` + +Alternatively, pass the features flag to `cargo run`: + +``` +cargo run --features parallel +``` + +### Image processing functions that support multithreading: +* `transform` module + * `crop` + * `scale` + * `scale_lanczos` +* All functions in the `filter` module, except: + * `threshold` + * `residual` + * `median_filter` + * `alpha_trimmed_mean_filter` \ No newline at end of file diff --git a/src/filter/bilateral.rs b/src/filter/bilateral.rs index 2f555b9..cd2f8a5 100644 --- a/src/filter/bilateral.rs +++ b/src/filter/bilateral.rs @@ -3,9 +3,11 @@ use crate::enums::{Bilateral, White}; use crate::image::{Image, BaseImage}; use crate::error::ImgProcResult; +#[cfg(feature = "rayon")] use rayon::prelude::*; /// Applies a bilateral filter using CIE LAB +#[cfg(not(feature = "rayon"))] pub fn bilateral_filter(input: &Image, range: f64, spatial: f64, algorithm: Bilateral) -> ImgProcResult> { error::check_non_neg(range, "range")?; @@ -32,8 +34,9 @@ pub fn bilateral_filter(input: &Image, range: f64, spatial: f64, algorithm: Ok(colorspace::lab_to_srgb(&output, &White::D65)) } -/// (Parallel) Applies a bilateral filter using CIE LAB -pub fn bilateral_filter_par(input: &Image, range: f64, spatial: f64, algorithm: Bilateral) +/// Applies a bilateral filter using CIE LAB +#[cfg(feature = "rayon")] +pub fn bilateral_filter(input: &Image, range: f64, spatial: f64, algorithm: Bilateral) -> ImgProcResult> { error::check_non_neg(range, "range")?; error::check_non_neg(spatial, "spatial")?; diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 03feb10..58b0738 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -12,6 +12,7 @@ use crate::util::constants::{K_SOBEL_1D_VERT, K_SOBEL_1D_HORZ, K_UNSHARP_MASKING use crate::enums::Thresh; use crate::error::ImgProcResult; +#[cfg(feature = "rayon")] use rayon::prelude::*; ///////////////////// @@ -20,6 +21,7 @@ use rayon::prelude::*; /// Applies a 1D filter. If `is_vert` is true, applies `kernel` /// as a vertical filter; otherwise applies `kernel` as a horizontal filter +#[cfg(not(feature = "rayon"))] pub fn filter_1d(input: &Image, kernel: &[f64], is_vert: bool) -> ImgProcResult> { error::check_odd(kernel.len(), "kernel length")?; @@ -37,9 +39,10 @@ pub fn filter_1d(input: &Image, kernel: &[f64], is_vert: bool) -> ImgProcRe Ok(output) } -/// (Parallel) Applies a 1D filter. If `is_vert` is true, applies `kernel` +/// Applies a 1D filter. If `is_vert` is true, applies `kernel` /// as a vertical filter; otherwise applies `kernel` as a horizontal filter -pub fn filter_1d_par(input: &Image, kernel: &[f64], is_vert: bool) -> ImgProcResult> { +#[cfg(feature = "rayon")] +pub fn filter_1d(input: &Image, kernel: &[f64], is_vert: bool) -> ImgProcResult> { error::check_odd(kernel.len(), "kernel length")?; let (width, height, channels, alpha) = input.info().whca(); @@ -65,17 +68,8 @@ pub fn separable_filter(input: &Image, vert_kernel: &[f64], horz_kernel: &[ Ok(filter_1d(&vertical, horz_kernel, false)?) } -/// (Parallel) Applies a separable linear filter by first applying `vert_kernel` and then `horz_kernel` -pub fn separable_filter_par(input: &Image, vert_kernel: &[f64], horz_kernel: &[f64]) -> ImgProcResult> { - error::check_odd(vert_kernel.len(), "vert_kernel length")?; - error::check_odd(horz_kernel.len(), "horz_kernel length")?; - error::check_equal(vert_kernel.len(), horz_kernel.len(), "kernel lengths")?; - - let vertical = filter_1d_par(input, vert_kernel, true)?; - Ok(filter_1d_par(&vertical, horz_kernel, false)?) -} - /// Applies an unseparable linear filter +#[cfg(not(feature = "rayon"))] pub fn unseparable_filter(input: &Image, kernel: &[f64]) -> ImgProcResult> { error::check_odd(kernel.len(), "kernel length")?; error::check_square(kernel.len() as f64, "kernel length")?; @@ -94,8 +88,9 @@ pub fn unseparable_filter(input: &Image, kernel: &[f64]) -> ImgProcResult, kernel: &[f64]) -> ImgProcResult> { +/// Applies an unseparable linear filter +#[cfg(feature = "rayon")] +pub fn unseparable_filter(input: &Image, kernel: &[f64]) -> ImgProcResult> { error::check_odd(kernel.len(), "kernel length")?; error::check_square(kernel.len() as f64, "kernel length")?; @@ -125,18 +120,6 @@ pub fn linear_filter(input: &Image, kernel: &[f64]) -> ImgProcResult, kernel: &[f64]) -> ImgProcResult> { - error::check_odd(kernel.len(), "kernel length")?; - error::check_square(kernel.len() as f64, "kernel length")?; - - let separable = math::separate_kernel(kernel); - match separable { - Some((vert, horz)) => Ok(separable_filter_par(input, &vert, &horz)?), - None => Ok(unseparable_filter_par(input, &kernel)?) - } -} - ////////////// // Blurring ////////////// @@ -151,16 +134,6 @@ pub fn box_filter(input: &Image, size: u32) -> ImgProcResult> { Ok(separable_filter(input, &kernel, &kernel)?) } -/// (Parallel) Applies a box filter using a `size x size` kernel -pub fn box_filter_par(input: &Image, size: u32) -> ImgProcResult> { - error::check_odd(size, "size")?; - - let len = (size * size) as usize; - let kernel = vec![1.0; len]; - - Ok(separable_filter_par(input, &kernel, &kernel)?) -} - /// Applies a normalized box filter using a `size x size` kernel pub fn box_filter_normalized(input: &Image, size: u32) -> ImgProcResult> { error::check_odd(size, "size")?; @@ -171,16 +144,6 @@ pub fn box_filter_normalized(input: &Image, size: u32) -> ImgProcResult, size: u32) -> ImgProcResult> { - error::check_odd(size, "size")?; - - let len = (size * size) as usize; - let kernel = vec![1.0 / ((size * size) as f64); len]; - - Ok(separable_filter_par(input, &kernel, &kernel)?) -} - /// Applies a weighted average filter using a `size x size` kernel with a center weight of `weight` pub fn weighted_avg_filter(input: &Image, size: u32, weight: u32) -> ImgProcResult> { error::check_odd(size, "size")?; @@ -193,30 +156,12 @@ pub fn weighted_avg_filter(input: &Image, size: u32, weight: u32) -> ImgPro Ok(unseparable_filter(input, &kernel)?) } -/// (Parallel) Applies a weighted average filter using a `size x size` kernel with a center weight of `weight` -pub fn weighted_avg_filter_par(input: &Image, size: u32, weight: u32) -> ImgProcResult> { - error::check_odd(size, "size")?; - - let sum = (size * size) - 1 + weight; - let center = (size / 2) * size + (size / 2); - let mut kernel = vec![1.0 / (sum as f64); (size * size) as usize]; - kernel[center as usize] = (weight as f64) / (sum as f64); - - Ok(unseparable_filter_par(input, &kernel)?) -} - /// Applies a Gaussian blur using a `size x size` kernel pub fn gaussian_blur(input: &Image, size: u32, std_dev: f64) -> ImgProcResult> { let kernel = util::generate_gaussian_kernel(size, std_dev)?; Ok(linear_filter(input, &kernel)?) } -/// (Parallel) Applies a Gaussian blur using a `size x size` kernel -pub fn gaussian_blur_par(input: &Image, size: u32, std_dev: f64) -> ImgProcResult> { - let kernel = util::generate_gaussian_kernel(size, std_dev)?; - Ok(linear_filter_par(input, &kernel)?) -} - //////////////// // Sharpening //////////////// @@ -226,21 +171,11 @@ pub fn sharpen(input: &Image) -> ImgProcResult> { Ok(unseparable_filter(input, &K_SHARPEN)?) } -/// (Parallel) Sharpens image -pub fn sharpen_par(input: &Image) -> ImgProcResult> { - Ok(unseparable_filter_par(input, &K_SHARPEN)?) -} - /// Sharpens image by applying the unsharp masking kernel pub fn unsharp_masking(input: &Image) -> ImgProcResult> { Ok(unseparable_filter(input, &K_UNSHARP_MASKING)?) } -/// (Parallel) Sharpens image by applying the unsharp masking kernel -pub fn unsharp_masking_par(input: &Image) -> ImgProcResult> { - Ok(unseparable_filter_par(input, &K_UNSHARP_MASKING)?) -} - //////////////////// // Edge detection //////////////////// @@ -260,52 +195,22 @@ pub fn derivative_mask(input: &Image, vert_kernel: &[f64], horz_kernel: &[f Ok(output) } -/// (Parallel) Applies a separable derivative mask; first converts `input` to grayscale -pub fn derivative_mask_par(input: &Image, vert_kernel: &[f64], horz_kernel: &[f64]) -> ImgProcResult> { - let gray = colorspace::rgb_to_grayscale_f64(input); - let img_x = separable_filter_par(&gray, &vert_kernel, &horz_kernel)?; - let img_y = separable_filter_par(&gray, &horz_kernel, &vert_kernel)?; - let mut output = Image::blank(gray.info()); - - for i in 0..(output.info().full_size() as usize) { - output.set_pixel_indexed(i, &[(img_x[i][0].powf(2.0) + img_y[i][0].powf(2.0)).sqrt()]); - } - - Ok(output) -} - /// Applies the Prewitt operator pub fn prewitt(input: &Image) -> ImgProcResult> { Ok(derivative_mask(input, &K_PREWITT_1D_VERT, &K_PREWITT_1D_HORZ)?) } -/// (Parallel) Applies the Prewitt operator -pub fn prewitt_par(input: &Image) -> ImgProcResult> { - Ok(derivative_mask_par(input, &K_PREWITT_1D_VERT, &K_PREWITT_1D_HORZ)?) -} - /// Applies the Sobel operator pub fn sobel(input: &Image) -> ImgProcResult> { Ok(derivative_mask(input, &K_SOBEL_1D_VERT, &K_SOBEL_1D_HORZ)?) } -/// (Parallel) Applies the Sobel operator -pub fn sobel_par(input: &Image) -> ImgProcResult> { - Ok(derivative_mask_par(input, &K_SOBEL_1D_VERT, &K_SOBEL_1D_HORZ)?) -} - /// Applies a Sobel operator with weight `weight` pub fn sobel_weighted(input: &Image, weight: u32) -> ImgProcResult> { let vert_kernel = vec![1.0, weight as f64, 1.0]; Ok(derivative_mask(input, &vert_kernel, &K_SOBEL_1D_HORZ)?) } -/// (Parallel) Applies a Sobel operator with weight `weight` -pub fn sobel_weighted_par(input: &Image, weight: u32) -> ImgProcResult> { - let vert_kernel = vec![1.0, weight as f64, 1.0]; - Ok(derivative_mask_par(input, &vert_kernel, &K_SOBEL_1D_HORZ)?) -} - ////////////////// // Thresholding ////////////////// diff --git a/src/image/mod.rs b/src/image/mod.rs index 22b7325..1cc8394 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -58,12 +58,10 @@ pub use self::sub_image::*; pub use self::pixel::*; pub use self::from_impl::*; -pub use self::pixel_iter::*; mod sub_image; mod pixel; mod from_impl; -mod pixel_iter; use crate::error; diff --git a/src/image/pixel_iter.rs b/src/image/pixel_iter.rs deleted file mode 100644 index 22c0c8c..0000000 --- a/src/image/pixel_iter.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::image::{Image, BaseImage, Number}; - -/// A struct representing a pixel iterator for an image -pub struct PixelIter<'a, T: Number> { - image: &'a Image, - index: usize, -} - -pub struct PixelIter2d<'a, T: Number> { - image: &'a Image, - pub x: u32, - pub y: u32, -} - -pub struct Iter2d { - x: u32, - y: u32, - x_max: u32, - y_max: u32, -} - -impl Iter2d { - pub fn new(x_max: u32, y_max: u32) -> Self { - Iter2d { - x: 0, - y: 0, - x_max, - y_max, - } - } -} - -impl Iterator for Iter2d { - type Item = (u32, u32); - - fn next(&mut self) -> Option { - self.x += 1; - - if self.x >= self.x_max { - if self.y == self.y_max - 1 { - return None; - } else { - self.x = 0; - self.y += 1; - } - } - - Some((self.x - 1, self.y)) - } -} - -// impl IndexedParallelIterator for Iter2d { -// fn len(&self) -> usize { -// unimplemented!() -// } -// -// fn drive>(self, consumer: C) -> >::Result { -// unimplemented!() -// } -// -// fn with_producer>(self, callback: CB) -> >::Output { -// unimplemented!() -// } -// } -// -// impl ParallelIterator for Iter2d { -// type Item = (u32, u32); -// -// fn drive_unindexed(self, consumer: C) -> >::Result where -// C: UnindexedConsumer { -// -// } -// } - -impl IntoIterator for &Image { - type Item = (u32, u32); - type IntoIter = Iter2d; - - fn into_iter(self) -> Self::IntoIter { - Iter2d::new(self.info.width, self.info.height) - } -} - -impl<'a, T: Number> PixelIter<'a, T> { - pub fn new(image: &'a Image) -> Self { - PixelIter { - image, - index: 0, - } - } -} - -impl<'a, T: Number> Iterator for PixelIter<'a, T> { - type Item = &'a [T]; - - fn next(&mut self) -> Option { - self.index += 1; - - if self.index >= self.image.info().size() as usize { - None - } else { - Some(&self.image[self.index-1]) - } - } -} - -impl<'a, T: Number> PixelIter2d<'a, T> { - pub fn new(image: &'a Image) -> Self { - PixelIter2d { - image, - x: 0, - y: 0, - } - } -} - -impl<'a, T: Number> Iterator for PixelIter2d<'a, T> { - type Item = &'a [T]; - - fn next(&mut self) -> Option { - self.x += 1; - - if self.x >= self.image.info().width { - if self.y == self.image.info().height - 1 { - return None; - } else { - self.x = 0; - self.y += 1; - } - } - - Some(self.image.get_pixel(self.x - 1, self.y)) - } -} diff --git a/src/transform.rs b/src/transform.rs index 749079d..06ea8e5 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -5,10 +5,12 @@ use crate::image::{Number, Image, ImageInfo, BaseImage}; use crate::error::{ImgProcResult, ImgProcError}; use crate::enums::{Scale, Refl}; +#[cfg(feature = "rayon")] use rayon::prelude::*; /// Crops an image to a rectangle with upper left corner located at `(x, y)` with width `width` /// and height `height` +#[cfg(not(feature = "rayon"))] pub fn crop(input: &Image, x: u32, y: u32, width: u32, height: u32) -> ImgProcResult> { if (x + width) >= input.info().width { return Err(ImgProcError::InvalidArgError(format!("invalid width: input width is {} \ @@ -30,9 +32,10 @@ pub fn crop(input: &Image, x: u32, y: u32, width: u32, height: u32 Ok(output) } -/// (Parallel) Crops an image to a rectangle with upper left corner located at `(x, y)` with width `width` +/// Crops an image to a rectangle with upper left corner located at `(x, y)` with width `width` /// and height `height` -pub fn crop_par(input: &Image, x: u32, y: u32, width: u32, height: u32) -> ImgProcResult> { +#[cfg(feature = "rayon")] +pub fn crop(input: &Image, x: u32, y: u32, width: u32, height: u32) -> ImgProcResult> { if (x + width) >= input.info().width { return Err(ImgProcError::InvalidArgError(format!("invalid width: input width is {} \ but x + width is {}", input.info().width, (x + width)))); @@ -106,6 +109,7 @@ pub fn overlay(back: &Image, front: &Image, x: u32, y: u32) -> /// Scales an image horizontally by `x_factor` and vertically by `y_factor` using the specified /// `method` +#[cfg(not(feature = "rayon"))] pub fn scale(input: &Image, x_factor: f64, y_factor: f64, method: Scale) -> ImgProcResult> { error::check_non_neg(x_factor, "x_factor")?; error::check_non_neg(y_factor, "y_factor")?; @@ -132,10 +136,10 @@ pub fn scale(input: &Image, x_factor: f64, y_factor: f64, method: Scale) -> Ok(output) } - -/// (Parallel) Scales an image horizontally by `x_factor` and vertically by `y_factor` using the specified +/// Scales an image horizontally by `x_factor` and vertically by `y_factor` using the specified /// `method` -pub fn scale_par(input: &Image, x_factor: f64, y_factor: f64, method: Scale) -> ImgProcResult> { +#[cfg(feature = "rayon")] +pub fn scale(input: &Image, x_factor: f64, y_factor: f64, method: Scale) -> ImgProcResult> { error::check_non_neg(x_factor, "x_factor")?; error::check_non_neg(y_factor, "y_factor")?; @@ -145,21 +149,22 @@ pub fn scale_par(input: &Image, x_factor: f64, y_factor: f64, method: Scale return match method { Scale::NearestNeighbor => { - Ok(scale_nearest_neighbor_par(input, &info, x_factor, y_factor)) + Ok(scale_nearest_neighbor(input, &info, x_factor, y_factor)) }, Scale::Bilinear => { - Ok(scale_bilinear_par(input, &info, x_factor, y_factor)) + Ok(scale_bilinear(input, &info, x_factor, y_factor)) }, Scale::Bicubic => { - Ok(scale_bicubic_par(input, &info, x_factor, y_factor)) + Ok(scale_bicubic(input, &info, x_factor, y_factor)) }, Scale::Lanczos => { - Ok(scale_lanczos_resampling_par(input, &info, x_factor, y_factor, 3)) + Ok(scale_lanczos_resampling(input, &info, x_factor, y_factor, 3)) } } } /// Scales an image using Lanczos resampling with kernel of variable size `size` +#[cfg(not(feature = "rayon"))] pub fn scale_lanczos(input: &Image, x_factor: f64, y_factor: f64, size: u32) -> ImgProcResult> { error::check_non_neg(x_factor, "x_factor")?; error::check_non_neg(y_factor, "y_factor")?; @@ -174,8 +179,9 @@ pub fn scale_lanczos(input: &Image, x_factor: f64, y_factor: f64, size: u32 Ok(output) } -/// (Parallel) Scales an image using Lanczos resampling with kernel of variable size `size` -pub fn scale_lanczos_par(input: &Image, x_factor: f64, y_factor: f64, size: u32) -> ImgProcResult> { +/// Scales an image using Lanczos resampling with kernel of variable size `size` +#[cfg(feature = "rayon")] +pub fn scale_lanczos(input: &Image, x_factor: f64, y_factor: f64, size: u32) -> ImgProcResult> { error::check_non_neg(x_factor, "x_factor")?; error::check_non_neg(y_factor, "y_factor")?; error::check_non_neg(size, "size")?; @@ -184,7 +190,7 @@ pub fn scale_lanczos_par(input: &Image, x_factor: f64, y_factor: f64, size: let height = (input.info().height as f64 * y_factor).round() as u32; let info = ImageInfo::new(width, height, input.info().channels, input.info().alpha); - Ok(scale_lanczos_resampling_par(input, &info, x_factor, y_factor, size)) + Ok(scale_lanczos_resampling(input, &info, x_factor, y_factor, size)) } /// Translates an image to the position with upper left corner located at `(x, y)`. Fills in the @@ -314,6 +320,7 @@ pub fn shear(input: &Image, shear_x: f64, shear_y: f64) -> ImgProcResult, output: &mut Image, x_factor: f64, y_factor: f64) { for y in 0..output.info().height { for x in 0..output.info().width { @@ -323,7 +330,8 @@ fn scale_nearest_neighbor(input: &Image, output: &mut Image, x_factor: } } -fn scale_nearest_neighbor_par(input: &Image, info: &ImageInfo, x_factor: f64, y_factor: f64) -> Image { +#[cfg(feature = "rayon")] +fn scale_nearest_neighbor(input: &Image, info: &ImageInfo, x_factor: f64, y_factor: f64) -> Image { let size = info.size(); let (width, height, channels) = info.whc(); @@ -338,6 +346,7 @@ fn scale_nearest_neighbor_par(input: &Image, info: &ImageInfo, x_factor: f6 Image::from_vec_of_slice(width, height, channels, info.alpha, data) } +#[cfg(not(feature = "rayon"))] fn scale_bilinear(input: &Image, output: &mut Image, x_factor: f64, y_factor: f64) { for y in 0..output.info().height { for x in 0..output.info().width { @@ -347,7 +356,8 @@ fn scale_bilinear(input: &Image, output: &mut Image, x_factor: f64, y_ } } -fn scale_bilinear_par(input: &Image, info: &ImageInfo, x_factor: f64, y_factor: f64) -> Image { +#[cfg(feature = "rayon")] +fn scale_bilinear(input: &Image, info: &ImageInfo, x_factor: f64, y_factor: f64) -> Image { let size = info.size(); let (width, height, channels) = info.whc(); @@ -362,6 +372,7 @@ fn scale_bilinear_par(input: &Image, info: &ImageInfo, x_factor: f64, y_fac Image::from_vec_of_vec(width, height, channels, info.alpha, data) } +#[cfg(not(feature = "rayon"))] fn scale_bicubic(input: &Image, output: &mut Image, x_factor: f64, y_factor: f64) { for y in 0..output.info().height { for x in 0..output.info().width { @@ -371,7 +382,8 @@ fn scale_bicubic(input: &Image, output: &mut Image, x_factor: f64, y_f } } -fn scale_bicubic_par(input: &Image, info: &ImageInfo, x_factor: f64, y_factor: f64) -> Image { +#[cfg(feature = "rayon")] +fn scale_bicubic(input: &Image, info: &ImageInfo, x_factor: f64, y_factor: f64) -> Image { let size = info.size(); let (width, height, channels) = info.whc(); @@ -386,6 +398,7 @@ fn scale_bicubic_par(input: &Image, info: &ImageInfo, x_factor: f64, y_fact Image::from_vec_of_vec(width, height, channels, info.alpha, data) } +#[cfg(not(feature = "rayon"))] fn scale_lanczos_resampling(input: &Image, output: &mut Image, x_factor: f64, y_factor: f64, size: u32) { for y in 0..output.info().height { for x in 0..output.info().width { @@ -395,7 +408,8 @@ fn scale_lanczos_resampling(input: &Image, output: &mut Image, x_facto } } -fn scale_lanczos_resampling_par(input: &Image, info: &ImageInfo, x_factor: f64, y_factor: f64, size: u32) -> Image { +#[cfg(feature = "rayon")] +fn scale_lanczos_resampling(input: &Image, info: &ImageInfo, x_factor: f64, y_factor: f64, size: u32) -> Image { let img_size = info.size(); let (width, height, channels) = info.whc(); diff --git a/src/util/mod.rs b/src/util/mod.rs index 08be323..75dd831 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -85,17 +85,17 @@ pub fn generate_gaussian_kernel(size: u32, std_dev: f64) -> ImgProcResult = setup(PATH).unwrap().into(); + + let now = SystemTime::now(); + let gray = colorspace::rgb_to_grayscale_f64(&img); + println!("crop: {}", now.elapsed().unwrap().as_millis()); + + write(&gray.into(), "images/test/colorspace/gray_f64.png").unwrap(); +} + +#[test] +fn srgb_to_xyz_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = colorspace::srgb_to_xyz(&img); + println!("crop: {}", now.elapsed().unwrap().as_millis()); + + write(&proc.into(), "images/test/colorspace/srgb_xyz.png").unwrap(); +} + +#[test] +fn xyz_to_srgb_test() { + let img: Image = setup("images/test/colorspace/srgb_xyz.png").unwrap().into(); + + let now = SystemTime::now(); + let proc = colorspace::xyz_to_srgb(&img); + println!("crop: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/test/colorspace/xyz_srgb.png").unwrap(); +} + +#[test] +fn srgb_to_lab_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = colorspace::srgb_to_lab(&img, &White::D50); + println!("crop: {}", now.elapsed().unwrap().as_millis()); + + write(&proc.into(), "images/test/colorspace/srgb_lab.png").unwrap(); +} + +#[test] +fn lab_to_srgb_test() { + let img: Image = setup("images/test/colorspace/srgb_lab.png").unwrap().into(); + + let now = SystemTime::now(); + let proc = colorspace::lab_to_srgb(&img, &White::D50); + println!("crop: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/test/colorspace/lab_srgb.png").unwrap(); +} + +#[test] +fn rgb_to_hsv_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = colorspace::rgb_to_hsv(&img); + println!("crop: {}", now.elapsed().unwrap().as_millis()); + + write(&proc.into(), "images/test/colorspace/rgb_hsv.png").unwrap(); +} + +#[test] +fn hsv_to_rgb_test() { + let img: Image = setup("images/test/colorspace/rgb_hsv.png").unwrap().into(); + + let now = SystemTime::now(); + let proc = colorspace::hsv_to_rgb(&img); + println!("crop: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/test/colorspace/hsv_rgb.png").unwrap(); +} \ No newline at end of file diff --git a/tests/common.rs b/tests/common.rs index cef2020..d2ecb05 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -2,12 +2,10 @@ use imgproc_rs::error::ImgIoResult; use imgproc_rs::io::read; use imgproc_rs::image::{BaseImage, Image}; -const PATH: &str = "images/yosemite.jpg"; +pub fn setup(path: &str) -> ImgIoResult> { + let img = read(path)?; -pub fn setup() -> ImgIoResult> { - let img = read(PATH)?; - - println!("Reading: {}", PATH); + println!("Reading: {}", path); println!("{}", img.info()); Ok(img) diff --git a/tests/filter_test.rs b/tests/filter_test.rs index 53acea3..e46faec 100644 --- a/tests/filter_test.rs +++ b/tests/filter_test.rs @@ -10,45 +10,47 @@ use imgproc_rs::io::write; use std::time::SystemTime; use imgproc_rs::enums::Bilateral; +const PATH: &str = "images/yosemite.jpg"; + // #[test] -fn box_filter_par() { - let img: Image = setup().unwrap().into(); +fn box_filter() { + let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::box_filter_par(&img, 5).unwrap(); + let filtered = filter::box_filter(&img, 5).unwrap(); println!("box filter: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/box_filter.png").unwrap(); } // #[test] -fn box_filter_normalized_par() { - let img: Image = setup().unwrap().into(); +fn box_filter_normalized() { + let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::box_filter_normalized_par(&img, 5).unwrap(); + let filtered = filter::box_filter_normalized(&img, 5).unwrap(); println!("box filter: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/box_filter_norm.png").unwrap(); } // #[test] -fn weighted_avg_filter_par() { - let img: Image = setup().unwrap().into(); +fn weighted_avg_filter() { + let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::weighted_avg_filter_par(&img, 5, 5).unwrap(); + let filtered = filter::weighted_avg_filter(&img, 5, 5).unwrap(); println!("weighted avg filter: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/weighted_avg.png").unwrap(); } // #[test] -fn gaussian_blur_par() { - let img: Image = setup().unwrap().into(); +fn gaussian_blur() { + let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::gaussian_blur_par(&img, 5, 1.0).unwrap(); + let filtered = filter::gaussian_blur(&img, 5, 1.0).unwrap(); println!("gaussian blur filter: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/gaussian_blur.png").unwrap(); @@ -56,7 +58,7 @@ fn gaussian_blur_par() { // #[test] fn median_filter() { - let img = setup().unwrap(); + let img = setup(PATH).unwrap(); let now = SystemTime::now(); let filtered = filter::median_filter(&img, 5).unwrap(); @@ -67,7 +69,7 @@ fn median_filter() { // #[test] fn alpha_trimmed_mean_filter() { - let img = setup().unwrap(); + let img = setup(PATH).unwrap(); let now = SystemTime::now(); let filtered = filter::alpha_trimmed_mean_filter(&img, 5, 2).unwrap(); @@ -77,66 +79,66 @@ fn alpha_trimmed_mean_filter() { } // #[test] -fn bilateral_filter_par() { - let img = setup().unwrap(); +fn bilateral_filter() { + let img = setup(PATH).unwrap(); let now = SystemTime::now(); - let direct = filter::bilateral_filter_par(&img, 10.0, 4.0, Bilateral::Direct).unwrap(); + let direct = filter::bilateral_filter(&img, 10.0, 4.0, Bilateral::Direct).unwrap(); println!("bilateral direct: {}", now.elapsed().unwrap().as_millis()); write(&direct.into(), "images/tests/filter/bilateral_direct.png").unwrap(); } // #[test] -fn sharpen_par() { - let img: Image = setup().unwrap().into(); +fn sharpen() { + let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::sharpen_par(&img).unwrap(); + let filtered = filter::sharpen(&img).unwrap(); println!("sharpen: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/sharpen.png").unwrap(); } // #[test] -fn unsharp_masking_par() { - let img: Image = setup().unwrap().into(); +fn unsharp_masking() { + let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::unsharp_masking_par(&img).unwrap(); + let filtered = filter::unsharp_masking(&img).unwrap(); println!("unsharp masking: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/unsharp_masking.png").unwrap(); } // #[test] -fn prewitt_par() { - let img: Image = setup().unwrap().into(); +fn prewitt() { + let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::prewitt_par(&img).unwrap(); + let filtered = filter::prewitt(&img).unwrap(); println!("prewitt: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/prewitt.png").unwrap(); } // #[test] -fn sobel_par() { - let img: Image = setup().unwrap().into(); +fn sobel() { + let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::sobel_par(&img).unwrap(); + let filtered = filter::sobel(&img).unwrap(); println!("sobel: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/sobel.png").unwrap(); } // #[test] -fn sobel_weighted_par() { - let img: Image = setup().unwrap().into(); +fn sobel_weighted() { + let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::sobel_weighted_par(&img, 5).unwrap(); + let filtered = filter::sobel_weighted(&img, 5).unwrap(); println!("sobel weighted: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/sobel_weighted.png").unwrap(); diff --git a/tests/morphology_test.rs b/tests/morphology_test.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/tone_test.rs b/tests/tone_test.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/transform_test.rs b/tests/transform_test.rs index f7d599e..90cea64 100644 --- a/tests/transform_test.rs +++ b/tests/transform_test.rs @@ -10,13 +10,15 @@ use imgproc_rs::io::write; use std::time::SystemTime; use imgproc_rs::enums::{Scale, Refl}; +const PATH: &str = "images/beach.jpg"; + // #[test] -fn crop_par() { - let img = setup().unwrap(); +fn crop() { + let img = setup(PATH).unwrap(); let (width, height) = img.info().wh(); let now = SystemTime::now(); - let output = transform::crop_par(&img, 0, 0, width/2, height/2).unwrap(); + let output = transform::crop(&img, 0, 0, width/2, height/2).unwrap(); println!("crop: {}", now.elapsed().unwrap().as_millis()); write(&output, "images/tests/transform/crop.png").unwrap(); @@ -24,7 +26,7 @@ fn crop_par() { // #[test] fn superimpose() { - let img: Image = setup().unwrap().into(); + let img: Image = setup(PATH).unwrap().into(); let (width, height) = img.info().wh(); let now = SystemTime::now(); @@ -36,7 +38,7 @@ fn superimpose() { // #[test] fn overlay() { - let img = setup().unwrap(); + let img = setup(PATH).unwrap(); let (width, height) = img.info().wh(); let now = SystemTime::now(); @@ -46,9 +48,9 @@ fn overlay() { write(&output.into(), "images/tests/transform/overlay.png").unwrap(); } -// #[test] -fn scale_twice_par() { - let img: Image = setup().unwrap().into(); +#[test] +fn scale_twice() { + let img: Image = setup(PATH).unwrap().into(); let mut now = SystemTime::now(); let nearest = transform::scale(&img, 2.0, 2.0, Scale::NearestNeighbor).unwrap(); @@ -63,7 +65,7 @@ fn scale_twice_par() { println!("bicubic: {}", now.elapsed().unwrap().as_millis()); now = SystemTime::now(); - let lanczos = transform::scale_par(&img, 2.0, 2.0, Scale::Lanczos).unwrap(); + let lanczos = transform::scale(&img, 2.0, 2.0, Scale::Lanczos).unwrap(); println!("lanczos: {}", now.elapsed().unwrap().as_millis()); write(&nearest.into(), "images/tests/transform/scale_nearest_twice.png").unwrap(); @@ -73,23 +75,23 @@ fn scale_twice_par() { } // #[test] -fn scale_half_par() { - let img: Image = setup().unwrap().into(); +fn scale_half() { + let img: Image = setup(PATH).unwrap().into(); let mut now = SystemTime::now(); - let nearest = transform::scale_par(&img, 0.5, 0.5, Scale::NearestNeighbor).unwrap(); + let nearest = transform::scale(&img, 0.5, 0.5, Scale::NearestNeighbor).unwrap(); println!("nearest: {}", now.elapsed().unwrap().as_millis()); now = SystemTime::now(); - let bilinear = transform::scale_par(&img, 0.5, 0.5, Scale::Bilinear).unwrap(); + let bilinear = transform::scale(&img, 0.5, 0.5, Scale::Bilinear).unwrap(); println!("bilinear: {}", now.elapsed().unwrap().as_millis()); now = SystemTime::now(); - let bicubic = transform::scale_par(&img, 0.5, 0.5, Scale::Bicubic).unwrap(); + let bicubic = transform::scale(&img, 0.5, 0.5, Scale::Bicubic).unwrap(); println!("bicubic: {}", now.elapsed().unwrap().as_millis()); now = SystemTime::now(); - let lanczos = transform::scale_par(&img, 0.5, 0.5, Scale::Lanczos).unwrap(); + let lanczos = transform::scale(&img, 0.5, 0.5, Scale::Lanczos).unwrap(); println!("lanczos: {}", now.elapsed().unwrap().as_millis()); write(&nearest.into(), "images/tests/transform/scale_nearest_half.png").unwrap(); @@ -100,7 +102,7 @@ fn scale_half_par() { // #[test] fn translate() { - let img = setup().unwrap(); + let img = setup(PATH).unwrap(); let (width, height) = img.info().wh(); let now = SystemTime::now(); @@ -112,7 +114,7 @@ fn translate() { // #[test] fn rotate() { - let img: Image = setup().unwrap().into(); + let img: Image = setup(PATH).unwrap().into(); let mut now = SystemTime::now(); let output_90 = transform::rotate(&img, 90.0).unwrap(); @@ -133,7 +135,7 @@ fn rotate() { // #[test] fn reflect() { - let img = setup().unwrap(); + let img = setup(PATH).unwrap(); let mut now = SystemTime::now(); let output_horz = transform::reflect(&img, Refl::Horizontal).unwrap(); @@ -149,7 +151,7 @@ fn reflect() { // #[test] fn shear() { - let img: Image = setup().unwrap().into(); + let img: Image = setup(PATH).unwrap().into(); let mut now = SystemTime::now(); let output_pp = transform::shear(&img, 0.5, 0.0).unwrap(); From 1f3a523fda49a89f52ebee177965c3a70773f36a Mon Sep 17 00:00:00 2001 From: tiffany1618 Date: Wed, 3 Mar 2021 14:13:40 -0800 Subject: [PATCH 2/6] Fixed contrast and brightness, added more tests, updated README.md --- README.md | 6 +++ src/colorspace.rs | 8 ++-- src/filter/mod.rs | 12 +----- src/tone.rs | 24 ++++++++---- src/transform.rs | 4 +- tests/colorspace_test.rs | 84 ++++++++++++++++++++++------------------ tests/filter_test.rs | 13 +------ tests/tone_test.rs | 78 +++++++++++++++++++++++++++++++++++++ tests/transform_test.rs | 2 +- 9 files changed, 156 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 5892cca..14b2b96 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ A Rust image processing library. `imgproc-rs` uses the i/o functions provided in the [`image`](https://github.com/image-rs/image) crate. A list of supported image formats can be found [here](https://docs.rs/image/0.23.12/image/codecs/index.html#supported-formats). +## Notes +Running with the `release` profile greatly increases performance: +``` +cargo run --release +``` + ## Examples ### Reading and Writing Images diff --git a/src/colorspace.rs b/src/colorspace.rs index e142029..334e088 100644 --- a/src/colorspace.rs +++ b/src/colorspace.rs @@ -81,7 +81,7 @@ pub fn xyz_to_srgb_lin(input: &Image) -> Image { /// Converts an image from CIE XYZ to CIELAB // Input: CIEXYZ range [0, 1] -// Output: CIELAB with L* channel range [0, 1] +// Output: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127] pub fn xyz_to_lab(input: &Image, ref_white: &White) -> Image { let (x_n, y_n, z_n) = util::generate_xyz_tristimulus_vals(ref_white); @@ -97,7 +97,7 @@ pub fn xyz_to_lab(input: &Image, ref_white: &White) -> Image { } /// Converts an image from CIELAB to CIE XYZ -// Input: CIELAB with L* channel range [0, 1] +// Input: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127] // Output: CIEXYZ range [0, 1] pub fn lab_to_xyz(input: &Image, ref_white: &White) -> Image { let (x_n, y_n, z_n) = util::generate_xyz_tristimulus_vals(ref_white); @@ -195,14 +195,14 @@ pub fn xyz_to_srgb(input: &Image) -> Image { /// Converts an image from sRGB to CIELAB // Input: sRGB range [0, 255] unlinearized -// Output: CIELAB +// Output: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127] pub fn srgb_to_lab(input: &Image, ref_white: &White) -> Image { let xyz = srgb_to_xyz(input); xyz_to_lab(&xyz, ref_white) } /// Converts an image from CIELAB to sRGB -// Input: CIELAB +// Input: CIELAB with L* channel range [0, 100] and a*, b* channels range [-128,127] // Output: sRGB range [0, 255] unlinearized pub fn lab_to_srgb(input: &Image, ref_white: &White) -> Image { let xyz = lab_to_xyz(input, ref_white); diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 58b0738..1785750 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -124,18 +124,8 @@ pub fn linear_filter(input: &Image, kernel: &[f64]) -> ImgProcResult, size: u32) -> ImgProcResult> { - error::check_odd(size, "size")?; - - let len = (size * size) as usize; - let kernel = vec![1.0; len]; - - Ok(separable_filter(input, &kernel, &kernel)?) -} - /// Applies a normalized box filter using a `size x size` kernel -pub fn box_filter_normalized(input: &Image, size: u32) -> ImgProcResult> { +pub fn box_filter(input: &Image, size: u32) -> ImgProcResult> { error::check_odd(size, "size")?; let len = (size * size) as usize; diff --git a/src/tone.rs b/src/tone.rs index 56ff678..9459e05 100644 --- a/src/tone.rs +++ b/src/tone.rs @@ -18,15 +18,19 @@ pub fn brightness(input: &Image, bias: i32, method: Tone) -> ImgProcResult { let mut lookup_table: [u8; 256] = [0; 256]; util::create_lookup_table(&mut lookup_table, |i| { - (i as i32 + bias) as u8 + (i as i32 + bias).clamp(0, 255) as u8 }); Ok(input.map_channels_if_alpha(|channel| lookup_table[channel as usize], |a| a)) }, Tone::Xyz => { - let mut xyz = colorspace::srgb_to_xyz(input); - xyz.edit_channel(|num| num + (bias as f64 / 255.0), 1); - Ok(colorspace::xyz_to_srgb(&xyz)) + let mut lab = colorspace::srgb_to_lab(input, &White::D50); + lab.edit_channel(|num| num + (bias as f64) * 255.0 / 100.0, 0); + Ok(colorspace::lab_to_srgb(&lab, &White::D50)) + + // let mut xyz = colorspace::srgb_to_xyz(input); + // xyz.edit_channel(|num| num + (bias as f64 / 255.0), 1); + // Ok(colorspace::xyz_to_srgb(&xyz)) }, } } @@ -41,15 +45,19 @@ pub fn contrast(input: &Image, gain: f64, method: Tone) -> ImgProcResult { let mut lookup_table: [u8; 256] = [0; 256]; util::create_lookup_table(&mut lookup_table, |i| { - (i as f64 * gain).round() as u8 + (i as f64 * gain).round().clamp(0.0, 255.0) as u8 }); Ok(input.map_channels_if_alpha(|channel| lookup_table[channel as usize], |a| a)) }, Tone::Xyz => { - let mut xyz = colorspace::srgb_to_xyz(input); - xyz.edit_channel(|num| num * gain, 1); - Ok(colorspace::xyz_to_srgb(&xyz)) + let mut lab = colorspace::srgb_to_lab(input, &White::D50); + lab.edit_channel(|num| num * gain, 0); + Ok(colorspace::lab_to_srgb(&lab, &White::D50)) + + // let mut xyz = colorspace::srgb_to_xyz(input); + // xyz.edit_channel(|num| num * gain, 1); + // Ok(colorspace::xyz_to_srgb(&xyz)) }, } } diff --git a/src/transform.rs b/src/transform.rs index 06ea8e5..6327f3d 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -1,6 +1,8 @@ //! A module for image transformation operations -use crate::{math, error, util}; +use crate::{math, error}; +#[cfg(feature = "rayon")] +use crate::util; use crate::image::{Number, Image, ImageInfo, BaseImage}; use crate::error::{ImgProcResult, ImgProcError}; use crate::enums::{Scale, Refl}; diff --git a/tests/colorspace_test.rs b/tests/colorspace_test.rs index 4e6b91b..43af5ab 100644 --- a/tests/colorspace_test.rs +++ b/tests/colorspace_test.rs @@ -3,7 +3,7 @@ mod common; use common::setup; -use imgproc_rs::colorspace; +use imgproc_rs::{colorspace, convert}; use imgproc_rs::image::{BaseImage, Image}; use imgproc_rs::io::write; @@ -12,90 +12,98 @@ use imgproc_rs::enums::White; const PATH: &str = "images/spectrum.jpg"; -#[test] +// #[test] fn rgb_to_grayscale_test() { let img = setup(PATH).unwrap(); let now = SystemTime::now(); let gray = colorspace::rgb_to_grayscale(&img); - println!("crop: {}", now.elapsed().unwrap().as_millis()); + println!("processing: {}", now.elapsed().unwrap().as_millis()); - write(&gray, "images/test/colorspace/gray.png").unwrap(); + write(&gray, "images/tests/colorspace/gray.png").unwrap(); } -#[test] +// #[test] fn rgb_to_grayscale_f64_test() { let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); let gray = colorspace::rgb_to_grayscale_f64(&img); - println!("crop: {}", now.elapsed().unwrap().as_millis()); + println!("processing: {}", now.elapsed().unwrap().as_millis()); - write(&gray.into(), "images/test/colorspace/gray_f64.png").unwrap(); + write(&gray.into(), "images/tests/colorspace/gray_f64.png").unwrap(); } -#[test] +// #[test] fn srgb_to_xyz_test() { let img = setup(PATH).unwrap(); let now = SystemTime::now(); let proc = colorspace::srgb_to_xyz(&img); - println!("crop: {}", now.elapsed().unwrap().as_millis()); + println!("processing: {}", now.elapsed().unwrap().as_millis()); - write(&proc.into(), "images/test/colorspace/srgb_xyz.png").unwrap(); + write(&convert::scale_channels(&proc, 1.0, 255.0).unwrap().into(), "images/tests/colorspace/srgb_xyz.png").unwrap(); } -#[test] +// #[test] fn xyz_to_srgb_test() { - let img: Image = setup("images/test/colorspace/srgb_xyz.png").unwrap().into(); + let img: Image = setup("images/tests/colorspace/srgb_xyz.png").unwrap().into(); let now = SystemTime::now(); - let proc = colorspace::xyz_to_srgb(&img); - println!("crop: {}", now.elapsed().unwrap().as_millis()); + let proc = colorspace::xyz_to_srgb(&convert::scale_channels(&img, 255.0, 1.0).unwrap()); + println!("processing: {}", now.elapsed().unwrap().as_millis()); - write(&proc, "images/test/colorspace/xyz_srgb.png").unwrap(); + write(&proc, "images/tests/colorspace/xyz_srgb.png").unwrap(); } -#[test] -fn srgb_to_lab_test() { +// #[test] +fn lab_test() { let img = setup(PATH).unwrap(); let now = SystemTime::now(); - let proc = colorspace::srgb_to_lab(&img, &White::D50); - println!("crop: {}", now.elapsed().unwrap().as_millis()); - - write(&proc.into(), "images/test/colorspace/srgb_lab.png").unwrap(); -} - -#[test] -fn lab_to_srgb_test() { - let img: Image = setup("images/test/colorspace/srgb_lab.png").unwrap().into(); + let lab = colorspace::srgb_to_lab(&img, &White::D50); + println!("lab: {}", now.elapsed().unwrap().as_millis()); let now = SystemTime::now(); - let proc = colorspace::lab_to_srgb(&img, &White::D50); - println!("crop: {}", now.elapsed().unwrap().as_millis()); - - write(&proc, "images/test/colorspace/lab_srgb.png").unwrap(); + let proc = colorspace::lab_to_srgb(&lab, &White::D50); + println!("rgb: {}", now.elapsed().unwrap().as_millis()); + + // for c in 0..(proc.info().channels as usize) { + // let mut max = -255.0; + // let mut min = 255.0; + // + // for i in 0..(proc.info().size() as usize) { + // if proc[i][c] < min { + // min = proc[i][c]; + // } + // if proc[i][c] > max { + // max = proc[i][c]; + // } + // } + // println!("{}: {}, {}", c, min, max); + // } + + write(&proc, "images/tests/colorspace/lab_srgb.png").unwrap(); } -#[test] +// #[test] fn rgb_to_hsv_test() { let img = setup(PATH).unwrap(); let now = SystemTime::now(); let proc = colorspace::rgb_to_hsv(&img); - println!("crop: {}", now.elapsed().unwrap().as_millis()); + println!("processing: {}", now.elapsed().unwrap().as_millis()); - write(&proc.into(), "images/test/colorspace/rgb_hsv.png").unwrap(); + write(&convert::scale_channels(&proc, 1.0, 255.0).unwrap().into(), "images/tests/colorspace/rgb_hsv.png").unwrap(); } -#[test] +// #[test] fn hsv_to_rgb_test() { - let img: Image = setup("images/test/colorspace/rgb_hsv.png").unwrap().into(); + let img: Image = setup("images/tests/colorspace/rgb_hsv.png").unwrap().into(); let now = SystemTime::now(); - let proc = colorspace::hsv_to_rgb(&img); - println!("crop: {}", now.elapsed().unwrap().as_millis()); + let proc = colorspace::hsv_to_rgb(&convert::scale_channels(&img, 255.0, 1.0).unwrap()); + println!("processing: {}", now.elapsed().unwrap().as_millis()); - write(&proc, "images/test/colorspace/hsv_rgb.png").unwrap(); + write(&proc, "images/tests/colorspace/hsv_rgb.png").unwrap(); } \ No newline at end of file diff --git a/tests/filter_test.rs b/tests/filter_test.rs index e46faec..1347873 100644 --- a/tests/filter_test.rs +++ b/tests/filter_test.rs @@ -23,17 +23,6 @@ fn box_filter() { write(&filtered.into(), "images/tests/filter/box_filter.png").unwrap(); } -// #[test] -fn box_filter_normalized() { - let img: Image = setup(PATH).unwrap().into(); - - let now = SystemTime::now(); - let filtered = filter::box_filter_normalized(&img, 5).unwrap(); - println!("box filter: {}", now.elapsed().unwrap().as_millis()); - - write(&filtered.into(), "images/tests/filter/box_filter_norm.png").unwrap(); -} - // #[test] fn weighted_avg_filter() { let img: Image = setup(PATH).unwrap().into(); @@ -80,7 +69,7 @@ fn alpha_trimmed_mean_filter() { // #[test] fn bilateral_filter() { - let img = setup(PATH).unwrap(); + let img = setup("images/scaled.png").unwrap(); let now = SystemTime::now(); let direct = filter::bilateral_filter(&img, 10.0, 4.0, Bilateral::Direct).unwrap(); diff --git a/tests/tone_test.rs b/tests/tone_test.rs index e69de29..d7db28b 100644 --- a/tests/tone_test.rs +++ b/tests/tone_test.rs @@ -0,0 +1,78 @@ +#![allow(dead_code)] + +mod common; + +use common::setup; +use imgproc_rs::tone; +use imgproc_rs::image::Image; +use imgproc_rs::io::write; + +use std::time::SystemTime; +use imgproc_rs::enums::{Bilateral, Tone, White}; + +const PATH: &str = "images/beach.jpg"; + +// #[test] +fn brightness_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + // let proc = tone::brightness(&img, 20, Tone::Rgb).unwrap(); + println!("rgb: {}", now.elapsed().unwrap().as_millis()); + + let now = SystemTime::now(); + let proc2 = tone::brightness(&img, 20, Tone::Xyz).unwrap(); + println!("xyz: {}", now.elapsed().unwrap().as_millis()); + + // write(&proc, "images/tests/tone/bright_rgb.png").unwrap(); + write(&proc2, "images/tests/tone/bright_xyz.png").unwrap(); +} + +// #[test] +fn contrast_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + // let proc = tone::contrast(&img, 1.5, Tone::Rgb).unwrap(); + println!("rgb: {}", now.elapsed().unwrap().as_millis()); + + let now = SystemTime::now(); + let proc2 = tone::contrast(&img, 1.5, Tone::Xyz).unwrap(); + println!("xyz: {}", now.elapsed().unwrap().as_millis()); + + // write(&proc, "images/tests/tone/contrast_rgb.png").unwrap(); + write(&proc2, "images/tests/tone/contrast_xyz.png").unwrap(); +} + +// #[test] +fn saturation_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = tone::saturation(&img, 10).unwrap(); + println!("processing: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/tests/tone/saturation.png").unwrap(); +} + +// #[test] +fn gamma_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = tone::gamma(&img, 1.5, 255).unwrap(); + println!("processing: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/tests/tone/gamma.png").unwrap(); +} + +// #[test] +fn histogram_equalization_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = tone::histogram_equalization(&img, 0.5, &White::D50, 255.0).unwrap(); + println!("processing: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/tests/tone/histogram.png").unwrap(); +} diff --git a/tests/transform_test.rs b/tests/transform_test.rs index 90cea64..71fb796 100644 --- a/tests/transform_test.rs +++ b/tests/transform_test.rs @@ -48,7 +48,7 @@ fn overlay() { write(&output.into(), "images/tests/transform/overlay.png").unwrap(); } -#[test] +// #[test] fn scale_twice() { let img: Image = setup(PATH).unwrap().into(); From f537b5dcb8574434b61b48b9d8545fc8a552cd2b Mon Sep 17 00:00:00 2001 From: tiffany1618 Date: Wed, 3 Mar 2021 14:15:17 -0800 Subject: [PATCH 3/6] Replaced enum Tone::Xyz with Tone::Lab --- src/enums.rs | 2 +- src/tone.rs | 12 ++---------- tests/tone_test.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/enums.rs b/src/enums.rs index f2227e1..2db8fcc 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -6,7 +6,7 @@ pub enum Tone { Rgb, /// Tone operation should be carried out using XYZ channels - Xyz, + Lab, } /// An enum for reference white values diff --git a/src/tone.rs b/src/tone.rs index 9459e05..799fad3 100644 --- a/src/tone.rs +++ b/src/tone.rs @@ -23,14 +23,10 @@ pub fn brightness(input: &Image, bias: i32, method: Tone) -> ImgProcResult { + Tone::Lab => { let mut lab = colorspace::srgb_to_lab(input, &White::D50); lab.edit_channel(|num| num + (bias as f64) * 255.0 / 100.0, 0); Ok(colorspace::lab_to_srgb(&lab, &White::D50)) - - // let mut xyz = colorspace::srgb_to_xyz(input); - // xyz.edit_channel(|num| num + (bias as f64 / 255.0), 1); - // Ok(colorspace::xyz_to_srgb(&xyz)) }, } } @@ -50,14 +46,10 @@ pub fn contrast(input: &Image, gain: f64, method: Tone) -> ImgProcResult { + Tone::Lab => { let mut lab = colorspace::srgb_to_lab(input, &White::D50); lab.edit_channel(|num| num * gain, 0); Ok(colorspace::lab_to_srgb(&lab, &White::D50)) - - // let mut xyz = colorspace::srgb_to_xyz(input); - // xyz.edit_channel(|num| num * gain, 1); - // Ok(colorspace::xyz_to_srgb(&xyz)) }, } } diff --git a/tests/tone_test.rs b/tests/tone_test.rs index d7db28b..69b6c79 100644 --- a/tests/tone_test.rs +++ b/tests/tone_test.rs @@ -17,14 +17,14 @@ fn brightness_test() { let img = setup(PATH).unwrap(); let now = SystemTime::now(); - // let proc = tone::brightness(&img, 20, Tone::Rgb).unwrap(); + let proc = tone::brightness(&img, 20, Tone::Rgb).unwrap(); println!("rgb: {}", now.elapsed().unwrap().as_millis()); let now = SystemTime::now(); - let proc2 = tone::brightness(&img, 20, Tone::Xyz).unwrap(); + let proc2 = tone::brightness(&img, 20, Tone::Lab).unwrap(); println!("xyz: {}", now.elapsed().unwrap().as_millis()); - // write(&proc, "images/tests/tone/bright_rgb.png").unwrap(); + write(&proc, "images/tests/tone/bright_rgb.png").unwrap(); write(&proc2, "images/tests/tone/bright_xyz.png").unwrap(); } @@ -33,14 +33,14 @@ fn contrast_test() { let img = setup(PATH).unwrap(); let now = SystemTime::now(); - // let proc = tone::contrast(&img, 1.5, Tone::Rgb).unwrap(); + let proc = tone::contrast(&img, 1.5, Tone::Rgb).unwrap(); println!("rgb: {}", now.elapsed().unwrap().as_millis()); let now = SystemTime::now(); - let proc2 = tone::contrast(&img, 1.5, Tone::Xyz).unwrap(); + let proc2 = tone::contrast(&img, 1.5, Tone::Lab).unwrap(); println!("xyz: {}", now.elapsed().unwrap().as_millis()); - // write(&proc, "images/tests/tone/contrast_rgb.png").unwrap(); + write(&proc, "images/tests/tone/contrast_rgb.png").unwrap(); write(&proc2, "images/tests/tone/contrast_xyz.png").unwrap(); } From e064fa6ed19ae78a16142b50d6220e665b564dc3 Mon Sep 17 00:00:00 2001 From: tiffany1618 Date: Wed, 3 Mar 2021 16:22:35 -0800 Subject: [PATCH 4/6] Added morphology tests --- src/enums.rs | 2 +- src/morphology.rs | 4 +- tests/morphology_test.rs | 79 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/enums.rs b/src/enums.rs index 2db8fcc..12fff27 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -5,7 +5,7 @@ pub enum Tone { /// Tone operation should be carried out using an RGB channels Rgb, - /// Tone operation should be carried out using XYZ channels + /// Tone operation should be carried out using CIELAB channels Lab, } diff --git a/src/morphology.rs b/src/morphology.rs index d9366db..800fc89 100644 --- a/src/morphology.rs +++ b/src/morphology.rs @@ -48,8 +48,6 @@ pub fn dilate(input: &Image, radius: u32) -> ImgProcResult> { error::check_grayscale(input.info().channels, input.info().alpha)?; let (width, height) = input.info().wh(); - let size = 2 * radius + 1; - let max_sum = (size * size * 255) as f64 / 2.0; let table = util::summed_area_table(&input.clone().into()); let mut output = Image::blank(input.info()); @@ -73,7 +71,7 @@ pub fn dilate(input: &Image, radius: u32) -> ImgProcResult> { y_bot += radius; } - if util::rectangular_intensity_sum(&table, x_top, y_top, x_bot, y_bot)[0] >= max_sum { + if util::rectangular_intensity_sum(&table, x_top, y_top, x_bot, y_bot)[0] >= 255.0 { output.set_pixel(x, y, &[255]); } } diff --git a/tests/morphology_test.rs b/tests/morphology_test.rs index e69de29..ba77aee 100644 --- a/tests/morphology_test.rs +++ b/tests/morphology_test.rs @@ -0,0 +1,79 @@ +#![allow(dead_code)] + +mod common; + +use common::setup; +use imgproc_rs::{morphology, colorspace}; +use imgproc_rs::image::Image; +use imgproc_rs::io::write; + +use std::time::SystemTime; +use imgproc_rs::enums::Bilateral; + +const PATH: &str = "images/j.png"; + +// #[test] +fn erode_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = morphology::erode(&colorspace::rgb_to_grayscale(&img), 3).unwrap(); + println!("processing: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/tests/morphology/erode.png").unwrap(); +} + +// #[test] +fn dilate_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = morphology::dilate(&colorspace::rgb_to_grayscale(&img), 3).unwrap(); + println!("processing: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/tests/morphology/dilate.png").unwrap(); +} + +// #[test] +fn majority_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = morphology::majority(&colorspace::rgb_to_grayscale(&img), 3).unwrap(); + println!("processing: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/tests/morphology/majority.png").unwrap(); +} + +// #[test] +fn open_test() { + let img = setup("images/j_open.png").unwrap(); + + let now = SystemTime::now(); + let proc = morphology::open(&colorspace::rgb_to_grayscale(&img), 3).unwrap(); + println!("processing: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/tests/morphology/open.png").unwrap(); +} + +// #[test] +fn close_test() { + let img = setup("images/j_close.png").unwrap(); + + let now = SystemTime::now(); + let proc = morphology::close(&colorspace::rgb_to_grayscale(&img), 3).unwrap(); + println!("processing: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/tests/morphology/close.png").unwrap(); +} + +// #[test] +fn gradient_test() { + let img = setup(PATH).unwrap(); + + let now = SystemTime::now(); + let proc = morphology::gradient(&colorspace::rgb_to_grayscale(&img), 3).unwrap(); + println!("processing: {}", now.elapsed().unwrap().as_millis()); + + write(&proc, "images/tests/morphology/gradient.png").unwrap(); +} \ No newline at end of file From 5e57f94abbe6d624e46b158a479153582b0d995a Mon Sep 17 00:00:00 2001 From: tiffany1618 Date: Wed, 3 Mar 2021 17:08:20 -0800 Subject: [PATCH 5/6] Improved tests --- tests/colorspace_test.rs | 2 +- tests/filter_test.rs | 14 +++++++------- tests/morphology_test.rs | 2 -- tests/tone_test.rs | 3 +-- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/colorspace_test.rs b/tests/colorspace_test.rs index 43af5ab..55b4145 100644 --- a/tests/colorspace_test.rs +++ b/tests/colorspace_test.rs @@ -4,7 +4,7 @@ mod common; use common::setup; use imgproc_rs::{colorspace, convert}; -use imgproc_rs::image::{BaseImage, Image}; +use imgproc_rs::image::Image; use imgproc_rs::io::write; use std::time::SystemTime; diff --git a/tests/filter_test.rs b/tests/filter_test.rs index 1347873..9f0a513 100644 --- a/tests/filter_test.rs +++ b/tests/filter_test.rs @@ -39,7 +39,7 @@ fn gaussian_blur() { let img: Image = setup(PATH).unwrap().into(); let now = SystemTime::now(); - let filtered = filter::gaussian_blur(&img, 5, 1.0).unwrap(); + let filtered = filter::gaussian_blur(&img, 3, 1.0).unwrap(); println!("gaussian blur filter: {}", now.elapsed().unwrap().as_millis()); write(&filtered.into(), "images/tests/filter/gaussian_blur.png").unwrap(); @@ -100,9 +100,9 @@ fn unsharp_masking() { write(&filtered.into(), "images/tests/filter/unsharp_masking.png").unwrap(); } -// #[test] +#[test] fn prewitt() { - let img: Image = setup(PATH).unwrap().into(); + let img: Image = setup("images/poppy.jpg").unwrap().into(); let now = SystemTime::now(); let filtered = filter::prewitt(&img).unwrap(); @@ -111,9 +111,9 @@ fn prewitt() { write(&filtered.into(), "images/tests/filter/prewitt.png").unwrap(); } -// #[test] +#[test] fn sobel() { - let img: Image = setup(PATH).unwrap().into(); + let img: Image = setup("images/poppy.jpg").unwrap().into(); let now = SystemTime::now(); let filtered = filter::sobel(&img).unwrap(); @@ -122,9 +122,9 @@ fn sobel() { write(&filtered.into(), "images/tests/filter/sobel.png").unwrap(); } -// #[test] +#[test] fn sobel_weighted() { - let img: Image = setup(PATH).unwrap().into(); + let img: Image = setup("images/poppy.jpg").unwrap().into(); let now = SystemTime::now(); let filtered = filter::sobel_weighted(&img, 5).unwrap(); diff --git a/tests/morphology_test.rs b/tests/morphology_test.rs index ba77aee..016423e 100644 --- a/tests/morphology_test.rs +++ b/tests/morphology_test.rs @@ -4,11 +4,9 @@ mod common; use common::setup; use imgproc_rs::{morphology, colorspace}; -use imgproc_rs::image::Image; use imgproc_rs::io::write; use std::time::SystemTime; -use imgproc_rs::enums::Bilateral; const PATH: &str = "images/j.png"; diff --git a/tests/tone_test.rs b/tests/tone_test.rs index 69b6c79..2b29a45 100644 --- a/tests/tone_test.rs +++ b/tests/tone_test.rs @@ -4,11 +4,10 @@ mod common; use common::setup; use imgproc_rs::tone; -use imgproc_rs::image::Image; use imgproc_rs::io::write; use std::time::SystemTime; -use imgproc_rs::enums::{Bilateral, Tone, White}; +use imgproc_rs::enums::{Tone, White}; const PATH: &str = "images/beach.jpg"; From b034191e38a7c19f0eadbdcddac29a42e2cae7cf Mon Sep 17 00:00:00 2001 From: tiffany1618 Date: Wed, 3 Mar 2021 20:02:48 -0800 Subject: [PATCH 6/6] Added pixel iterator --- src/image/mod.rs | 4 +++- src/image/pixel_iter.rs | 53 +++++++++++++++++++++++++++++++++++++++++ tests/filter_test.rs | 41 +++++++++++++++++++++++++++---- 3 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 src/image/pixel_iter.rs diff --git a/src/image/mod.rs b/src/image/mod.rs index 1cc8394..f3d9156 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -58,10 +58,12 @@ pub use self::sub_image::*; pub use self::pixel::*; pub use self::from_impl::*; +pub use self::pixel_iter::*; mod sub_image; mod pixel; mod from_impl; +mod pixel_iter; use crate::error; @@ -398,7 +400,7 @@ impl Image { /// Applies function `f` to each pixel pub fn map_pixels(&self, f: F) -> Image where F: Fn(&[T]) -> Vec { - let mut data= Vec::new(); + let mut data= Vec::new(); for i in 0..(self.info.size() as usize) { data.append(&mut f(&self[i])); diff --git a/src/image/pixel_iter.rs b/src/image/pixel_iter.rs new file mode 100644 index 0000000..af83648 --- /dev/null +++ b/src/image/pixel_iter.rs @@ -0,0 +1,53 @@ +use crate::image::{Image, BaseImage, Number}; + +/// A struct representing a pixel iterator for an image +#[derive(Debug, Clone)] +pub struct PixelIter<'a, T: Number> { + image: &'a Image, + x: u32, + y: u32, + x_max: u32, + y_max: u32, +} + +impl<'a, T: Number> PixelIter<'a, T> { + pub fn new(image: &'a Image) -> Self { + PixelIter { + image, + x: 0, + y: 0, + x_max: image.info().width - 1, + y_max: image.info().height - 1, + } + } +} + +impl<'a, T: Number> Iterator for PixelIter<'a, T> { + type Item = (u32, u32, &'a [T]); + + fn next(&mut self) -> Option { + if self.x > self.x_max { + if self.y >= self.y_max { + return None; + } else { + self.x = 0; + self.y += 1; + } + } + + let temp_x = self.x; + let temp_y = self.y; + self.x += 1; + + Some((temp_x, temp_y, self.image.get_pixel(temp_x, temp_y))) + } +} + +impl<'a, T: Number> IntoIterator for &'a Image { + type Item = (u32, u32, &'a [T]); + type IntoIter = PixelIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + PixelIter::new(&self) + } +} \ No newline at end of file diff --git a/tests/filter_test.rs b/tests/filter_test.rs index 9f0a513..d48499e 100644 --- a/tests/filter_test.rs +++ b/tests/filter_test.rs @@ -3,12 +3,12 @@ mod common; use common::setup; -use imgproc_rs::filter; +use imgproc_rs::{filter, colorspace}; use imgproc_rs::image::Image; use imgproc_rs::io::write; use std::time::SystemTime; -use imgproc_rs::enums::Bilateral; +use imgproc_rs::enums::{Bilateral, Thresh}; const PATH: &str = "images/yosemite.jpg"; @@ -100,7 +100,7 @@ fn unsharp_masking() { write(&filtered.into(), "images/tests/filter/unsharp_masking.png").unwrap(); } -#[test] +// #[test] fn prewitt() { let img: Image = setup("images/poppy.jpg").unwrap().into(); @@ -111,7 +111,7 @@ fn prewitt() { write(&filtered.into(), "images/tests/filter/prewitt.png").unwrap(); } -#[test] +// #[test] fn sobel() { let img: Image = setup("images/poppy.jpg").unwrap().into(); @@ -122,7 +122,7 @@ fn sobel() { write(&filtered.into(), "images/tests/filter/sobel.png").unwrap(); } -#[test] +// #[test] fn sobel_weighted() { let img: Image = setup("images/poppy.jpg").unwrap().into(); @@ -132,3 +132,34 @@ fn sobel_weighted() { write(&filtered.into(), "images/tests/filter/sobel_weighted.png").unwrap(); } + +// #[test] +fn threshold_test() { + let img: Image = colorspace::rgb_to_grayscale(&setup(PATH).unwrap()).into(); + + let mut now = SystemTime::now(); + let bin = filter::threshold(&img, 100.0, 255.0, Thresh::Binary).unwrap(); + println!("bin: {}", now.elapsed().unwrap().as_millis()); + + now = SystemTime::now(); + let bin_inv = filter::threshold(&img, 100.0, 255.0, Thresh::BinaryInv).unwrap(); + println!("bin inv: {}", now.elapsed().unwrap().as_millis()); + + now = SystemTime::now(); + let trunc = filter::threshold(&img, 100.0, 255.0, Thresh::Trunc).unwrap(); + println!("trunc: {}", now.elapsed().unwrap().as_millis()); + + now = SystemTime::now(); + let zero = filter::threshold(&img, 100.0, 255.0, Thresh::ToZero).unwrap(); + println!("zero: {}", now.elapsed().unwrap().as_millis()); + + now = SystemTime::now(); + let zero_inv = filter::threshold(&img, 100.0, 255.0, Thresh::ToZeroInv).unwrap(); + println!("zero inv: {}", now.elapsed().unwrap().as_millis()); + + write(&bin.into(), "images/tests/filter/thresh_binary.png").unwrap(); + write(&bin_inv.into(), "images/tests/filter/thresh_binary_inv.png").unwrap(); + write(&trunc.into(), "images/tests/filter/thresh_trunc.png").unwrap(); + write(&zero.into(), "images/tests/filter/thresh_to_zero.png").unwrap(); + write(&zero_inv.into(), "images/tests/filter/thresh_to_zero_inv.png").unwrap(); +} \ No newline at end of file