use serde::{Deserialize, Serialize};
use smallvec::{SmallVec, smallvec};
use crate::color::{Color, ColorFilterMatrix};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FlattenMatrix {
pub width: usize,
pub height: usize,
pub matrix: Vec<f32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum FilterOp {
Color(ColorFilterMatrix),
Convolution(FlattenMatrix),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilterLayer {
pub ops: SmallVec<[FilterOp; 1]>,
pub composite: FilterComposite,
#[serde(default)]
pub offset: [f32; 2],
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Default)]
pub enum FilterComposite {
#[default]
Replace,
ExcludeSource,
}
#[derive(Default, Clone, Debug)]
pub struct Filter {
pub layers: SmallVec<[FilterLayer; 1]>,
}
impl Filter {
pub fn new() -> Self { Self { layers: SmallVec::new() } }
pub fn then(mut self, filter: Self) -> Self {
if filter.is_empty() {
return self;
}
if self.is_empty() {
return filter;
}
let can_merge = {
let last = self.layers.last().unwrap();
let first = filter.layers.first().unwrap();
last.composite == FilterComposite::Replace
&& last.offset == [0., 0.]
&& first.composite == FilterComposite::Replace
&& first.offset == [0., 0.]
};
if can_merge {
let mut iter = filter.layers.into_iter();
let first = iter.next().unwrap();
self
.layers
.last_mut()
.unwrap()
.ops
.extend(first.ops);
self.layers.extend(iter);
} else {
self.layers.extend(filter.layers);
}
self
}
pub fn composite_op(mut self, composite: FilterComposite) -> Self {
if let Some(layer) = self.layers.last_mut() {
layer.composite = composite;
}
self
}
pub fn offset(mut self, dx: f32, dy: f32) -> Self {
if let Some(layer) = self.layers.last_mut() {
layer.offset = [dx, dy];
}
self
}
#[rustfmt::skip]
pub fn grayscale(amount: f32) -> Self {
let t = amount.clamp(0.0, 1.0);
let (r, g, b) = (0.2126, 0.7152, 0.0722);
Self::from_color_matrix(ColorFilterMatrix {
matrix: [
1.0 - t + t * r, t * g, t * b, 0.0, t * r, 1.0 - t + t * g, t * b, 0.0, t * r, t * g, 1.0 - t + t * b, 0.0, 0.0, 0.0, 0.0, 1.0, ],
base_color: None,
})
}
#[rustfmt::skip]
pub fn sepia(amount: f32) -> Self {
let t = amount.clamp(0.0, 1.0);
let (r0, r1, r2) = (0.393, 0.769, 0.189);
let (g0, g1, g2) = (0.349, 0.686, 0.168);
let (b0, b1, b2) = (0.272, 0.534, 0.131);
Self::from_color_matrix(ColorFilterMatrix {
matrix: [
1.0 - t + t * r0, t * r1, t * r2, 0.0, t * g0, 1.0 - t + t * g1, t * g2, 0.0, t * b0, t * b1, 1.0 - t + t * b2, 0.0, 0.0, 0.0, 0.0, 1.0, ],
base_color: None,
})
}
#[rustfmt::skip]
pub fn saturate(level: f32) -> Self {
Self::from_color_matrix(ColorFilterMatrix {
matrix: [
0.213 + 0.787 * level, 0.715 - 0.715 * level, 0.072 - 0.072 * level, 0., 0.213 - 0.213 * level, 0.715 + 0.285 * level, 0.072 - 0.072 * level, 0., 0.213 - 0.213 * level, 0.715 - 0.715 * level, 0.072 + 0.928 * level, 0., 0., 0., 0., 1., ],
base_color: None,
})
}
#[rustfmt::skip]
pub fn opacity(amount: f32) -> Self {
let v = amount.clamp(0.0, 1.0);
Self::from_color_matrix(ColorFilterMatrix {
matrix: [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, v
],
base_color: None,
})
}
pub fn contrast(amount: f32) -> Self {
let c = amount.clamp(0.0, 1.0);
let color_offset = 0.5 * (1.0 - c);
Self::from_color_matrix(ColorFilterMatrix {
matrix: [
c, 0.0, 0.0, 0.0, 0.0, c, 0.0, 0.0, 0.0, 0.0, c, 0.0, 0.0, 0.0, 0.0, 1.0, ],
base_color: Some(Color::from_f32_rgba(color_offset, color_offset, color_offset, 0.0)),
})
}
#[rustfmt::skip]
pub fn brightness(amount: f32) -> Self {
let t = (amount - 1.0).max(-1.0);
Self::from_color_matrix(ColorFilterMatrix {
matrix: [
1., 0.0, 0.0, 0.0,
0.0, 1., 0.0, 0.0,
0.0, 0.0, 1., 0.0,
0.0, 0.0, 0.0, 1.0,
],
base_color: Some(Color::from_f32_rgba(t, t, t, 0.0)),
})
}
#[rustfmt::skip]
pub fn hue_rotate(rad: f32) -> Self {
Self::from_color_matrix(ColorFilterMatrix {
matrix: [
0.213 + rad.cos() * 0.787 - rad.sin() * 0.213,
0.715 - rad.cos() * 0.715 - rad.sin() * 0.715,
0.072 - rad.cos() * 0.072 + rad.sin() * 0.928,
0.,
0.213 - rad.cos() * 0.213 + rad.sin() * 0.143,
0.715 + rad.cos() * 0.285 + rad.sin() * 0.14,
0.072 - rad.cos() * 0.072 - rad.sin() * 0.283,
0.,
0.213 - rad.cos() * 0.213 - rad.sin() * 0.787,
0.715 - rad.cos() * 0.715 + rad.sin() * 0.715,
0.072 + rad.cos() * 0.928 + rad.sin() * 0.072,
0.,
0., 0., 0.,1.,
],
base_color: None,
})
}
#[rustfmt::skip]
pub fn invert(amount: f32) -> Self {
let i = amount.clamp(0.0, 1.0);
Self::from_color_matrix(ColorFilterMatrix {
matrix: [
1. - 2. * i, 0.0, 0.0, 0.0,
0.0, 1. - 2. * i, 0.0, 0.0,
0.0, 0.0, 1. - 2. * i, 0.0,
0.0, 0.0, 0.0, 1.0,
],
base_color: Some(Color::from_f32_rgba(i, i, i, 0.)),
})
}
#[rustfmt::skip]
pub fn luminance_to_alpha() -> Self {
Self::from_color_matrix(ColorFilterMatrix {
matrix: [
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.2125, 0.7154, 0.0721, 0., ],
base_color: None,
})
}
pub fn blur(radius: f32) -> Self {
if radius <= 0.001 {
return Self::default();
}
let ops = blur_ops(radius);
Self {
layers: smallvec![FilterLayer {
ops,
composite: FilterComposite::default(),
offset: [0., 0.]
}],
}
}
pub fn color(matrix: ColorFilterMatrix) -> Self { Self::from_color_matrix(matrix) }
fn from_color_matrix(matrix: ColorFilterMatrix) -> Self {
Self {
layers: smallvec![FilterLayer {
ops: smallvec![FilterOp::Color(matrix)],
composite: FilterComposite::default(),
offset: [0., 0.],
}],
}
}
pub fn convolution(matrix: FlattenMatrix) -> Self {
Self {
layers: smallvec![FilterLayer {
ops: smallvec![FilterOp::Convolution(matrix)],
composite: FilterComposite::default(),
offset: [0., 0.],
}],
}
}
pub fn drop_shadow(offset: (f32, f32), blur_radius: f32, shadow_color: Color) -> Self {
let shadow_matrix = shadow_color_matrix(shadow_color);
let mut ops = smallvec![FilterOp::Color(shadow_matrix)];
if blur_radius > 0.001 {
ops.extend(blur_ops(blur_radius));
}
Self {
layers: smallvec![FilterLayer {
ops,
composite: FilterComposite::ExcludeSource,
offset: [offset.0, offset.1],
}],
}
}
pub fn is_empty(&self) -> bool { self.layers.is_empty() }
pub fn len(&self) -> usize { self.layers.len() }
pub(crate) fn into_layers(self) -> Vec<FilterLayer> { self.layers.into_vec() }
pub(crate) fn extract_color_and_convolution(self) -> (Option<ColorFilterMatrix>, Filter) {
let mut color_matrix: Option<ColorFilterMatrix> = None;
let mut layers = SmallVec::new();
let mut optimization_broken = false;
for mut layer in self.layers {
if !optimization_broken
&& layer.offset == [0., 0.]
&& layer.composite == FilterComposite::Replace
{
let mut new_ops = SmallVec::new();
for op in layer.ops {
if !optimization_broken {
match op {
FilterOp::Color(m) => {
match &mut color_matrix {
Some(existing) => *existing = existing.chains(&m),
None => color_matrix = Some(m),
}
continue; }
_ => {
optimization_broken = true;
new_ops.push(op);
}
}
} else {
new_ops.push(op);
}
}
if !new_ops.is_empty() {
layer.ops = new_ops;
layers.push(layer);
}
} else {
optimization_broken = true;
layers.push(layer);
}
}
(color_matrix, Filter { layers })
}
}
fn blur_ops(radius: f32) -> SmallVec<[FilterOp; 1]> {
let radius_usize = radius.ceil() as usize;
let radius_usize = radius_usize.min(30);
let kernel = gaussian_kernel(radius_usize, radius / 2.);
let kernel_len = kernel.len();
smallvec![
FilterOp::Convolution(FlattenMatrix { width: kernel_len, height: 1, matrix: kernel.clone() }),
FilterOp::Convolution(FlattenMatrix { width: 1, height: kernel_len, matrix: kernel })
]
}
fn gaussian_kernel(radius: usize, sigma: f32) -> Vec<f32> {
let size = 2 * radius + 1;
let mut kernel = Vec::with_capacity(size);
let mut sum = 0.0;
for i in 0..=radius {
let x = i as f32 - radius as f32;
let weight = (-x.powi(2) / (2.0 * sigma.powi(2))).exp();
sum += weight;
kernel.push(weight);
}
for i in 1..=radius {
let weight = kernel[radius - i];
sum += weight;
kernel.push(weight);
}
let reciprocal = 1.0 / sum;
kernel.iter_mut().for_each(|w| *w *= reciprocal);
kernel
}
#[rustfmt::skip]
fn shadow_color_matrix(color: Color) -> ColorFilterMatrix {
let [r, g, b, a] = color.into_f32_components();
ColorFilterMatrix {
matrix: [
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, a, ],
base_color: Some(Color::from_f32_rgba(r, g, b, 0.0)),
}
}