use crate::color::{AlphaColor, Srgb};
use crate::kurbo::{Affine, Rect};
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 bounds_expansion(&self, transform: &Affine) -> Rect {
let [a, b, c, d, _e, _f] = transform.as_coeffs();
let linear_only = Affine::new([a, b, c, d, 0.0, 0.0]);
self.graph.bounds_expansion(&linear_only)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FilterGraph {
pub primitives: SmallVec<[FilterPrimitive; 1]>,
pub output: FilterId,
next_id: u16,
expansion_rect: 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,
expansion_rect: Rect::ZERO,
}
}
pub fn add(&mut self, primitive: FilterPrimitive, _inputs: Option<FilterInputs>) -> FilterId {
let id = FilterId(self.next_id);
self.next_id += 1;
let primitive_rect = primitive.expansion_rect();
self.expansion_rect = self.expansion_rect.union(primitive_rect);
self.primitives.push(primitive);
id
}
pub fn set_output(&mut self, output: FilterId) {
self.output = output;
}
pub fn bounds_expansion(&self, transform: &Affine) -> Rect {
transform.transform_rect_bbox(self.expansion_rect)
}
}
#[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,
},
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 expansion_rect(&self) -> Rect {
match self {
Self::GaussianBlur { std_deviation, .. } => {
let radius = (*std_deviation * 3.0) as f64;
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,
..
} => {
let blur_radius = (*std_deviation * 3.0) as f64;
let dx = *dx as f64;
let dy = *dy as f64;
Rect::new(
-(blur_radius + (-dx).max(0.0)),
-(blur_radius + (-dy).max(0.0)),
blur_radius + dx.max(0.0),
blur_radius + dy.max(0.0),
)
}
_ => Rect::ZERO,
}
}
}
#[cfg(test)]
mod offset_expansion_tests {
use super::FilterPrimitive;
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.expansion_rect(),
Rect::new(0.0, -3.0, 2.5, 0.0),
"Offset expansion should be asymmetric and include the shift vector"
);
}
}
#[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,
}
}
}