use kurbo::{Affine, Rect, Vec2};
use peniko::color::{AlphaColor, Srgb};
use smallvec::SmallVec;
use self::{
blur::GaussianBlurFilter,
color_transformation::ColorMatrix,
component_transfer::ComponentTransferFilter,
composite::CompositeOperator,
convolution::ConvolutionKernel,
displacement::DisplacementMapFilter,
lighting::{DiffuseLightingFilter, SpecularLightingFilter},
morphology::MorphologyFilter,
shadow::DropShadow,
turbulence::TurbulenceFilter,
};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Filter {
primitives: SmallVec<[FilterGraphNode; 1]>,
output: FilterId,
expansion_rect: Rect,
}
impl Default for Filter {
fn default() -> Self {
Self::empty()
}
}
impl Filter {
pub fn empty() -> Self {
Self {
primitives: SmallVec::new(),
output: FilterId(0),
expansion_rect: Rect::ZERO,
}
}
pub fn single(primitive: FilterEffect) -> Self {
let mut graph = Self::empty();
let filter_id = graph.add(primitive, FilterInputs::NONE);
graph.set_output(filter_id);
graph
}
pub fn linear_list(primitives: impl Iterator<Item = FilterEffect>) -> Self {
let mut graph = Self::empty();
let mut last_id = None;
for primitive in primitives {
let inputs = FilterInputs {
primary: last_id.map(FilterInput::Result),
secondary: None,
};
let filter_id = graph.add(primitive, inputs);
graph.set_output(filter_id);
last_id = Some(filter_id);
}
graph
}
pub fn add(&mut self, effect: FilterEffect, inputs: FilterInputs) -> FilterId {
let id = FilterId(self.primitives.len() as u16);
let primitive_rect = effect.expansion_rect();
self.expansion_rect = self.expansion_rect.union(primitive_rect);
self.primitives.push(FilterGraphNode { effect, inputs });
id
}
pub fn nodes(&self) -> &[FilterGraphNode] {
&self.primitives
}
pub fn output(&self) -> FilterId {
self.output
}
fn set_output(&mut self, output: FilterId) {
self.output = output;
}
pub fn linear_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.bounds_expansion(&linear_only)
}
pub fn bounds_expansion(&self, transform: &Affine) -> Rect {
transform.transform_rect_bbox(self.expansion_rect)
}
pub fn expansion_rect(&self) -> Rect {
self.expansion_rect
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FilterGraphNode {
pub effect: FilterEffect,
pub inputs: FilterInputs,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FilterId(pub u16);
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FilterInputs {
pub primary: Option<FilterInput>,
pub secondary: Option<FilterInput>,
}
impl FilterInputs {
pub const NONE: Self = Self {
primary: None,
secondary: None,
};
}
impl FilterInputs {
pub fn single(input: FilterInput) -> Self {
Self {
primary: Some(input),
secondary: None,
}
}
pub fn dual(input1: FilterInput, input2: FilterInput) -> Self {
Self {
primary: Some(input1),
secondary: Some(input2),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FilterInput {
Source(FilterSource),
Result(FilterId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FilterSource {
SourceGraphic,
SourceAlpha,
BackgroundImage,
BackgroundAlpha,
FillPaint,
StrokePaint,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EdgeMode {
Duplicate,
Wrap,
Mirror,
#[default]
None,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FilterEffect {
Flood(AlphaColor<Srgb>),
GaussianBlur(GaussianBlurFilter),
DropShadow(DropShadow),
ColorMatrix(ColorMatrix),
Offset(Vec2),
Composite(CompositeOperator),
Blend(BlendMode),
Morphology(MorphologyFilter),
ConvolveMatrix(ConvolutionKernel),
Turbulence(TurbulenceFilter),
DisplacementMap(DisplacementMapFilter),
ComponentTransfer(ComponentTransferFilter),
Image(ExternalImageSource),
Tile,
DiffuseLighting(DiffuseLightingFilter),
SpecularLighting(SpecularLightingFilter),
}
#[cfg(not(target_arch = "wasm32"))]
const _: [u8; 128] = [0; std::mem::size_of::<FilterEffect>()];
#[cfg(target_arch = "wasm32")]
const _: [u8; 88] = [0; std::mem::size_of::<FilterEffect>()];
impl FilterEffect {
pub fn blur(radius: f32) -> Self {
Self::GaussianBlur(GaussianBlurFilter {
std_deviation: radius,
edge_mode: EdgeMode::None,
})
}
pub fn drop_shadow(dx: f32, dy: f32, std_deviation: f32, color: AlphaColor<Srgb>) -> Self {
Self::DropShadow(DropShadow {
dx,
dy,
std_deviation,
color,
edge_mode: EdgeMode::None,
})
}
pub fn opacity(amount: f32) -> Self {
Self::ComponentTransfer(ComponentTransferFilter::opacity(amount))
}
pub fn invert(amount: f32) -> Self {
Self::ComponentTransfer(ComponentTransferFilter::invert(amount))
}
pub fn brightness(amount: f32) -> Self {
Self::ComponentTransfer(ComponentTransferFilter::brightness(amount))
}
pub fn contrast(amount: f32) -> Self {
Self::ComponentTransfer(ComponentTransferFilter::contrast(amount))
}
pub fn hue_rotate(angle_radians: f32) -> Self {
Self::ColorMatrix(ColorMatrix::hue_rotate(angle_radians))
}
pub fn saturate(amount: f32) -> Self {
Self::ColorMatrix(ColorMatrix::saturate(amount))
}
pub fn sepia(amount: f32) -> Self {
Self::ColorMatrix(ColorMatrix::sepia(amount))
}
pub fn grayscale(amount: f32) -> Self {
Self::ColorMatrix(ColorMatrix::grayscale(amount))
}
pub fn expansion_rect(&self) -> Rect {
match self {
Self::GaussianBlur(blur) => {
let radius = (blur.std_deviation * 3.0) as f64;
Rect::new(-radius, -radius, radius, radius)
}
Self::Offset(offset) => {
let dx = offset.x;
let dy = offset.y;
Rect::new(dx.min(0.0), dy.min(0.0), dx.max(0.0), dy.max(0.0))
}
Self::DropShadow(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::FilterEffect;
use kurbo::{Rect, Vec2};
#[test]
fn offset_expands_in_direction_of_shift() {
let p = FilterEffect::Offset(Vec2 { x: 2.5, y: -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"
);
}
}
pub type BlendMode = peniko::Mix;
pub mod composite {
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CompositeOperator {
Over,
In,
Out,
Atop,
Xor,
Arithmetic(ArithmeticCompositeOperator),
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ArithmeticCompositeOperator {
pub k1: f32,
pub k2: f32,
pub k3: f32,
pub k4: f32,
}
}
mod blur {
use crate::filters::EdgeMode;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GaussianBlurFilter {
pub std_deviation: f32,
pub edge_mode: EdgeMode,
}
}
pub mod shadow {
use super::EdgeMode;
use peniko::color::{AlphaColor, Srgb};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DropShadow {
pub dx: f32,
pub dy: f32,
pub std_deviation: f32,
pub color: AlphaColor<Srgb>,
pub edge_mode: EdgeMode,
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExternalImageSource {
pub image_id: u32,
pub transform: Option<[f32; 6]>,
}
pub mod morphology {
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MorphologyFilter {
pub operator: MorphologyOperator,
pub radius: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MorphologyOperator {
Erode,
Dilate,
}
}
pub mod turbulence {
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TurbulenceFilter {
pub base_frequency: f32,
pub num_octaves: u32,
pub seed: u32,
pub turbulence_type: TurbulenceType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TurbulenceType {
FractalNoise,
Turbulence,
}
}
pub mod displacement {
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DisplacementMapFilter {
pub scale: f32,
pub x_channel: ColorChannel,
pub y_channel: ColorChannel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ColorChannel {
Red,
Green,
Blue,
Alpha,
}
}
pub mod component_transfer {
use smallvec::SmallVec;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ComponentTransferFilter {
pub red_function: TransferFunction,
pub green_function: TransferFunction,
pub blue_function: TransferFunction,
pub alpha_function: TransferFunction,
}
impl ComponentTransferFilter {
pub fn opacity(amount: f32) -> Self {
let func = TransferFunction::Table(SmallVec::from([0.0, amount]));
Self {
red_function: TransferFunction::Identity,
green_function: TransferFunction::Identity,
blue_function: TransferFunction::Identity,
alpha_function: func,
}
}
pub fn invert(amount: f32) -> Self {
let func = TransferFunction::Table(SmallVec::from([amount, 1.0 - amount]));
Self {
red_function: func.clone(),
green_function: func.clone(),
blue_function: func.clone(),
alpha_function: TransferFunction::Identity,
}
}
pub fn brightness(amount: f32) -> Self {
let func = TransferFunction::Linear(LinearTransferFunction {
slope: amount,
intercept: 0.0,
});
Self {
red_function: func.clone(),
green_function: func.clone(),
blue_function: func.clone(),
alpha_function: TransferFunction::Identity,
}
}
pub fn contrast(amount: f32) -> Self {
let func = TransferFunction::Linear(LinearTransferFunction {
slope: amount,
intercept: -(0.5 * amount) + 0.5,
});
Self {
red_function: func.clone(),
green_function: func.clone(),
blue_function: func.clone(),
alpha_function: TransferFunction::Identity,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TransferFunction {
Identity,
Table(SmallVec<[f32; 2]>),
Discrete(Vec<f32>),
Linear(LinearTransferFunction),
Gamma(GammaTransferFunction),
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LinearTransferFunction {
pub slope: f32,
pub intercept: f32,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GammaTransferFunction {
pub amplitude: f32,
pub exponent: f32,
pub offset: f32,
}
}
pub mod color_transformation {
const LUMA_R: f32 = 0.213;
const LUMA_G: f32 = 0.715;
const LUMA_B: f32 = 0.072;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ColorMatrix(pub [f32; 20]);
impl ColorMatrix {
pub fn hue_rotate(angle_radians: f32) -> Self {
let sin = angle_radians.sin();
let cos = angle_radians.cos();
Self([
LUMA_R + cos * (1.0 - LUMA_R) - sin * LUMA_R,
LUMA_G - cos * LUMA_G - sin * LUMA_G,
LUMA_B - cos * LUMA_B + sin * (1.0 - LUMA_B),
0.0,
0.0,
LUMA_R - cos * LUMA_R + sin * 0.143,
LUMA_G + cos * (1.0 - LUMA_G) + sin * 0.140,
LUMA_B - cos * LUMA_B - sin * 0.283,
0.0,
0.0,
LUMA_R - cos * LUMA_R - sin * (1.0 - LUMA_R),
LUMA_G - cos * LUMA_G + sin * LUMA_G,
LUMA_B + cos * (1.0 - LUMA_B) + sin * LUMA_B,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
])
}
pub fn saturate(amount: f32) -> Self {
Self([
LUMA_R + amount * (1.0 - LUMA_R),
LUMA_G - amount * LUMA_G,
LUMA_B - amount * LUMA_B,
0.0,
0.0,
LUMA_R - amount * LUMA_R,
LUMA_G + amount * (1.0 - LUMA_G),
LUMA_B - amount * LUMA_B,
0.0,
0.0,
LUMA_R - amount * LUMA_R,
LUMA_G - amount * LUMA_G,
LUMA_B + amount * (1.0 - LUMA_B),
0.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
])
}
pub fn sepia(amount: f32) -> Self {
Self([
(0.393 + 0.607 * (1.0 - amount)),
(0.769 - 0.769 * (1.0 - amount)),
(0.189 - 0.189 * (1.0 - amount)),
0.0,
0.0,
(0.349 - 0.349 * (1.0 - amount)),
(0.686 + 0.314 * (1.0 - amount)),
(0.168 - 0.168 * (1.0 - amount)),
0.0,
0.0,
(0.272 - 0.272 * (1.0 - amount)),
(0.534 - 0.534 * (1.0 - amount)),
(0.131 + 0.869 * (1.0 - amount)),
0.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
])
}
pub fn grayscale(amount: f32) -> Self {
Self([
(0.2126 + 0.7874 * (1.0 - amount)),
(0.7152 - 0.7152 * (1.0 - amount)),
(0.0722 - 0.0722 * (1.0 - amount)),
0.0,
0.0,
(0.2126 - 0.2126 * (1.0 - amount)),
(0.7152 + 0.2848 * (1.0 - amount)),
(0.0722 - 0.0722 * (1.0 - amount)),
0.0,
0.0,
(0.2126 - 0.2126 * (1.0 - amount)),
(0.7152 - 0.7152 * (1.0 - amount)),
(0.0722 + 0.9278 * (1.0 - amount)),
0.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
])
}
pub const IDENTITY: Self = Self([
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: Self = Self([
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 mod lighting {
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DiffuseLightingFilter {
pub surface_scale: f32,
pub diffuse_constant: f32,
pub kernel_unit_length: f32,
pub light_source: LightSource,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SpecularLightingFilter {
pub surface_scale: f32,
pub specular_constant: f32,
pub specular_exponent: f32,
pub kernel_unit_length: f32,
pub light_source: LightSource,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LightSource {
Distant(DistantLightSource),
Point(PointLightSource),
Spot(SpotLightSource),
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DistantLightSource {
pub azimuth: f32,
pub elevation: f32,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PointLightSource {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SpotLightSource {
pub x: f32,
pub y: f32,
pub z: f32,
pub points_at_x: f32,
pub points_at_y: f32,
pub points_at_z: f32,
pub specular_exponent: f32,
pub limiting_cone_angle: Option<f32>,
}
}
pub mod convolution {
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ConvolutionKernel {
pub size: u32,
pub values: Vec<f32>,
pub divisor: f32,
pub bias: f32,
pub preserve_alpha: bool,
}
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,
}
}
}