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}