Added Custom variant for FilterType enum and corresponding Filter structure.

This commit is contained in:
Kirill Kuzminykh
2023-12-30 13:55:41 +03:00
parent 1b6f61a48f
commit f6d843891d
5 changed files with 157 additions and 19 deletions
+6 -1
View File
@@ -1,14 +1,19 @@
## [Unreleased] - ReleaseDate
### Added
- Added `Custom` variant for `FilterType` enum and corresponding `Filter` structure.
### Changed
- Slightly improved (about 3%) speed of `AVX2` implementation of `Convolution` trait
for `U8x3` and `U8x4` images.
- **BREAKING**: Changed internal data type for `U8x4` structure.
Now it is `[u8; 4]` instead of `u32`.
- Significantly improved (4.5 times on `x86_64`) speed of vertical convolution pass implemented
in native Rust for `U8`, `U8x2`, `U8x3` and `U8x4` images.
- Changed order of convolution passes for `U8`, `U8x2`, `U8x3` and `U8x4` images.
Now the vertical pass is the first and the horizontal pass is the second.
- **BREAKING**: Changed internal data type for `U8x4` structure. Not it is `[u8; 4]` instead of `u32`.
## [2.7.3] - 2023-05-07
+74 -1
View File
@@ -1,7 +1,77 @@
use std::f64::consts::PI;
use std::fmt::{Debug, Formatter};
use thiserror::Error;
pub type FilterFn<'a> = &'a dyn Fn(f64) -> f64;
/// Description of custom filter for image convolution.
#[derive(Clone, Copy)]
pub struct Filter<'f> {
/// Name of filter
name: &'static str,
/// Filter function
func: FilterFn<'f>,
/// Minimal "radius" of kernel in pixels
support: f64,
}
impl<'f> PartialEq for Filter<'f> {
fn eq(&self, other: &Self) -> bool {
self.support == other.support && self.name == other.name
}
}
impl<'f> Eq for Filter<'f> {}
impl<'f> Debug for Filter<'f> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Filter")
.field("name", &self.name)
.field("support", &self.support)
.finish()
}
}
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum CreateFilterError {
/// Value of 'support' argument must be finite and greater than 0.0
#[error("Value of 'support' argument must be finite and greater than 0.0")]
InvalidSupport,
}
impl<'f> Filter<'f> {
/// # Arguments
///
/// * `name` - Name of filter
/// * `func` - Filter function
/// * `support` - Minimal "radius" of kernel in pixels
pub fn new(
name: &'static str,
func: FilterFn<'f>,
support: f64,
) -> Result<Self, CreateFilterError> {
if support.is_finite() && support > 0.0 {
Ok(Self {
name,
func,
support,
})
} else {
Err(CreateFilterError::InvalidSupport)
}
}
/// Name of filter
pub fn name(&self) -> &'static str {
self.name
}
/// Minimal "radius" of kernel in pixels
pub fn support(&self) -> f64 {
self.support
}
}
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FilterType {
@@ -43,11 +113,13 @@ pub enum FilterType {
/// Minimal kernel size 6x6 px.
#[default]
Lanczos3,
/// Custom filter function.
Custom(Filter<'static>),
}
/// Returns reference to filter function and value of `filter_support`.
#[inline]
pub fn get_filter_func(filter_type: FilterType) -> (FilterFn<'static>, f64) {
pub(crate) fn get_filter_func(filter_type: FilterType) -> (FilterFn<'static>, f64) {
match filter_type {
FilterType::Box => (&box_filter, 0.5),
FilterType::Bilinear => (&bilinear_filter, 1.0),
@@ -55,6 +127,7 @@ pub fn get_filter_func(filter_type: FilterType) -> (FilterFn<'static>, f64) {
FilterType::CatmullRom => (&catmul_filter, 2.0),
FilterType::Mitchell => (&mitchell_filter, 2.0),
FilterType::Lanczos3 => (&lanczos_filter, 3.0),
FilterType::Custom(custom) => (custom.func, custom.support),
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
use std::num::NonZeroU32;
pub use filters::{get_filter_func, FilterType};
pub use filters::*;
use crate::pixels::PixelExt;
use crate::CpuExtensions;
@@ -50,20 +50,20 @@ where
}
#[derive(Debug, Clone, Copy)]
pub struct Bound {
pub(crate) struct Bound {
pub start: u32,
pub size: u32,
}
#[derive(Debug, Clone)]
pub struct Coefficients {
pub(crate) struct Coefficients {
pub values: Vec<f64>,
pub window_size: usize,
pub bounds: Vec<Bound>,
}
#[derive(Debug, Clone, Copy)]
pub struct CoefficientsChunk<'a> {
pub(crate) struct CoefficientsChunk<'a> {
pub start: u32,
pub values: &'a [f64],
}
@@ -85,7 +85,7 @@ impl Coefficients {
}
}
pub fn precompute_coefficients(
pub(crate) fn precompute_coefficients(
in_size: NonZeroU32,
in0: f64, // Left border for cropping
in1: f64, // Right border for cropping
+1 -1
View File
@@ -3,7 +3,7 @@
pub use alpha::errors::*;
pub use color::mappers::*;
pub use color::PixelComponentMapper;
pub use convolution::FilterType;
pub use convolution::*;
pub use dynamic_image_view::{
change_type_of_pixel_components_dyn, DynamicImageView, DynamicImageViewMut,
};
+71 -11
View File
@@ -407,7 +407,7 @@ mod not_u8x4 {
P::downscale_test(
ResizeAlg::Convolution(FilterType::Lanczos3),
cpu_extensions,
[2923557],
[2923555],
);
}
}
@@ -435,7 +435,7 @@ mod not_u8x4 {
P::upscale_test(
ResizeAlg::Convolution(FilterType::Lanczos3),
cpu_extensions,
[1148811406],
[1148811829],
);
}
}
@@ -463,7 +463,7 @@ mod not_u8x4 {
P::downscale_test(
ResizeAlg::Convolution(FilterType::Lanczos3),
cpu_extensions,
[2923557, 6122818],
[2923555, 6122718],
);
}
}
@@ -495,7 +495,7 @@ mod not_u8x4 {
P::upscale_test(
ResizeAlg::Convolution(FilterType::Lanczos3),
cpu_extensions,
[1146283728, 2364890194],
[1146284886, 2364890085],
);
}
}
@@ -527,7 +527,7 @@ mod not_u8x4 {
P::downscale_test(
ResizeAlg::Convolution(FilterType::Lanczos3),
cpu_extensions,
[2942479, 2947850, 2885072],
[2942547, 2947799, 2885025],
);
}
}
@@ -559,7 +559,7 @@ mod not_u8x4 {
P::upscale_test(
ResizeAlg::Convolution(FilterType::Lanczos3),
cpu_extensions,
[1156107005, 1158443335, 1135101759],
[1156107445, 1158443938, 1135102297],
);
}
}
@@ -814,11 +814,16 @@ mod not_u8x4 {
}
mod u8x4 {
use std::f64::consts::PI;
use fast_image_resize::Filter;
use super::*;
type P = U8x4;
#[test]
fn downscale_u8x4() {
type P = U8x4;
P::downscale_test(
ResizeAlg::Nearest,
CpuExtensions::None,
@@ -843,20 +848,19 @@ mod u8x4 {
P::downscale_test(
ResizeAlg::Convolution(FilterType::Lanczos3),
cpu_extensions,
[2942479, 2947850, 2885072, 6122818],
[2942547, 2947799, 2885025, 6122718],
);
P::downscale_test(
ResizeAlg::SuperSampling(FilterType::Lanczos3, 2),
cpu_extensions,
[2942546, 2947627, 2884866, 6123158],
[2942426, 2947750, 2884861, 6123019],
);
}
}
#[test]
fn upscale_u8x4() {
type P = U8x4;
P::upscale_test(
ResizeAlg::Nearest,
CpuExtensions::None,
@@ -881,8 +885,64 @@ mod u8x4 {
P::upscale_test(
ResizeAlg::Convolution(FilterType::Lanczos3),
cpu_extensions,
[1155201788, 1152688479, 1123328716, 2364890194],
[1155201879, 1152689565, 1123329272, 2364890085],
);
}
}
#[test]
fn custom_filter_u8x4() {
std::env::set_var("DONT_SAVE_RESULT", "1");
const LANCZOS3_RESULT: [u64; 4] = [2942547, 2947799, 2885025, 6122718];
const LANCZOS4_RESULT: [u64; 4] = [2943083, 2948315, 2885436, 6122629];
P::downscale_test(
ResizeAlg::Convolution(FilterType::Lanczos3),
CpuExtensions::None,
LANCZOS3_RESULT,
);
fn sinc_filter(mut x: f64) -> f64 {
if x == 0.0 {
1.0
} else {
x *= PI;
x.sin() / x
}
}
fn lanczos3_filter(x: f64) -> f64 {
if (-3.0..3.0).contains(&x) {
sinc_filter(x) * sinc_filter(x / 3.)
} else {
0.0
}
}
for bad_support in [0.0, -1.0, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(Filter::new("bad_support", &lanczos3_filter, bad_support).is_err());
}
let my_lanczos3 = Filter::new("MyLanczos3", &lanczos3_filter, 3.0).unwrap();
P::downscale_test(
ResizeAlg::Convolution(FilterType::Custom(my_lanczos3)),
CpuExtensions::None,
LANCZOS3_RESULT,
);
fn lanczos4_filter(x: f64) -> f64 {
if (-4.0..4.0).contains(&x) {
sinc_filter(x) * sinc_filter(x / 4.)
} else {
0.0
}
}
let my_lanczos4 = Filter::new("MyLanczos4", &lanczos4_filter, 4.0).unwrap();
P::downscale_test(
ResizeAlg::Convolution(FilterType::Custom(my_lanczos4)),
CpuExtensions::None,
LANCZOS4_RESULT,
);
}
}