use crate::color::{AlphaColor, Srgb};
use crate::kurbo::{Affine, Rect, Vec2};
use alloc::sync::Arc;
use alloc::vec::Vec;
use smallvec::SmallVec;
#[derive(Debug, Clone, PartialEq)]
pub struct Filter {
pub graph: Arc<FilterGraph>,
}
impl Filter {
pub fn from_function(function: FilterFunction) -> Self {
let primitive = match function {
FilterFunction::Blur { radius } => FilterPrimitive::GaussianBlur {
std_deviation: radius,
edge_mode: EdgeMode::default(),
},
_ => unimplemented!("Filter function {:?} not supported", function),
};
Self::from_primitive(primitive)
}
pub fn from_primitive(primitive: FilterPrimitive) -> Self {
let mut graph = FilterGraph::new();
let filter_id = graph.add(primitive, None);
graph.set_output(filter_id);
Self {
graph: Arc::new(graph),
}
}
pub fn filter_expansion(&self, transform: &Affine) -> Rect {
let [a, b, c, d, ..] = transform.as_coeffs();
let linear_only = Affine::new([a, b, c, d, 0.0, 0.0]);
self.graph.filter_expansion(&linear_only)
}
pub fn source_expansion(&self, transform: &Affine) -> Rect {
let [a, b, c, d, ..] = transform.as_coeffs();
let linear_only = Affine::new([a, b, c, d, 0.0, 0.0]);
self.graph.source_expansion(&linear_only)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FilterGraph {
pub primitives: SmallVec<[FilterPrimitive; 1]>,
pub output: FilterId,
next_id: u16,
filter_expansion: Rect,
source_expansion: Rect,
}
impl Default for FilterGraph {
fn default() -> Self {
Self::new()
}
}
impl FilterGraph {
pub fn new() -> Self {
Self {
primitives: SmallVec::new(),
output: FilterId(0),
next_id: 0,
filter_expansion: Rect::ZERO,
source_expansion: Rect::ZERO,
}
}
pub fn add(&mut self, primitive: FilterPrimitive, _inputs: Option<FilterInputs>) -> FilterId {
let id = FilterId(self.next_id);
self.next_id += 1;
self.filter_expansion = self.filter_expansion.union(primitive.filter_expansion());
self.source_expansion = self.source_expansion.union(primitive.source_expansion());
self.primitives.push(primitive);
id
}
pub fn set_output(&mut self, output: FilterId) {
self.output = output;
}
pub fn filter_expansion(&self, transform: &Affine) -> Rect {
transform.transform_rect_bbox(self.filter_expansion)
}
pub fn source_expansion(&self, transform: &Affine) -> Rect {
transform.transform_rect_bbox(self.source_expansion)
}
}
#[derive(Debug, Clone)]
pub enum FilterEffect {
Function(FilterFunction),
Primitive(FilterPrimitive),
}
#[derive(Debug, Clone)]
pub enum FilterFunction {
Blur {
radius: f32,
},
Brightness {
amount: f32,
},
Contrast {
amount: f32,
},
Grayscale {
amount: f32,
},
HueRotate {
angle: f32,
},
Invert {
amount: f32,
},
Opacity {
amount: f32,
},
Saturate {
amount: f32,
},
Sepia {
amount: f32,
},
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum EdgeMode {
Duplicate,
Wrap,
Mirror,
#[default]
None,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FilterPrimitive {
Flood {
color: AlphaColor<Srgb>,
},
GaussianBlur {
std_deviation: f32,
edge_mode: EdgeMode,
},
DropShadow {
dx: f32,
dy: f32,
std_deviation: f32,
color: AlphaColor<Srgb>,
edge_mode: EdgeMode,
},
DropShadowOnly {
dx: f32,
dy: f32,
std_deviation: f32,
color: AlphaColor<Srgb>,
edge_mode: EdgeMode,
},
ColorMatrix {
matrix: [f32; 20],
},
Offset {
dx: f32,
dy: f32,
},
Composite {
operator: CompositeOperator,
},
Blend {
mode: BlendMode,
},
Morphology {
operator: MorphologyOperator,
radius: f32,
},
ConvolveMatrix {
kernel: ConvolutionKernel,
},
Turbulence {
base_frequency: f32,
num_octaves: u32,
seed: u32,
turbulence_type: TurbulenceType,
},
DisplacementMap {
scale: f32,
x_channel: ColorChannel,
y_channel: ColorChannel,
},
ComponentTransfer {
red_function: Option<TransferFunction>,
green_function: Option<TransferFunction>,
blue_function: Option<TransferFunction>,
alpha_function: Option<TransferFunction>,
},
Image {
image_id: u32,
transform: Option<[f32; 6]>,
},
Tile,
DiffuseLighting {
surface_scale: f32,
diffuse_constant: f32,
kernel_unit_length: f32,
light_source: LightSource,
},
SpecularLighting {
surface_scale: f32,
specular_constant: f32,
specular_exponent: f32,
kernel_unit_length: f32,
light_source: LightSource,
},
}
impl FilterPrimitive {
pub fn filter_expansion(&self) -> Rect {
match self {
Self::GaussianBlur { std_deviation, .. } => {
let radius = blur_radius(*std_deviation);
Rect::new(-radius, -radius, radius, radius)
}
Self::Offset { dx, dy } => {
let dx = *dx as f64;
let dy = *dy as f64;
Rect::new(dx.min(0.0), dy.min(0.0), dx.max(0.0), dy.max(0.0))
}
Self::DropShadow {
std_deviation,
dx,
dy,
..
}
| Self::DropShadowOnly {
std_deviation,
dx,
dy,
..
} => {
let blur_radius = blur_radius(*std_deviation);
let dx = f64::from(*dx);
let dy = f64::from(*dy);
Rect::new(
(dx - blur_radius).min(0.0),
(dy - blur_radius).min(0.0),
(dx + blur_radius).max(0.0),
(dy + blur_radius).max(0.0),
)
}
_ => Rect::ZERO,
}
}
pub fn source_expansion(&self) -> Rect {
match self {
Self::Offset { dx, dy } => {
self.filter_expansion() - Vec2::new(f64::from(*dx), f64::from(*dy))
}
Self::DropShadow {
std_deviation,
dx,
dy,
..
}
| Self::DropShadowOnly {
std_deviation,
dx,
dy,
..
} => {
let blur_radius = blur_radius(*std_deviation);
let dx = -f64::from(*dx);
let dy = -f64::from(*dy);
Rect::new(
(dx - blur_radius).min(0.0),
(dy - blur_radius).min(0.0),
(dx + blur_radius).max(0.0),
(dy + blur_radius).max(0.0),
)
}
_ => self.filter_expansion(),
}
}
}
fn blur_radius(std_deviation: f32) -> f64 {
f64::from(std_deviation * 3.0)
}
#[cfg(test)]
mod expansion_tests {
use super::FilterPrimitive;
use crate::color::palette::css::RED;
use crate::filter_effects::EdgeMode;
use crate::kurbo::Rect;
#[test]
fn offset_expands_in_direction_of_shift() {
let p = FilterPrimitive::Offset { dx: 2.5, dy: -3.0 };
assert_eq!(
p.filter_expansion(),
Rect::new(0.0, -3.0, 2.5, 0.0),
"Offset expansion should be asymmetric and include the shift vector"
);
}
#[test]
fn drop_shadow_expansion_combines_blur_and_offset_tightly() {
let p = FilterPrimitive::DropShadow {
dx: 20.0,
dy: -10.0,
std_deviation: 8.0,
color: RED,
edge_mode: EdgeMode::None,
};
assert_eq!(p.filter_expansion(), Rect::new(-4.0, -34.0, 44.0, 14.0));
assert_eq!(p.source_expansion(), Rect::new(-44.0, -14.0, 4.0, 34.0));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FilterId(pub u16);
#[derive(Debug, Clone, PartialEq)]
pub struct FilterInputs {
pub primary: FilterInput,
pub secondary: Option<FilterInput>,
}
impl FilterInputs {
pub fn single(input: FilterInput) -> Self {
Self {
primary: input,
secondary: None,
}
}
pub fn dual(input1: FilterInput, input2: FilterInput) -> Self {
Self {
primary: input1,
secondary: Some(input2),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FilterInput {
Source(FilterSource),
Result(FilterId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterSource {
SourceGraphic,
SourceAlpha,
BackgroundImage,
BackgroundAlpha,
FillPaint,
StrokePaint,
}
#[derive(Debug, Clone)]
pub enum CompoundFilter {
InnerShadow {
dx: f32,
dy: f32,
blur: f32,
color: AlphaColor<Srgb>,
},
Glow {
blur: f32,
color: AlphaColor<Srgb>,
},
Bevel {
angle: f32,
distance: f32,
highlight: AlphaColor<Srgb>,
shadow: AlphaColor<Srgb>,
},
Emboss {
angle: f32,
depth: f32,
amount: f32,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CompositeOperator {
Over,
In,
Out,
Atop,
Xor,
Arithmetic {
k1: f32,
k2: f32,
k3: f32,
k4: f32,
},
}
pub type BlendMode = peniko::Mix;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MorphologyOperator {
Erode,
Dilate,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConvolutionKernel {
pub size: u32,
pub values: Vec<f32>,
pub divisor: f32,
pub bias: f32,
pub preserve_alpha: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TurbulenceType {
FractalNoise,
Turbulence,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorChannel {
Red,
Green,
Blue,
Alpha,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TransferFunction {
Identity,
Table {
values: Vec<f32>,
},
Discrete {
values: Vec<f32>,
},
Linear {
slope: f32,
intercept: f32,
},
Gamma {
amplitude: f32,
exponent: f32,
offset: f32,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum LightSource {
Distant {
azimuth: f32,
elevation: f32,
},
Point {
x: f32,
y: f32,
z: f32,
},
Spot {
x: f32,
y: f32,
z: f32,
points_at_x: f32,
points_at_y: f32,
points_at_z: f32,
specular_exponent: f32,
limiting_cone_angle: Option<f32>,
},
}
pub mod matrices {
pub const IDENTITY: [f32; 20] = [
1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, ];
pub const ALPHA_TO_BLACK: [f32; 20] = [
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, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, ];
pub const GRAYSCALE: [f32; 20] = [
0.2126, 0.7152, 0.0722, 0.0, 0.0, 0.2126, 0.7152, 0.0722, 0.0, 0.0, 0.2126, 0.7152, 0.0722, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, ];
pub const SEPIA: [f32; 20] = [
0.393, 0.769, 0.189, 0.0, 0.0, 0.349, 0.686, 0.168, 0.0, 0.0, 0.272, 0.534, 0.131, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, ];
}
pub mod kernels {
use super::ConvolutionKernel;
use alloc::vec;
pub fn gaussian_3x3() -> ConvolutionKernel {
ConvolutionKernel {
size: 3,
values: vec![1.0, 2.0, 1.0, 2.0, 4.0, 2.0, 1.0, 2.0, 1.0],
divisor: 16.0,
bias: 0.0,
preserve_alpha: false,
}
}
pub fn sharpen_3x3() -> ConvolutionKernel {
ConvolutionKernel {
size: 3,
values: vec![0.0, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0],
divisor: 1.0,
bias: 0.0,
preserve_alpha: true,
}
}
pub fn edge_detect_3x3() -> ConvolutionKernel {
ConvolutionKernel {
size: 3,
values: vec![-1.0, -1.0, -1.0, -1.0, 8.0, -1.0, -1.0, -1.0, -1.0],
divisor: 1.0,
bias: 0.0,
preserve_alpha: true,
}
}
pub fn emboss_3x3() -> ConvolutionKernel {
ConvolutionKernel {
size: 3,
values: vec![-2.0, -1.0, 0.0, -1.0, 1.0, 1.0, 0.0, 1.0, 2.0],
divisor: 1.0,
bias: 0.5,
preserve_alpha: true,
}
}
}