use alloc::vec::Vec;
use crate::filter_effects::EdgeMode;
use crate::kurbo::Affine;
use crate::util::extract_scales;
use core::f32::consts::E;
#[cfg(not(feature = "std"))]
use peniko::kurbo::common::FloatFuncs as _;
pub(crate) fn transform_blur_params(std_deviation: f32, transform: &Affine) -> f32 {
let (scale_x, scale_y) = extract_scales(transform);
let uniform_scale = (scale_x + scale_y) / 2.0;
std_deviation * uniform_scale
}
pub const MAX_KERNEL_SIZE: usize = 13;
#[cfg(test)]
const _: () = const {
if MAX_KERNEL_SIZE.is_multiple_of(2) {
panic!("`MAX_KERNEL_SIZE` must be odd");
}
if MAX_KERNEL_SIZE > u8::MAX as usize {
panic!("`MAX_KERNEL_SIZE` must be less than or equal to `u8::MAX`");
}
};
#[derive(Debug)]
pub struct GaussianBlur {
pub std_deviation: f32,
pub n_decimations: usize,
pub kernel: [f32; MAX_KERNEL_SIZE],
pub kernel_size: u8,
pub edge_mode: EdgeMode,
}
impl GaussianBlur {
pub fn new(std_deviation: f32, edge_mode: EdgeMode) -> Self {
let (n_decimations, kernel, kernel_size) = plan_decimated_blur(std_deviation);
Self {
std_deviation,
edge_mode,
n_decimations,
kernel,
kernel_size,
}
}
}
pub fn plan_decimated_blur(std_deviation: f32) -> (usize, [f32; MAX_KERNEL_SIZE], u8) {
if std_deviation <= 0.0 {
let mut kernel = [0.0; MAX_KERNEL_SIZE];
kernel[0] = 1.0;
return (0, kernel, 1);
}
let variance = std_deviation * std_deviation;
let mut n_decimations = 0;
let mut remaining_variance = variance;
while remaining_variance > 4.0 {
remaining_variance = (remaining_variance - 1.5) * 0.25;
n_decimations += 1;
}
let remaining_sigma = remaining_variance.sqrt();
let (kernel, kernel_size) = compute_gaussian_kernel(remaining_sigma);
(n_decimations, kernel, kernel_size)
}
pub fn compute_gaussian_kernel(std_deviation: f32) -> ([f32; MAX_KERNEL_SIZE], u8) {
let radius = (3.0 * std_deviation).ceil() as usize;
let kernel_size = (1 + radius * 2).min(MAX_KERNEL_SIZE) as u8;
let mut kernel = [0.0; MAX_KERNEL_SIZE];
let gaussian_denominator = 2.0 * std_deviation * std_deviation;
let mut sum = 0.0;
let kernel_center = (kernel_size / 2) as f32;
for (i, weight) in kernel.iter_mut().enumerate().take(usize::from(kernel_size)) {
let x = (i as f32) - kernel_center;
*weight = E.powf(-x * x / gaussian_denominator);
sum += *weight;
}
let scale = 1.0 / sum;
for weight in kernel.iter_mut().take(usize::from(kernel_size)) {
*weight *= scale;
}
(kernel, kernel_size)
}
#[derive(Debug, Default)]
pub struct DecimationSizer {
width: u16,
height: u16,
dim_stack: Vec<(u16, u16)>,
}
impl DecimationSizer {
#[inline]
pub fn new(width: u16, height: u16) -> Self {
Self {
width,
height,
dim_stack: Vec::new(),
}
}
#[inline]
pub fn reset(&mut self, width: u16, height: u16) {
self.width = width;
self.height = height;
self.dim_stack.clear();
}
#[inline]
pub fn current(&self) -> (u16, u16) {
(self.width, self.height)
}
#[inline]
pub fn downscale(&mut self) -> (u16, u16) {
self.dim_stack.push((self.width, self.height));
self.width = self.width.div_ceil(2);
self.height = self.height.div_ceil(2);
(self.width, self.height)
}
#[inline]
pub fn upscale(&mut self) -> (u16, u16) {
let (target_w, target_h) = self.dim_stack.pop().unwrap();
self.width = (self.width * 2).min(target_w);
self.height = (self.height * 2).min(target_h);
(self.width, self.height)
}
}
#[cfg(test)]
mod tests {
use crate::filter::gaussian_blur::{
DecimationSizer, compute_gaussian_kernel, plan_decimated_blur,
};
#[test]
fn test_gaussian_kernel_small_sigma() {
let (kernel, size) = compute_gaussian_kernel(1.0);
assert_eq!(size, 7);
for i in 0..size / 2 {
assert!((kernel[usize::from(i)] - kernel[usize::from(size - 1 - i)]).abs() < 1e-6);
}
let sum: f32 = kernel.iter().take(usize::from(size)).sum();
assert!((sum - 1.0).abs() < 1e-6);
let center_idx = size / 2;
for i in 0..size {
if i != center_idx {
assert!(kernel[usize::from(center_idx)] >= kernel[usize::from(i)]);
}
}
}
#[test]
fn test_gaussian_kernel_very_small_sigma() {
let (kernel, size) = compute_gaussian_kernel(0.1);
assert_eq!(size, 3);
let sum: f32 = kernel.iter().take(usize::from(size)).sum();
assert!((sum - 1.0).abs() < 1e-6);
assert!(kernel[1] > 0.9); }
#[test]
fn test_gaussian_kernel_fractional_sigma() {
let (kernel, size) = compute_gaussian_kernel(0.5);
assert_eq!(size, 5);
let sum: f32 = kernel.iter().take(usize::from(size)).sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_plan_no_decimation() {
let (n_decimations, _kernel, _size) = plan_decimated_blur(1.0);
assert_eq!(n_decimations, 0);
}
#[test]
fn test_plan_with_decimation() {
let (n_decimations, _kernel, _size) = plan_decimated_blur(5.0);
assert_eq!(n_decimations, 2);
}
#[test]
fn test_plan_decimation_boundary() {
let (n_decimations, _kernel, _size) = plan_decimated_blur(2.0);
assert_eq!(n_decimations, 0);
}
#[test]
fn test_plan_negative_sigma() {
let (n_decimations, kernel, size) = plan_decimated_blur(-1.0);
assert_eq!(n_decimations, 0);
assert_eq!(size, 1);
assert!((kernel[0] - 1.0).abs() < 1e-6);
}
#[test]
fn test_decimation_sizer_even() {
let mut sizer = DecimationSizer::new(8, 8);
assert_eq!(sizer.current(), (8, 8));
assert_eq!(sizer.downscale(), (4, 4));
assert_eq!(sizer.downscale(), (2, 2));
assert_eq!(sizer.upscale(), (4, 4));
assert_eq!(sizer.upscale(), (8, 8));
}
#[test]
fn test_decimation_sizer_odd() {
let mut sizer = DecimationSizer::new(5, 7);
assert_eq!(sizer.downscale(), (3, 4));
assert_eq!(sizer.downscale(), (2, 2));
assert_eq!(sizer.upscale(), (3, 4));
assert_eq!(sizer.upscale(), (5, 7));
}
#[test]
fn test_decimation_sizer_single_level() {
let mut sizer = DecimationSizer::new(100, 50);
assert_eq!(sizer.downscale(), (50, 25));
assert_eq!(sizer.upscale(), (100, 50));
}
}