Skip to main content

vello_common/
filter_effects.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Filter effects API based on the W3C Filter Effects specification.
5//!
6//! This module provides a comprehensive filter system supporting both high-level
7//! CSS filter functions and low-level SVG filter primitives. The API is designed
8//! to follow the W3C Filter Effects Module Level 1 specification.
9//!
10//! See: <https://drafts.fxtf.org/filter-effects/>
11//!
12//! ## Implementation Status
13//!
14//! ### ✅ Implemented
15//!
16//! **Filter Functions:**
17//! - `Blur` - Gaussian blur effect
18//!
19//! **Filter Primitives (Single Use Only):**
20//! - `Flood` - Solid color fill
21//! - `GaussianBlur` - Gaussian blur filter
22//! - `DropShadow` - Drop shadow effect (compound primitive)
23//! - `Offset` - Translation/shift (single primitive)
24//!
25//! **Note:** Currently only single primitive filters are supported. Filter graphs with
26//! multiple connected primitives are not yet implemented.
27//!
28//! ### 🚧 Not Yet Implemented
29//!
30//! **Core Features:**
31//! - `FilterGraph` execution - Chaining multiple filter primitives together
32//! - `FilterInputs` - Connecting primitives to create complex effects
33//!
34//! **Filter Functions:**
35//! - `Brightness`, `Contrast`, `Grayscale`, `HueRotate`, `Invert`,
36//!   `Opacity`, `Saturate`, `Sepia`
37//!
38//! **Filter Primitives:**
39//! - `ColorMatrix` - Matrix-based color transformation
40//! - `Composite` - Porter-Duff compositing operations
41//! - `Blend` - Blend mode operations
42//! - `Morphology` - Dilate/erode operations
43//! - `ConvolveMatrix` - Custom convolution kernels
44//! - `Turbulence` - Perlin noise generation
45//! - `DisplacementMap` - Pixel displacement
46//! - `ComponentTransfer` - Per-channel transfer functions
47//! - `Image` - External image reference
48//! - `Tile` - Tiling operation
49//! - `DiffuseLighting`, `SpecularLighting` - Lighting effects
50
51use crate::color::{AlphaColor, Srgb};
52use crate::kurbo::{Affine, Rect};
53use alloc::sync::Arc;
54use alloc::vec::Vec;
55use smallvec::SmallVec;
56
57/// The main filter system.
58///
59/// A filter combines a graph of filter primitives with optional spatial bounds.
60/// If bounds are specified, the filter only applies within that region.
61#[derive(Debug, Clone, PartialEq)]
62pub struct Filter {
63    /// Filter graph defining the effect pipeline.
64    pub graph: Arc<FilterGraph>,
65    // TODO: Add bounds restricting where the filter applies.
66    // Optional bounds restricting where the filter applies.
67    // If `None`, the filter applies to the entire filtered element.
68    // pub bounds: Option<Rect>,
69}
70
71impl Filter {
72    /// Create a simple filter system from a filter function.
73    ///
74    /// Converts a high-level CSS-style filter function into a filter graph.
75    /// Use this for simple effects like blur, brightness, etc.
76    pub fn from_function(function: FilterFunction) -> Self {
77        // Convert function to primitive
78        let primitive = match function {
79            FilterFunction::Blur { radius } => FilterPrimitive::GaussianBlur {
80                std_deviation: radius,
81                edge_mode: EdgeMode::default(),
82            },
83            _ => unimplemented!("Filter function {:?} not supported", function),
84        };
85
86        Self::from_primitive(primitive)
87    }
88
89    /// Create a filter system from a filter primitive.
90    ///
91    /// Creates a simple filter graph with a single primitive.
92    /// Use this for direct access to low-level SVG filter operations.
93    pub fn from_primitive(primitive: FilterPrimitive) -> Self {
94        let mut graph = FilterGraph::new();
95        let filter_id = graph.add(primitive, None);
96        graph.set_output(filter_id);
97
98        Self {
99            graph: Arc::new(graph),
100        }
101    }
102
103    /// Calculate the bounds expansion for this filter in pixel/device space.
104    ///
105    /// Returns a `Rect` representing how many extra pixels are needed around the
106    /// filtered region to correctly compute the filter effect. For example, a blur
107    /// filter needs to sample beyond the original bounds to avoid edge artifacts.
108    ///
109    /// The expansion accounts for the transform (rotation, scale, and shear) to compute
110    /// the correct axis-aligned bounding box expansion in device space.
111    ///
112    /// The returned rect is centered at origin:
113    /// - x0: negative left expansion (in pixels)
114    /// - y0: negative top expansion (in pixels)
115    /// - x1: positive right expansion (in pixels)
116    /// - y1: positive bottom expansion (in pixels)
117    ///
118    /// # Arguments
119    /// * `transform` - The transform applied to this filter layer
120    pub fn bounds_expansion(&self, transform: &Affine) -> Rect {
121        let [a, b, c, d, _e, _f] = transform.as_coeffs();
122        let linear_only = Affine::new([a, b, c, d, 0.0, 0.0]);
123
124        self.graph.bounds_expansion(&linear_only)
125    }
126}
127
128/// A directed acyclic graph (DAG) of filter operations.
129///
130/// The graph represents a pipeline of filter primitives where outputs of some
131/// primitives can be used as inputs to others. Each primitive has a unique `FilterId`.
132#[derive(Debug, Clone, PartialEq)]
133pub struct FilterGraph {
134    /// All filter primitives in the graph, stored in insertion order.
135    pub primitives: SmallVec<[FilterPrimitive; 1]>,
136    /// The final output filter ID whose result is the output of this graph.
137    pub output: FilterId,
138    /// Next available filter ID (monotonically increasing counter).
139    next_id: u16,
140    /// Accumulated bounds expansion from all primitives in the graph, cached in user space.
141    /// This is the axis-aligned bounding box of the expansion region (centered at origin),
142    /// which can be transformed to device space when needed.
143    expansion_rect: Rect,
144}
145
146impl Default for FilterGraph {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152impl FilterGraph {
153    /// Create a new empty filter graph.
154    pub fn new() -> Self {
155        Self {
156            primitives: SmallVec::new(),
157            output: FilterId(0),
158            next_id: 0,
159            expansion_rect: Rect::ZERO,
160        }
161    }
162
163    /// Add a filter primitive with optional inputs.
164    ///
165    /// Returns a `FilterId` that can be referenced by other primitives.
166    /// Automatically updates the accumulated bounds expansion based on the primitive's requirements.
167    pub fn add(&mut self, primitive: FilterPrimitive, _inputs: Option<FilterInputs>) -> FilterId {
168        let id = FilterId(self.next_id);
169        self.next_id += 1;
170
171        // Update accumulated expansion by taking the union of rects
172        let primitive_rect = primitive.expansion_rect();
173        self.expansion_rect = self.expansion_rect.union(primitive_rect);
174
175        self.primitives.push(primitive);
176
177        id
178    }
179
180    /// Set the output filter for the graph.
181    pub fn set_output(&mut self, output: FilterId) {
182        self.output = output;
183    }
184
185    /// Get the accumulated bounds expansion for all primitives in this graph.
186    ///
187    /// This returns the expansion required by all primitives in the graph,
188    /// representing the padding needed to render all filter effects correctly.
189    ///
190    /// The expansion accounts for the transform (rotation, scale, and shear) to compute
191    /// the correct axis-aligned bounding box expansion in device space.
192    ///
193    /// # Arguments
194    /// * `transform` - The transform applied to this filter layer
195    pub fn bounds_expansion(&self, transform: &Affine) -> Rect {
196        // Transform the cached expansion rect to device space
197        // transform_rect_bbox computes the axis-aligned bounding box of the transformed rect
198        transform.transform_rect_bbox(self.expansion_rect)
199    }
200}
201
202/// All possible filter effects.
203///
204/// This enum allows choosing between high-level filter functions (simple CSS-style effects)
205/// and low-level filter primitives (complex SVG-style effects with full control).
206/// Use `FilterFunction` for common effects like blur, and `FilterPrimitive` for
207/// advanced composition and custom filter graphs.
208#[derive(Debug, Clone)]
209pub enum FilterEffect {
210    /// Simple, high-level filter functions.
211    Function(FilterFunction),
212    /// Low-level filter primitives (granular control).
213    Primitive(FilterPrimitive),
214}
215
216/// High-level filter functions for common effects (CSS filter functions).
217///
218/// These match the CSS Filter Effects specification and provide simple,
219/// commonly-used visual effects without needing to construct a filter graph.
220///
221/// See: <https://drafts.fxtf.org/filter-effects/#filter-functions>
222#[derive(Debug, Clone)]
223pub enum FilterFunction {
224    /// Gaussian blur effect.
225    ///
226    /// Applies a Gaussian blur to the input image. Larger radius values
227    /// produce more blur. The blur is applied equally in all directions.
228    ///
229    /// Note: Per the W3C Filter Effects specification, this `radius` parameter
230    /// represents the standard deviation (σ) of the Gaussian function, not the
231    /// effective blur range. The effective blur range is approximately 3× this value.
232    Blur {
233        /// Standard deviation of the Gaussian blur in pixels. Must be non-negative.
234        /// A value of 0 means no blur.
235        ///
236        /// Despite being called "radius" (to match CSS filter syntax), this is
237        /// actually the standard deviation. The visible blur effect extends
238        /// approximately 3 times this value in each direction.
239        radius: f32,
240    },
241    //
242    // ============================================================
243    // TODO: The following filter functions are not yet implemented
244    // ============================================================
245    //
246    /// Brightness adjustment.
247    ///
248    /// Adjusts the brightness of the input image using a linear multiplier.
249    Brightness {
250        /// Brightness amount: 0.0 = completely black, 1.0 = no change, >1.0 = brighter.
251        /// Must be non-negative.
252        amount: f32,
253    },
254    /// Contrast adjustment.
255    ///
256    /// Adjusts the contrast of the input image.
257    Contrast {
258        /// Contrast amount: 0.0 = uniform gray, 1.0 = no change, >1.0 = higher contrast.
259        /// Must be non-negative.
260        amount: f32,
261    },
262    /// Grayscale conversion.
263    ///
264    /// Converts the input to grayscale. Amount controls the strength of the conversion.
265    Grayscale {
266        /// Grayscale amount: 0.0 = original colors, 1.0 = full grayscale.
267        /// Values should be in range [0.0, 1.0].
268        amount: f32,
269    },
270    /// Hue rotation.
271    ///
272    /// Rotates the hue of all colors in the input image by the specified angle.
273    HueRotate {
274        /// Rotation angle in degrees. Can be negative.
275        /// 0° = no change, 180° = opposite hue, 360° = back to original.
276        angle: f32,
277    },
278    /// Color inversion.
279    ///
280    /// Inverts the colors of the input image.
281    Invert {
282        /// Inversion amount: 0.0 = original colors, 1.0 = fully inverted.
283        /// Values should be in range [0.0, 1.0].
284        amount: f32,
285    },
286    /// Opacity adjustment.
287    ///
288    /// Multiplies the alpha channel by the specified amount.
289    Opacity {
290        /// Opacity amount: 0.0 = fully transparent, 1.0 = no change.
291        /// Values should be in range [0.0, 1.0].
292        amount: f32,
293    },
294    /// Saturation adjustment.
295    ///
296    /// Adjusts the color saturation of the input image.
297    Saturate {
298        /// Saturation amount: 0.0 = completely desaturated (grayscale),
299        /// 1.0 = no change, >1.0 = oversaturated.
300        /// Must be non-negative.
301        amount: f32,
302    },
303    /// Sepia tone effect.
304    ///
305    /// Applies a sepia tone effect (vintage/old photo appearance).
306    Sepia {
307        /// Sepia amount: 0.0 = original colors, 1.0 = full sepia tone.
308        /// Values should be in range [0.0, 1.0].
309        amount: f32,
310    },
311}
312
313/// Edge mode for filter operations.
314///
315/// Determines how to extend the input image when filter operations require sampling
316/// beyond the original image boundaries. This is particularly important for blur and
317/// convolution operations near edges.
318///
319/// See: <https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-primitive-edgemode>
320#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
321pub enum EdgeMode {
322    /// Extend by duplicating edge pixels (clamp to edge).
323    ///
324    /// The input image is extended along each border by replicating the color values
325    /// at the given edge of the input image. This prevents dark halos around edges.
326    Duplicate,
327    /// Extend by wrapping to the opposite edge (repeat/tile).
328    ///
329    /// The input image is extended by taking color values from the opposite edge,
330    /// creating a tiling effect.
331    Wrap,
332    /// Extend by mirroring across the edge.
333    ///
334    /// The input image is extended by taking color values mirrored across the edge.
335    /// This creates seamless continuation at boundaries.
336    Mirror,
337    /// Extend with transparent black (zeros).
338    ///
339    /// The input image is extended with pixel values of zero for R, G, B and A.
340    /// This is the default and most common mode, creating natural fade-to-transparent edges.
341    #[default]
342    None,
343}
344
345/// Low-level filter primitives for granular control (SVG filter primitives).
346///
347/// These are the building blocks for complex filter effects, corresponding to SVG
348/// filter primitives. They can be combined in a `FilterGraph` to create sophisticated
349/// visual effects.
350///
351/// See: <https://drafts.fxtf.org/filter-effects/#FilterPrimitivesOverview>
352#[derive(Debug, Clone, PartialEq)]
353pub enum FilterPrimitive {
354    /// Generate a solid color fill.
355    ///
356    /// Creates a rectangle filled with the specified color, typically used as
357    /// input to other filter operations (e.g., for colored shadows).
358    Flood {
359        /// Fill color with alpha channel.
360        color: AlphaColor<Srgb>,
361    },
362    /// Gaussian blur filter.
363    ///
364    /// Applies a Gaussian blur using the specified standard deviation (σ).
365    /// The effective blur range (distance over which pixels are sampled) is
366    /// approximately 3 × `std_deviation`, as this captures ~99.7% of the
367    /// Gaussian distribution.
368    GaussianBlur {
369        /// Standard deviation for the blur kernel. Larger values create more blur.
370        /// Must be non-negative. A value of 0 means no blur.
371        ///
372        /// This directly corresponds to the σ (sigma) parameter in the Gaussian
373        /// function. The visible blur effect extends approximately 3σ in each direction.
374        ///
375        /// TODO: Per the W3C specification, this should support separate x and y values.
376        /// The spec allows `stdDeviation` to be either one number (applied to both axes)
377        /// or two numbers (first for x-axis, second for y-axis). Currently only uniform
378        /// blur is supported. Consider changing to `(f32, f32)` or a dedicated type.
379        std_deviation: f32,
380        /// Edge mode determining how pixels beyond the input bounds are handled.
381        edge_mode: EdgeMode,
382    },
383    /// Drop shadow effect (compound primitive).
384    ///
385    /// Creates a drop shadow by blurring the input's alpha channel, offsetting it,
386    /// and compositing it with the original. This is a compound operation that
387    /// combines multiple primitive operations into one.
388    ///
389    /// See: <https://drafts.fxtf.org/filter-effects-2/#feDropShadowElement>
390    DropShadow {
391        /// Horizontal offset of the shadow in pixels. Positive values shift right.
392        dx: f32,
393        /// Vertical offset of the shadow in pixels. Positive values shift down.
394        dy: f32,
395        /// Blur standard deviation for the shadow. Larger values create softer shadows.
396        std_deviation: f32,
397        /// Shadow color with alpha channel. Alpha controls shadow opacity.
398        color: AlphaColor<Srgb>,
399        /// Edge mode for handling boundaries during blur operation.
400        /// Default is `EdgeMode::None` per SVG spec.
401        edge_mode: EdgeMode,
402    },
403    //
404    // ============================================================
405    // TODO: The following filter primitives are not yet implemented
406    // ============================================================
407    //
408    /// Matrix-based color transformation.
409    ///
410    /// Applies a 4x5 matrix transformation to colors, allowing arbitrary
411    /// color space transformations, hue shifts, and color adjustments.
412    ColorMatrix {
413        /// 4x5 color transformation matrix: 4 rows (R,G,B,A) × 5 columns (R,G,B,A,offset).
414        /// Each output channel is computed as a linear combination of input channels plus offset.
415        matrix: [f32; 20],
416    },
417    /// Geometric offset/translation.
418    ///
419    /// Shifts the input image by the specified offset. Useful for creating
420    /// shadow effects or positioning elements in a filter graph.
421    Offset {
422        /// Horizontal offset in pixels. Positive values shift right.
423        dx: f32,
424        /// Vertical offset in pixels. Positive values shift down.
425        dy: f32,
426    },
427
428    /// Composite two inputs using Porter-Duff compositing operations.
429    ///
430    /// Combines two input images using standard compositing operators
431    /// (over, in, out, atop, xor) or custom arithmetic combination.
432    Composite {
433        /// Porter-Duff compositing operator to apply.
434        operator: CompositeOperator,
435    },
436    /// Blend two inputs using blend modes.
437    ///
438    /// Combines two input images using Photoshop-style blend modes
439    /// (multiply, screen, overlay, etc.).
440    Blend {
441        /// Blend mode determining how colors are combined.
442        mode: BlendMode,
443    },
444    /// Morphological operations (dilate/erode).
445    ///
446    /// Expands (dilate) or contracts (erode) the shapes in the input image.
447    /// Useful for creating outline effects or cleaning up edges.
448    Morphology {
449        /// Morphological operator determining whether to erode or dilate.
450        operator: MorphologyOperator,
451        /// Operation radius in pixels. Larger values create stronger effects.
452        radius: f32,
453    },
454    /// Custom convolution kernel for image processing.
455    ///
456    /// Applies a custom convolution matrix to the input image, enabling
457    /// effects like sharpening, edge detection, embossing, and custom filters.
458    ConvolveMatrix {
459        /// Convolution kernel specification including size, values, and normalization.
460        kernel: ConvolutionKernel,
461    },
462    /// Generate Perlin noise/turbulence patterns.
463    ///
464    /// Creates procedural noise patterns useful for textures, clouds,
465    /// marble effects, and other organic-looking randomness.
466    Turbulence {
467        /// Base frequency for noise generation. Higher values create finer detail.
468        base_frequency: f32,
469        /// Number of octaves for fractal noise. More octaves add finer detail.
470        num_octaves: u32,
471        /// Random seed for reproducible noise generation.
472        seed: u32,
473        /// Type of noise: smooth fractal or more chaotic turbulence.
474        turbulence_type: TurbulenceType,
475    },
476    /// Displace pixels using a displacement map.
477    ///
478    /// Uses the color values from a second input to spatially displace pixels
479    /// in the primary input, creating warping and distortion effects.
480    DisplacementMap {
481        /// Scale factor controlling the displacement intensity.
482        scale: f32,
483        /// Color channel from the displacement map used for X-axis displacement.
484        x_channel: ColorChannel,
485        /// Color channel from the displacement map used for Y-axis displacement.
486        y_channel: ColorChannel,
487    },
488    /// Per-channel component transfer using lookup tables or functions.
489    ///
490    /// Applies independent transfer functions to each color channel,
491    /// enabling color corrections, gamma adjustments, and custom mappings.
492    ComponentTransfer {
493        /// Transfer function applied to the red channel (None = identity).
494        red_function: Option<TransferFunction>,
495        /// Transfer function applied to the green channel (None = identity).
496        green_function: Option<TransferFunction>,
497        /// Transfer function applied to the blue channel (None = identity).
498        blue_function: Option<TransferFunction>,
499        /// Transfer function applied to the alpha channel (None = identity).
500        alpha_function: Option<TransferFunction>,
501    },
502    /// Reference an external image as filter input.
503    ///
504    /// Allows using pre-existing images (from an atlas or resource) as
505    /// input to filter operations, useful for texturing and overlays.
506    Image {
507        /// Identifier referencing an image in the resource atlas.
508        image_id: u32,
509        /// Optional 2D affine transformation matrix [a, b, c, d, e, f].
510        /// Transforms the image before using it as filter input.
511        transform: Option<[f32; 6]>,
512    },
513    /// Tile the input to fill the filter region.
514    ///
515    /// Repeats the input image to fill the entire filter primitive subregion,
516    /// creating a tiling/repeating pattern.
517    Tile,
518    /// Diffuse lighting simulation.
519    ///
520    /// Creates a lighting effect by treating the input's alpha channel as a height map
521    /// and calculating diffuse (matte) reflection from a light source.
522    DiffuseLighting {
523        /// Surface scale factor for converting alpha values to heights.
524        surface_scale: f32,
525        /// Diffuse reflection constant (kd). Controls lighting intensity.
526        diffuse_constant: f32,
527        /// Kernel unit length for gradient calculations in user space.
528        kernel_unit_length: f32,
529        /// Configuration of the light source (point, distant, or spot).
530        light_source: LightSource,
531    },
532    /// Specular lighting simulation.
533    ///
534    /// Creates a lighting effect by treating the input's alpha channel as a height map
535    /// and calculating specular (shiny) reflection highlights from a light source.
536    SpecularLighting {
537        /// Surface scale factor for converting alpha values to heights.
538        surface_scale: f32,
539        /// Specular reflection constant (ks). Controls highlight intensity.
540        specular_constant: f32,
541        /// Specular reflection exponent. Controls highlight sharpness (higher = sharper).
542        specular_exponent: f32,
543        /// Kernel unit length for gradient calculations in user space.
544        kernel_unit_length: f32,
545        /// Configuration of the light source (point, distant, or spot).
546        light_source: LightSource,
547    },
548}
549
550impl FilterPrimitive {
551    /// Calculate the bounds expansion as a `Rect` in user space.
552    ///
553    /// Returns a rectangle centered at the origin representing how much the filter
554    /// expands the processing region in each direction. The rect coordinates are:
555    /// - x0: negative left expansion
556    /// - y0: negative top expansion
557    /// - x1: positive right expansion
558    /// - y1: positive bottom expansion
559    ///
560    /// A `Rect::ZERO` means no expansion. This representation allows the expansion
561    /// to be correctly transformed (including rotation) using standard rect transforms.
562    ///
563    /// For example, a blur filter needs additional pixels around the edges (3*sigma).
564    /// Most filters that don't sample neighboring pixels return `Rect::ZERO`.
565    pub fn expansion_rect(&self) -> Rect {
566        match self {
567            Self::GaussianBlur { std_deviation, .. } => {
568                // Gaussian blur expands uniformly by 3*sigma (covers 99.7% of distribution)
569                let radius = (*std_deviation * 3.0) as f64;
570                Rect::new(-radius, -radius, radius, radius)
571            }
572            Self::Offset { dx, dy } => {
573                // Offset shifts pixels; expand bounds asymmetrically so shifted content isn't cut.
574                let dx = *dx as f64;
575                let dy = *dy as f64;
576                Rect::new(dx.min(0.0), dy.min(0.0), dx.max(0.0), dy.max(0.0))
577            }
578            Self::DropShadow {
579                std_deviation,
580                dx,
581                dy,
582                ..
583            } => {
584                // Drop shadow = blur + offset + composite with original
585                // The expansion rect encompasses both the blur and the offset
586                let blur_radius = (*std_deviation * 3.0) as f64;
587                let dx = *dx as f64;
588                let dy = *dy as f64;
589
590                Rect::new(
591                    -(blur_radius + (-dx).max(0.0)),
592                    -(blur_radius + (-dy).max(0.0)),
593                    blur_radius + dx.max(0.0),
594                    blur_radius + dy.max(0.0),
595                )
596            }
597            // Most other filters don't expand bounds
598            _ => Rect::ZERO,
599        }
600    }
601}
602
603#[cfg(test)]
604mod offset_expansion_tests {
605    use super::FilterPrimitive;
606    use crate::kurbo::Rect;
607
608    #[test]
609    fn offset_expands_in_direction_of_shift() {
610        let p = FilterPrimitive::Offset { dx: 2.5, dy: -3.0 };
611        assert_eq!(
612            p.expansion_rect(),
613            Rect::new(0.0, -3.0, 2.5, 0.0),
614            "Offset expansion should be asymmetric and include the shift vector"
615        );
616    }
617}
618
619/// Unique identifier for a filter primitive in the graph.
620#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
621pub struct FilterId(pub u16);
622
623/// Input connections for a filter primitive.
624#[derive(Debug, Clone, PartialEq)]
625pub struct FilterInputs {
626    /// Primary input ("in" attribute in SVG).
627    pub primary: FilterInput,
628    /// Secondary input ("in2" attribute in SVG, for composite/blend operations).
629    pub secondary: Option<FilterInput>,
630}
631
632impl FilterInputs {
633    /// Create filter inputs with a single input.
634    ///
635    /// Use this for primitives that operate on a single source (blur, color matrix, etc.).
636    pub fn single(input: FilterInput) -> Self {
637        Self {
638            primary: input,
639            secondary: None,
640        }
641    }
642
643    /// Create filter inputs with two inputs (for composite, blend, etc.).
644    ///
645    /// Use this for primitives that combine two sources (composite, blend, displacement map, etc.).
646    pub fn dual(input1: FilterInput, input2: FilterInput) -> Self {
647        Self {
648            primary: input1,
649            secondary: Some(input2),
650        }
651    }
652}
653
654/// A single filter input.
655#[derive(Debug, Clone, PartialEq)]
656pub enum FilterInput {
657    /// Input from a source (`SourceGraphic`, `SourceAlpha`, etc.).
658    Source(FilterSource),
659    /// Input from another filter's result.
660    Result(FilterId),
661}
662
663/// Filter input sources.
664///
665/// Defines the various built-in sources that can be used as filter inputs,
666/// matching the SVG filter primitive input types. These represent implicit
667/// inputs available to any filter primitive without requiring previous operations.
668#[derive(Debug, Clone, Copy, PartialEq, Eq)]
669pub enum FilterSource {
670    /// The original graphic content being filtered.
671    ///
672    /// This is the default input - the rendered result of the element
673    /// the filter is applied to, including all its fill, stroke, and content.
674    SourceGraphic,
675    /// Alpha channel only of the original graphic.
676    ///
677    /// Useful for creating effects based on shape/transparency, such as
678    /// shadows that follow the element's outline.
679    SourceAlpha,
680    /// Background image content behind the filtered element.
681    ///
682    /// Allows filters to incorporate or blend with content behind the element.
683    /// Not always available depending on the rendering context.
684    BackgroundImage,
685    /// Alpha channel only of the background image.
686    ///
687    /// The transparency mask of the background content.
688    BackgroundAlpha,
689    /// The fill paint of the element as an image input.
690    ///
691    /// For elements with gradient or pattern fills, this provides access
692    /// to the fill as a filter input.
693    FillPaint,
694    /// The stroke paint of the element as an image input.
695    ///
696    /// For elements with gradient or pattern strokes, this provides access
697    /// to the stroke as a filter input.
698    StrokePaint,
699}
700
701/// Pre-built compound effects for common use cases.
702///
703/// These effects combine multiple filter primitives into commonly-used visual effects.
704/// They provide a convenient high-level API for complex multi-step filter operations.
705///
706/// **Note:** These are planned but not yet implemented. Use `FilterGraph` to manually
707/// construct these effects from primitives.
708#[derive(Debug, Clone)]
709pub enum CompoundFilter {
710    /// Inner shadow effect (shadow inside the shape).
711    ///
712    /// Creates a shadow that appears inside the boundaries of the shape,
713    /// giving a recessed or inset appearance. This is the opposite of a drop shadow.
714    InnerShadow {
715        /// Horizontal offset of the shadow in pixels. Positive values shift right.
716        dx: f32,
717        /// Vertical offset of the shadow in pixels. Positive values shift down.
718        dy: f32,
719        /// Blur radius for the shadow in pixels. Larger values create softer shadows.
720        blur: f32,
721        /// Shadow color with alpha channel.
722        color: AlphaColor<Srgb>,
723    },
724    /// Glow effect around the shape.
725    ///
726    /// Creates a soft glowing halo around the shape by blurring and
727    /// compositing a colored version with the original.
728    Glow {
729        /// Blur radius for the glow in pixels. Larger values create softer glows.
730        blur: f32,
731        /// Glow color with alpha channel.
732        color: AlphaColor<Srgb>,
733    },
734    /// Bevel effect (3D raised/recessed appearance).
735    ///
736    /// Creates a 3D beveled edge effect using lighting simulation,
737    /// making the shape appear raised or recessed from the surface.
738    Bevel {
739        /// Light source angle in degrees (0° = right, 90° = up).
740        angle: f32,
741        /// Width of the bevel edge in pixels.
742        distance: f32,
743        /// Color for the highlight (lit) side of the bevel.
744        highlight: AlphaColor<Srgb>,
745        /// Color for the shadow (dark) side of the bevel.
746        shadow: AlphaColor<Srgb>,
747    },
748    /// Emboss effect for a raised relief appearance.
749    ///
750    /// Creates an embossed/stamped appearance by simulating lighting
751    /// on a raised surface based on the shape's alpha channel.
752    Emboss {
753        /// Light angle in degrees determining emboss direction.
754        angle: f32,
755        /// Depth of the emboss effect.
756        depth: f32,
757        /// Overall strength/intensity of the effect (0.0 = none, 1.0 = full).
758        amount: f32,
759    },
760}
761
762/// Composite operators for combining filter inputs.
763///
764/// These are the Porter-Duff compositing operators used to combine two images.
765/// Each operator defines how the source (input 1) and destination (input 2)
766/// are combined based on their color and alpha values.
767#[derive(Debug, Clone, Copy, PartialEq)]
768pub enum CompositeOperator {
769    /// Source over destination (standard alpha blending).
770    ///
771    /// The source is composited over the destination. This is the most common
772    /// blending mode where source alpha determines visibility.
773    Over,
774    /// Source in destination (intersection).
775    ///
776    /// The source is only visible where the destination is opaque.
777    /// Result alpha = `source_alpha` × `dest_alpha`.
778    In,
779    /// Source out destination (subtract).
780    ///
781    /// The source is only visible where the destination is transparent.
782    /// Useful for masking/cutting out regions.
783    Out,
784    /// Source atop destination.
785    ///
786    /// Source is composited over destination, but only where destination is opaque.
787    Atop,
788    /// Source XOR destination (exclusive or).
789    ///
790    /// Shows source where destination is transparent and vice versa,
791    /// but not where both are opaque.
792    Xor,
793    /// Arithmetic combination with custom coefficients.
794    ///
795    /// Custom linear combination: result = k1*src*dst + k2*src + k3*dst + k4.
796    /// Allows creating custom compositing operations beyond the standard Porter-Duff set.
797    Arithmetic {
798        /// Coefficient k1 for the (source * destination) term.
799        k1: f32,
800        /// Coefficient k2 for the source term.
801        k2: f32,
802        /// Coefficient k3 for the destination term.
803        k3: f32,
804        /// Constant offset k4 added to the result.
805        k4: f32,
806    },
807}
808
809/// Blend modes for combining colors.
810///
811/// These are blend modes that define how to combine the colors
812/// of two layers. Unlike compositing operators which deal with alpha, blend modes
813/// focus on color mixing while preserving the compositing behavior.
814///
815/// See: <https://drafts.fxtf.org/compositing/#blending>
816#[derive(Debug, Clone, Copy, PartialEq, Eq)]
817pub enum BlendMode {
818    /// Normal blending (simple alpha over, no color mixing).
819    Normal,
820    /// Multiply colors (darkening - like overlaying transparencies).
821    /// Result color is always darker unless one color is white.
822    Multiply,
823    /// Screen colors (lightening - opposite of multiply).
824    /// Result color is always lighter unless one color is black.
825    Screen,
826    /// Overlay blending (multiply if dark, screen if light).
827    /// Combines multiply and screen, preserving highlights and shadows.
828    Overlay,
829    /// Darken (select darker of source and destination per channel).
830    Darken,
831    /// Lighten (select lighter of source and destination per channel).
832    Lighten,
833    /// Color dodge (brightens destination based on source).
834    /// Divides destination by inverted source, creating intense highlights.
835    ColorDodge,
836    /// Color burn (darkens destination based on source).
837    /// Inverts destination, divides by source, and inverts again.
838    ColorBurn,
839    /// Hard light (source overlay - strong effect).
840    /// Like overlay but with source and destination swapped.
841    HardLight,
842    /// Soft light (subtle contrast adjustment).
843    /// Similar to overlay but with a softer, more gentle effect.
844    SoftLight,
845    /// Difference (absolute difference of colors).
846    /// Subtracts the darker from the lighter color, creating inversions.
847    Difference,
848    /// Exclusion (similar to difference but lower contrast).
849    /// Like difference but with less extreme results, more grays.
850    Exclusion,
851    /// Hue blending (hue of source, saturation and luminosity of destination).
852    Hue,
853    /// Saturation blending (saturation of source, hue and luminosity of destination).
854    Saturation,
855    /// Color blending (hue and saturation of source, luminosity of destination).
856    Color,
857    /// Luminosity blending (luminosity of source, hue and saturation of destination).
858    Luminosity,
859}
860
861/// Morphological operators for dilate/erode operations.
862///
863/// These operators modify the shape of objects by expanding or contracting them.
864/// They work by examining neighborhoods of pixels and applying min/max operations.
865#[derive(Debug, Clone, Copy, PartialEq, Eq)]
866pub enum MorphologyOperator {
867    /// Erode operation (shrink/thin shapes).
868    ///
869    /// Makes objects smaller by removing pixels at the edges. Takes the minimum
870    /// value in the neighborhood. Useful for removing noise or separating touching objects.
871    Erode,
872    /// Dilate operation (expand/thicken shapes).
873    ///
874    /// Makes objects larger by adding pixels at the edges. Takes the maximum
875    /// value in the neighborhood. Useful for filling holes or connecting nearby objects.
876    Dilate,
877}
878
879/// Convolution kernel for custom filtering operations.
880///
881/// Defines a square matrix of weights used for convolution-based image processing.
882/// The kernel is applied to each pixel by multiplying surrounding pixels by the weights,
883/// summing the results, dividing by the divisor, and adding the bias.
884#[derive(Debug, Clone, PartialEq)]
885pub struct ConvolutionKernel {
886    /// Kernel size (e.g., 3 for a 3×3 kernel, 5 for 5×5).
887    /// The kernel must be square, so this defines both width and height.
888    pub size: u32,
889    /// Kernel weight values in row-major order.
890    /// Length must equal size × size. Center of kernel is typically at (size/2, size/2).
891    pub values: Vec<f32>,
892    /// Normalization divisor applied to the convolution result.
893    /// Common practice is to use the sum of all weights for averaging, or 1.0 otherwise.
894    pub divisor: f32,
895    /// Bias value added to the result after normalization.
896    /// Useful for edge detection or emboss effects to shift the result range.
897    pub bias: f32,
898    /// Whether to preserve the alpha channel unchanged.
899    /// If true, convolution only applies to RGB; if false, it applies to RGBA.
900    pub preserve_alpha: bool,
901}
902
903/// Types of turbulence noise generation.
904///
905/// Determines the algorithm used for generating procedural noise patterns.
906#[derive(Debug, Clone, Copy, PartialEq, Eq)]
907pub enum TurbulenceType {
908    /// Fractal noise (smooth, natural-looking Perlin noise).
909    ///
910    /// Creates smooth, continuous patterns suitable for natural textures
911    /// like clouds, marble, wood grain, or terrain.
912    FractalNoise,
913    /// Turbulence noise (more chaotic and energetic).
914    ///
915    /// Creates more chaotic patterns with sharper transitions,
916    /// suitable for fire, smoke, or turbulent effects.
917    Turbulence,
918}
919
920/// Color channels for displacement mapping and channel selection.
921///
922/// Specifies which color channel to use for operations that need to
923/// extract or reference individual channels from an image.
924#[derive(Debug, Clone, Copy, PartialEq, Eq)]
925pub enum ColorChannel {
926    /// Red color channel (R component).
927    Red,
928    /// Green color channel (G component).
929    Green,
930    /// Blue color channel (B component).
931    Blue,
932    /// Alpha channel (transparency/opacity).
933    Alpha,
934}
935
936/// Transfer functions for component transfer operations.
937///
938/// These functions map input color channel values to output values,
939/// enabling gamma correction, color grading, and custom color curves.
940/// Input and output values are typically in the range [0, 1].
941#[derive(Debug, Clone, PartialEq)]
942pub enum TransferFunction {
943    /// Identity function (output = input, no change).
944    Identity,
945    /// Table lookup with linear interpolation.
946    ///
947    /// Maps input values using a lookup table with linear interpolation between entries.
948    /// Input 0.0 maps to values\[0\], 1.0 maps to values\[n-1\], intermediate values interpolate.
949    Table {
950        /// Lookup table values defining the transfer curve.
951        /// More values provide smoother curves. Minimum 2 values required.
952        values: Vec<f32>,
953    },
954    /// Discrete step function (posterization).
955    ///
956    /// Maps input to discrete output values without interpolation, creating step/banding effects.
957    /// Each segment gets a constant output value from the table.
958    Discrete {
959        /// Step values for each discrete output level.
960        /// Input range is divided into len(values) segments, each mapping to one value.
961        values: Vec<f32>,
962    },
963    /// Linear function: output = slope × input + intercept.
964    ///
965    /// Simple linear transformation of the input value.
966    Linear {
967        /// Slope coefficient (rate of change).
968        slope: f32,
969        /// Intercept offset (constant added to result).
970        intercept: f32,
971    },
972    /// Gamma correction: output = amplitude × input^exponent + offset.
973    ///
974    /// Applies power-law transformation, commonly used for gamma correction and
975    /// adjusting midtone brightness without affecting blacks or whites.
976    Gamma {
977        /// Amplitude multiplier applied to the result.
978        amplitude: f32,
979        /// Gamma exponent (< 1 brightens, > 1 darkens midtones).
980        exponent: f32,
981        /// Offset added to the final result.
982        offset: f32,
983    },
984}
985
986/// Light source configurations for lighting effects.
987///
988/// Defines different types of light sources used in diffuse and specular lighting
989/// filter primitives. Each type has different characteristics and use cases.
990#[derive(Debug, Clone, PartialEq)]
991pub enum LightSource {
992    /// Distant light source (infinitely far away, like the sun).
993    ///
994    /// All rays are parallel, creating uniform lighting across the surface.
995    /// Direction is specified using spherical coordinates (azimuth and elevation).
996    Distant {
997        /// Azimuth angle in degrees (0° = pointing right, 90° = pointing up).
998        /// Defines the horizontal direction of the light.
999        azimuth: f32,
1000        /// Elevation angle in degrees (0° = horizon, 90° = directly overhead).
1001        /// Defines the vertical angle of the light source.
1002        elevation: f32,
1003    },
1004    /// Point light source at a specific 3D position.
1005    ///
1006    /// Light radiates uniformly in all directions from a single point.
1007    /// Intensity decreases with distance. Like a light bulb.
1008    Point {
1009        /// Light source X coordinate in user space.
1010        x: f32,
1011        /// Light source Y coordinate in user space.
1012        y: f32,
1013        /// Light source Z coordinate (height above the surface).
1014        /// Larger values create softer lighting across larger areas.
1015        z: f32,
1016    },
1017    /// Spot light with position, direction, and cone angle.
1018    ///
1019    /// Light emanates from a point in a specific direction with limited spread.
1020    /// Like a flashlight or stage spotlight with adjustable focus.
1021    Spot {
1022        /// Light source X coordinate in user space.
1023        x: f32,
1024        /// Light source Y coordinate in user space.
1025        y: f32,
1026        /// Light source Z coordinate (height above the surface).
1027        z: f32,
1028        /// X coordinate the spotlight is aimed at.
1029        points_at_x: f32,
1030        /// Y coordinate the spotlight is aimed at.
1031        points_at_y: f32,
1032        /// Z coordinate the spotlight is aimed at.
1033        points_at_z: f32,
1034        /// Specular exponent controlling the focus/sharpness of the spotlight beam.
1035        /// Higher values create tighter, more focused beams.
1036        specular_exponent: f32,
1037        /// Optional cone angle in degrees limiting the spotlight spread.
1038        /// If None, the light spreads based only on the specular exponent.
1039        limiting_cone_angle: Option<f32>,
1040    },
1041}
1042
1043/// Common color transformation matrices.
1044///
1045/// These 4x5 matrices are used with the `ColorMatrix` filter primitive.
1046/// Each row transforms a color channel: [R, G, B, A, offset].
1047pub mod matrices {
1048    /// Identity matrix (no change).
1049    pub const IDENTITY: [f32; 20] = [
1050        1.0, 0.0, 0.0, 0.0, 0.0, // Red
1051        0.0, 1.0, 0.0, 0.0, 0.0, // Green
1052        0.0, 0.0, 1.0, 0.0, 0.0, // Blue
1053        0.0, 0.0, 0.0, 1.0, 0.0, // Alpha
1054    ];
1055
1056    /// Extract alpha channel to RGB (for shadow effects).
1057    pub const ALPHA_TO_BLACK: [f32; 20] = [
1058        0.0, 0.0, 0.0, 1.0, 0.0, // Red = Alpha
1059        0.0, 0.0, 0.0, 1.0, 0.0, // Green = Alpha
1060        0.0, 0.0, 0.0, 1.0, 0.0, // Blue = Alpha
1061        0.0, 0.0, 0.0, 1.0, 0.0, // Alpha = Alpha
1062    ];
1063
1064    /// Grayscale conversion matrix using luminosity weights.
1065    pub const GRAYSCALE: [f32; 20] = [
1066        0.2126, 0.7152, 0.0722, 0.0, 0.0, // Red
1067        0.2126, 0.7152, 0.0722, 0.0, 0.0, // Green
1068        0.2126, 0.7152, 0.0722, 0.0, 0.0, // Blue
1069        0.0, 0.0, 0.0, 1.0, 0.0, // Alpha
1070    ];
1071
1072    /// Sepia tone matrix for vintage photo effect.
1073    pub const SEPIA: [f32; 20] = [
1074        0.393, 0.769, 0.189, 0.0, 0.0, // Red
1075        0.349, 0.686, 0.168, 0.0, 0.0, // Green
1076        0.272, 0.534, 0.131, 0.0, 0.0, // Blue
1077        0.0, 0.0, 0.0, 1.0, 0.0, // Alpha
1078    ];
1079}
1080
1081/// Common convolution kernels.
1082///
1083/// These kernels are used with the `ConvolveMatrix` filter primitive
1084/// for various image processing effects. All provided kernels are 3x3.
1085pub mod kernels {
1086    use super::ConvolutionKernel;
1087    use alloc::vec;
1088
1089    /// 3x3 Gaussian blur kernel for basic smoothing.
1090    pub fn gaussian_3x3() -> ConvolutionKernel {
1091        ConvolutionKernel {
1092            size: 3,
1093            values: vec![1.0, 2.0, 1.0, 2.0, 4.0, 2.0, 1.0, 2.0, 1.0],
1094            divisor: 16.0,
1095            bias: 0.0,
1096            preserve_alpha: false,
1097        }
1098    }
1099
1100    /// 3x3 Sharpen kernel to enhance edges and details.
1101    pub fn sharpen_3x3() -> ConvolutionKernel {
1102        ConvolutionKernel {
1103            size: 3,
1104            values: vec![0.0, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0],
1105            divisor: 1.0,
1106            bias: 0.0,
1107            preserve_alpha: true,
1108        }
1109    }
1110
1111    /// 3x3 Edge detection kernel (Laplacian operator).
1112    pub fn edge_detect_3x3() -> ConvolutionKernel {
1113        ConvolutionKernel {
1114            size: 3,
1115            values: vec![-1.0, -1.0, -1.0, -1.0, 8.0, -1.0, -1.0, -1.0, -1.0],
1116            divisor: 1.0,
1117            bias: 0.0,
1118            preserve_alpha: true,
1119        }
1120    }
1121
1122    /// 3x3 Emboss kernel for creating a raised/beveled appearance.
1123    pub fn emboss_3x3() -> ConvolutionKernel {
1124        ConvolutionKernel {
1125            size: 3,
1126            values: vec![-2.0, -1.0, 0.0, -1.0, 1.0, 1.0, 0.0, 1.0, 2.0],
1127            divisor: 1.0,
1128            bias: 0.5,
1129            preserve_alpha: true,
1130        }
1131    }
1132}