diff --git a/Cargo.lock b/Cargo.lock
index c7a943f..ca15d5c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -40,6 +40,13 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "filter_effects"
+version = "0.1.0"
+dependencies = [
+ "peniko",
+]
+
[[package]]
name = "kurbo"
version = "0.13.1"
diff --git a/Cargo.toml b/Cargo.toml
index 55e0ee7..22aec51 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace]
-members = ["peniko"]
+members = ["filter_effects", "peniko"]
resolver = "2"
[workspace.package]
diff --git a/filter_effects/Cargo.toml b/filter_effects/Cargo.toml
new file mode 100644
index 0000000..0704a95
--- /dev/null
+++ b/filter_effects/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "filter_effects"
+version = "0.1.0"
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+description = "Vocabulary types for applying filter effects to images"
+keywords = ["graphics"]
+categories = ["graphics"]
+readme = "README.md"
+
+[package.metadata.docs.rs]
+all-features = true
+# There are no platform specific docs.
+default-target = "x86_64-unknown-linux-gnu"
+targets = []
+
+[features]
+std = ["peniko/std"]
+libm = ["peniko/libm"]
+
+[dependencies]
+peniko = { version = "0.6.1", path = "../peniko", default-features = false }
+
+[lints]
+workspace = true
diff --git a/filter_effects/LICENSE-APACHE b/filter_effects/LICENSE-APACHE
new file mode 100644
index 0000000..f4f87bd
--- /dev/null
+++ b/filter_effects/LICENSE-APACHE
@@ -0,0 +1,203 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
\ No newline at end of file
diff --git a/filter_effects/LICENSE-MIT b/filter_effects/LICENSE-MIT
new file mode 100644
index 0000000..f3d8434
--- /dev/null
+++ b/filter_effects/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2018 Raph Levien
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/filter_effects/README.md b/filter_effects/README.md
new file mode 100644
index 0000000..890210e
--- /dev/null
+++ b/filter_effects/README.md
@@ -0,0 +1,72 @@
+
+
+# Filter Effects
+
+**Rust definitions for SVG filter effects.**
+
+[](https://xi.zulipchat.com/#narrow/channel/260979-kurbo)
+[](https://deps.rs/repo/github/linebender/peniko)
+[](#license)
+[](https://github.com/linebender/peniko/actions)
+[](https://crates.io/crates/filter_effects)
+[](https://docs.rs/filter_effects)
+
+
+
+The `filter_effects` library builds on top of [`peniko`] and provides
+the [`FilterPrimitive`] vocabulary type to specify transformations as part of SVG filter graphs.
+
+This library doesn't include any code for computing these effects on the CPU or on the GPU,
+and doesn't include a definition of a filter graph.
+It only defines the effects themselves, and lets consumers decide how to compose
+and apply them.
+
+[`peniko`]: https://crates.io/crates/peniko
+[`FilterPrimitive`]: https://docs.rs/filter_effects/latest/filter_effects/struct.FilterPrimitive.html
+
+## Minimum supported Rust Version (MSRV)
+
+This version of Filter Effects has been verified to compile with **Rust 1.85** and later.
+
+Future versions of Filter Effects might increase the Rust version requirement.
+It will not be treated as a breaking change and as such can even happen with small patch releases.
+
+
+Click here if compiling fails.
+
+As time has passed, some of Filter Effects's dependencies could have released versions with a higher Rust requirement.
+If you encounter a compilation issue due to a dependency and don't want to upgrade your Rust toolchain, then you could downgrade the dependency.
+
+```sh
+# Use the problematic dependency's name and version
+cargo update -p package_name --precise 0.1.1
+```
+
+
+## Community
+
+[](https://xi.zulipchat.com/#narrow/channel/260979-kurbo)
+
+Discussion of Peniko development happens in the Linebender Zulip at , specifically the [#kurbo channel](https://xi.zulipchat.com/#narrow/channel/260979-kurbo).
+All public content can be read without logging in.
+
+## License
+
+Licensed under either of
+
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or )
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or )
+
+at your option.
+
+## Contribution
+
+Contributions are welcome by pull request. The [Rust code of conduct] applies.
+Please feel free to add your name to the [AUTHORS] file in any substantive pull request.
+
+Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.
+
+[color]: https://crates.io/crates/color
+[kurbo]: https://crates.io/crates/kurbo
+[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct
+[AUTHORS]: ./AUTHORS
diff --git a/filter_effects/src/color_channel.rs b/filter_effects/src/color_channel.rs
new file mode 100644
index 0000000..15bb502
--- /dev/null
+++ b/filter_effects/src/color_channel.rs
@@ -0,0 +1,18 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+/// Color channels for displacement mapping and channel selection.
+///
+/// Specifies which color channel to use for operations that need to
+/// extract or reference individual channels from an image.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ColorChannel {
+ /// Red color channel (R component).
+ Red,
+ /// Green color channel (G component).
+ Green,
+ /// Blue color channel (B component).
+ Blue,
+ /// Alpha channel (transparency/opacity).
+ Alpha,
+}
diff --git a/filter_effects/src/composite_operator.rs b/filter_effects/src/composite_operator.rs
new file mode 100644
index 0000000..62dff74
--- /dev/null
+++ b/filter_effects/src/composite_operator.rs
@@ -0,0 +1,49 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+/// Composite operators for combining filter inputs.
+///
+/// These are the Porter-Duff compositing operators used to combine two images.
+/// Each operator defines how the source (input 1) and destination (input 2)
+/// are combined based on their color and alpha values.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum CompositeOperator {
+ /// Source over destination (standard alpha blending).
+ ///
+ /// The source is composited over the destination. This is the most common
+ /// blending mode where source alpha determines visibility.
+ Over,
+ /// Source in destination (intersection).
+ ///
+ /// The source is only visible where the destination is opaque.
+ /// Result alpha = `source_alpha` × `dest_alpha`.
+ In,
+ /// Source out destination (subtract).
+ ///
+ /// The source is only visible where the destination is transparent.
+ /// Useful for masking/cutting out regions.
+ Out,
+ /// Source atop destination.
+ ///
+ /// Source is composited over destination, but only where destination is opaque.
+ Atop,
+ /// Source XOR destination (exclusive or).
+ ///
+ /// Shows source where destination is transparent and vice versa,
+ /// but not where both are opaque.
+ Xor,
+ /// Arithmetic combination with custom coefficients.
+ ///
+ /// Custom linear combination: result = k1*src*dst + k2*src + k3*dst + k4.
+ /// Allows creating custom compositing operations beyond the standard Porter-Duff set.
+ Arithmetic {
+ /// Coefficient k1 for the (source * destination) term.
+ k1: f32,
+ /// Coefficient k2 for the source term.
+ k2: f32,
+ /// Coefficient k3 for the destination term.
+ k3: f32,
+ /// Constant offset k4 added to the result.
+ k4: f32,
+ },
+}
diff --git a/filter_effects/src/convolution_kernel.rs b/filter_effects/src/convolution_kernel.rs
new file mode 100644
index 0000000..0b1795a
--- /dev/null
+++ b/filter_effects/src/convolution_kernel.rs
@@ -0,0 +1,28 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+use alloc::vec::Vec;
+
+/// Convolution kernel for custom filtering operations.
+///
+/// Defines a square matrix of weights used for convolution-based image processing.
+/// The kernel is applied to each pixel by multiplying surrounding pixels by the weights,
+/// summing the results, dividing by the divisor, and adding the bias.
+#[derive(Debug, Clone, PartialEq)]
+pub struct ConvolutionKernel {
+ /// Kernel size (e.g., 3 for a 3×3 kernel, 5 for 5×5).
+ /// The kernel must be square, so this defines both width and height.
+ pub size: u32,
+ /// Kernel weight values in row-major order.
+ /// Length must equal size × size. Center of kernel is typically at (size/2, size/2).
+ pub values: Vec,
+ /// Normalization divisor applied to the convolution result.
+ /// Common practice is to use the sum of all weights for averaging, or 1.0 otherwise.
+ pub divisor: f32,
+ /// Bias value added to the result after normalization.
+ /// Useful for edge detection or emboss effects to shift the result range.
+ pub bias: f32,
+ /// Whether to preserve the alpha channel unchanged.
+ /// If true, convolution only applies to RGB; if false, it applies to RGBA.
+ pub preserve_alpha: bool,
+}
diff --git a/filter_effects/src/diffuse_lighting.rs b/filter_effects/src/diffuse_lighting.rs
new file mode 100644
index 0000000..3e02d34
--- /dev/null
+++ b/filter_effects/src/diffuse_lighting.rs
@@ -0,0 +1,22 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+use crate::LightSource;
+
+/// Diffuse lighting simulation.
+///
+/// Creates a lighting effect by treating the input's alpha channel as a height map
+/// and calculating diffuse (matte) reflection from a light source.
+///
+/// See [`FilterPrimitive::DiffuseLighting`](crate::FilterPrimitive::DiffuseLighting).
+#[derive(Debug, Clone, PartialEq)]
+pub struct DiffuseLighting {
+ /// Surface scale factor for converting alpha values to heights.
+ pub surface_scale: f32,
+ /// Diffuse reflection constant (kd). Controls lighting intensity.
+ pub diffuse_constant: f32,
+ /// Kernel unit length for gradient calculations in user space.
+ pub kernel_unit_length: f32,
+ /// Configuration of the light source (point, distant, or spot).
+ pub light_source: LightSource,
+}
diff --git a/filter_effects/src/displacement_map.rs b/filter_effects/src/displacement_map.rs
new file mode 100644
index 0000000..6a779c2
--- /dev/null
+++ b/filter_effects/src/displacement_map.rs
@@ -0,0 +1,20 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+use crate::ColorChannel;
+
+/// Displace pixels using a displacement map.
+///
+/// Uses the color values from a second input to spatially displace pixels
+/// in the primary input, creating warping and distortion effects.
+///
+/// See [`FilterPrimitive::DisplacementMap`](crate::FilterPrimitive::DisplacementMap).
+#[derive(Debug, Clone, PartialEq)]
+pub struct DisplacementMap {
+ /// Scale factor controlling the displacement intensity.
+ pub scale: f32,
+ /// Color channel from the displacement map used for X-axis displacement.
+ pub x_channel: ColorChannel,
+ /// Color channel from the displacement map used for Y-axis displacement.
+ pub y_channel: ColorChannel,
+}
diff --git a/filter_effects/src/drop_shadow.rs b/filter_effects/src/drop_shadow.rs
new file mode 100644
index 0000000..d42da2e
--- /dev/null
+++ b/filter_effects/src/drop_shadow.rs
@@ -0,0 +1,28 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+use peniko::color::{AlphaColor, Srgb};
+
+use crate::EdgeMode;
+
+/// Drop shadow effect (compound primitive).
+///
+/// Creates a drop shadow by blurring the input's alpha channel, offsetting it,
+/// and compositing it with the original. This is a compound operation that
+/// combines multiple primitive operations into one.
+///
+/// See [`FilterPrimitive::DropShadow`](crate::FilterPrimitive::DropShadow).
+#[derive(Debug, Clone, PartialEq)]
+pub struct DropShadow {
+ /// Horizontal offset of the shadow in pixels. Positive values shift right.
+ pub dx: f32,
+ /// Vertical offset of the shadow in pixels. Positive values shift down.
+ pub dy: f32,
+ /// Blur standard deviation for the shadow. Larger values create softer shadows.
+ pub std_deviation: f32,
+ /// Shadow color with alpha channel. Alpha controls shadow opacity.
+ pub color: AlphaColor,
+ /// Edge mode for handling boundaries during blur operation.
+ /// Default is `EdgeMode::None` per SVG spec.
+ pub edge_mode: EdgeMode,
+}
diff --git a/filter_effects/src/edge_mode.rs b/filter_effects/src/edge_mode.rs
new file mode 100644
index 0000000..e4fe23d
--- /dev/null
+++ b/filter_effects/src/edge_mode.rs
@@ -0,0 +1,34 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+/// Edge mode for filter operations.
+///
+/// Determines how to extend the input image when filter operations require sampling
+/// beyond the original image boundaries. This is particularly important for blur and
+/// convolution operations near edges.
+///
+/// See:
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
+pub enum EdgeMode {
+ /// Extend by duplicating edge pixels (clamp to edge).
+ ///
+ /// The input image is extended along each border by replicating the color values
+ /// at the given edge of the input image. This prevents dark halos around edges.
+ Duplicate,
+ /// Extend by wrapping to the opposite edge (repeat/tile).
+ ///
+ /// The input image is extended by taking color values from the opposite edge,
+ /// creating a tiling effect.
+ Wrap,
+ /// Extend by mirroring across the edge.
+ ///
+ /// The input image is extended by taking color values mirrored across the edge.
+ /// This creates seamless continuation at boundaries.
+ Mirror,
+ /// Extend with transparent black (zeros).
+ ///
+ /// The input image is extended with pixel values of zero for R, G, B and A.
+ /// This is the default and most common mode, creating natural fade-to-transparent edges.
+ #[default]
+ None,
+}
diff --git a/filter_effects/src/filter_primitive.rs b/filter_effects/src/filter_primitive.rs
new file mode 100644
index 0000000..24411f7
--- /dev/null
+++ b/filter_effects/src/filter_primitive.rs
@@ -0,0 +1,228 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+use alloc::vec::Vec;
+
+use peniko::Mix;
+use peniko::color::{AlphaColor, Srgb};
+
+use crate::{
+ CompositeOperator, ConvolutionKernel, DiffuseLighting, DisplacementMap, DropShadow,
+ GaussianBlur, Morphology, Offset, SpecularLighting, Turbulence,
+};
+
+/// Low-level definition of a visual effect that should be applied to zero, one or more input images.
+///
+/// This only stores the definition of the transformation to be applied, not inputs,
+/// outputs, texture resolution, or any other kind of filter graph data.
+///
+/// Most filters expect one input image.
+/// Some filters expect two inputs.
+/// [`Self::Flood`] and [`Self::Image`] are essentially sources expect no input.
+/// [`Self::Merge`] expects an arbitrary number of inputs.
+///
+/// Filter definitions match SVG filter primitives.
+/// See [Filter Effects Module Level 1 § 9.1](https://drafts.csswg.org/filter-effects/#FilterPrimitivesOverviewIntro) for more info.
+#[derive(Debug, Clone, PartialEq)]
+pub enum FilterPrimitive {
+ /// Blend two inputs using an imaging software blending mode.
+ ///
+ /// The [`BlendMode`](peniko::BlendMode) consists of the given color [`Mix`]
+ /// applied with the [`Compose::SrcOver`](peniko::Compose::SrcOver) operator.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.5](https://drafts.csswg.org/filter-effects/#feBlendElement).
+ Blend(Mix),
+
+ /// Apply a matrix transformation on the RGBA values of every inpux pixel.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.6](https://drafts.csswg.org/filter-effects/#feColorMatrixElement).
+ ColorMatrix {
+ // TODO - Replace with color_operations::ColorMatrix
+ // once https://github.com/linebender/color/pull/227 is merged
+ /// 4x5 color transformation matrix: 4 rows (R,G,B,A) × 5 columns (R,G,B,A,offset).
+ /// Each output channel is computed as a linear combination of input channels plus offset.
+ matrix: [f32; 20],
+ },
+
+ /// Perform per-channel remapping on the RGBA values of every inpux pixel.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.7](https://drafts.csswg.org/filter-effects/#feComponentTransferElement).
+ ComponentTransfer {
+ // TODO - Replace with color_operations::ComponentTransfer
+ // once https://github.com/linebender/color/pull/227 is merged
+ /// Transfer function applied to the red channel (None = identity).
+ red_function: Option,
+ /// Transfer function applied to the green channel (None = identity).
+ green_function: Option,
+ /// Transfer function applied to the blue channel (None = identity).
+ blue_function: Option,
+ /// Transfer function applied to the alpha channel (None = identity).
+ alpha_function: Option,
+ },
+
+ /// Combine two inputs using Porter-Duff compositing operations.
+ ///
+ /// Uses standard operators (over, in, out, atop, xor) or custom arithmetic combination.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.8](https://drafts.csswg.org/filter-effects/#feCompositeElement).
+ Composite(CompositeOperator),
+
+ /// Apply convolution kernel to input image.
+ ///
+ /// Each output pixel is a result of multiplying the input pixels and its neighbors
+ /// by the convolution matrix.
+ /// Enables effects like sharpening, edge detection, embossing, and custom filters.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.9](https://drafts.csswg.org/filter-effects/#feConvolveMatrixElement).
+ ConvolveMatrix(ConvolutionKernel),
+
+ /// Light an image using the alpha channel as a bump map.
+ ///
+ /// Computes diffuse (matte) reflection from a light source on that bump map.
+ /// The computed light map can be combined with a texture image using the
+ /// [`Composite`](Self::Composite) operation.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.10](https://drafts.csswg.org/filter-effects/#feDiffuseLightingElement).
+ DiffuseLighting(DiffuseLighting),
+
+ /// Displace pixels using a displacement map.
+ ///
+ /// Uses the color values from a second input as vectors to spatially displace
+ /// pixels in the primary input, creating warping and distortion effects.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.11](https://drafts.csswg.org/filter-effects/#feDisplacementMapElement).
+ DisplacementMap(DisplacementMap),
+
+ /// Create a drop shadow of the input image.
+ ///
+ /// Blurs the input's alpha channel, offsets it, composes it with the original.
+ /// This is a compound operation that combines multiple primitive operations into one.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.12](https://drafts.csswg.org/filter-effects/#feDropShadowElement).
+ DropShadow(DropShadow),
+
+ /// Create a rectangle filled with the specified color
+ ///
+ /// Typically used as input to other filter operations (e.g., for colored shadows).
+ ///
+ /// See [Filter Effects Module Level 1 § 9.13](https://drafts.csswg.org/filter-effects/#feFloodElement).
+ Flood(AlphaColor),
+
+ /// Perform a Gaussian blur on the input image.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.14](https://drafts.csswg.org/filter-effects/#feGaussianBlurElement).
+ GaussianBlur(GaussianBlur),
+
+ /// Reference an external image as filter input.
+ ///
+ /// Allows using pre-existing images (from an atlas or resource) as
+ /// input to filter operations, useful for texturing and overlays.
+ ///
+ /// The id is an arbitrary integer; how the integer is interpreted is defined
+ /// by whichever framework consumes this filter.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.15](https://drafts.csswg.org/filter-effects/#feImageElement).
+ Image(ImageId),
+
+ /// Composite input images on top of each other using the
+ /// [`Over`](CompositeOperator::Over) operator.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.16](https://drafts.csswg.org/filter-effects/#feMergeElement).
+ Merge,
+
+ /// Performs "fattening" or "thinning" of input image.
+ ///
+ /// Useful for creating outline effects or cleaning up edges.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.17](https://drafts.csswg.org/filter-effects/#feMorphologyElement).
+ Morphology(Morphology),
+
+ /// Translate the input image by the specified offset.
+ ///
+ /// This should be interpreted as translating the input layer's pixels,
+ /// not the input layer itself.
+ /// A large translation may result in an empty image, as all the pixels are
+ /// translated out of bounds.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.18](https://drafts.csswg.org/filter-effects/#feOffsetElement).
+ Offset(Offset),
+
+ /// Light an image using the alpha channel as a bump map.
+ ///
+ /// Computes specular (shiny) reflection highlights from a light source on that bump map.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.19](https://drafts.csswg.org/filter-effects/#feSpecularLightingElement).
+ SpecularLighting(SpecularLighting),
+
+ /// Tile the input to fill the filter region.
+ ///
+ /// Repeats the input image to fill the entire filter primitive subregion,
+ /// creating a tiling/repeating pattern.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.20](https://drafts.csswg.org/filter-effects/#feTileElement).
+ Tile,
+
+ /// Generate Perlin noise/turbulence patterns.
+ ///
+ /// Creates procedural noise patterns useful for textures, clouds,
+ /// marble effects, and other organic-looking randomness.
+ ///
+ /// See [Filter Effects Module Level 1 § 9.21](https://drafts.csswg.org/filter-effects/#feTurbulenceElement).
+ Turbulence(Turbulence),
+}
+
+/// Arbitrary integer representing an index into some imagee storing system.
+pub type ImageId = u64;
+
+// ---
+
+// TODO - Remove once https://github.com/linebender/color/pull/227 is merged
+/// Transfer functions for component transfer operations.
+///
+/// These functions map input color channel values to output values,
+/// enabling gamma correction, color grading, and custom color curves.
+/// Input and output values are typically in the range [0, 1].
+#[derive(Debug, Clone, PartialEq)]
+pub enum TransferFunction {
+ /// Identity function (output = input, no change).
+ Identity,
+ /// Table lookup with linear interpolation.
+ ///
+ /// Maps input values using a lookup table with linear interpolation between entries.
+ /// Input 0.0 maps to values\[0\], 1.0 maps to values\[n-1\], intermediate values interpolate.
+ Table {
+ /// Lookup table values defining the transfer curve.
+ /// More values provide smoother curves. Minimum 2 values required.
+ values: Vec,
+ },
+ /// Discrete step function (posterization).
+ ///
+ /// Maps input to discrete output values without interpolation, creating step/banding effects.
+ /// Each segment gets a constant output value from the table.
+ Discrete {
+ /// Step values for each discrete output level.
+ /// Input range is divided into len(values) segments, each mapping to one value.
+ values: Vec,
+ },
+ /// Linear function: output = slope × input + intercept.
+ ///
+ /// Simple linear transformation of the input value.
+ Linear {
+ /// Slope coefficient (rate of change).
+ slope: f32,
+ /// Intercept offset (constant added to result).
+ intercept: f32,
+ },
+ /// Gamma correction: output = amplitude × input^exponent + offset.
+ ///
+ /// Applies power-law transformation, commonly used for gamma correction and
+ /// adjusting midtone brightness without affecting blacks or whites.
+ Gamma {
+ /// Amplitude multiplier applied to the result.
+ amplitude: f32,
+ /// Gamma exponent (< 1 brightens, > 1 darkens midtones).
+ exponent: f32,
+ /// Offset added to the final result.
+ offset: f32,
+ },
+}
diff --git a/filter_effects/src/gaussian_blur.rs b/filter_effects/src/gaussian_blur.rs
new file mode 100644
index 0000000..21bef76
--- /dev/null
+++ b/filter_effects/src/gaussian_blur.rs
@@ -0,0 +1,29 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+use crate::EdgeMode;
+
+/// Gaussian blur filter.
+///
+/// Applies a Gaussian blur using the specified standard deviation (σ).
+/// The effective blur range (distance over which pixels are sampled) is
+/// approximately 3 × `std_deviation`, as this captures ~99.7% of the
+/// Gaussian distribution.
+///
+/// See [`FilterPrimitive::GaussianBlur`](crate::FilterPrimitive::GaussianBlur).
+#[derive(Debug, Clone, PartialEq)]
+pub struct GaussianBlur {
+ /// Standard deviation for the blur kernel. Larger values create more blur.
+ /// Must be non-negative. A value of 0 means no blur.
+ ///
+ /// This directly corresponds to the σ (sigma) parameter in the Gaussian
+ /// function. The visible blur effect extends approximately 3σ in each direction.
+ ///
+ /// TODO: Per the W3C specification, this should support separate x and y values.
+ /// The spec allows `stdDeviation` to be either one number (applied to both axes)
+ /// or two numbers (first for x-axis, second for y-axis). Currently only uniform
+ /// blur is supported. Consider changing to `(f32, f32)` or a dedicated type.
+ pub std_deviation: f32,
+ /// Edge mode determining how pixels beyond the input bounds are handled.
+ pub edge_mode: EdgeMode,
+}
diff --git a/filter_effects/src/lib.rs b/filter_effects/src/lib.rs
new file mode 100644
index 0000000..660e70d
--- /dev/null
+++ b/filter_effects/src/lib.rs
@@ -0,0 +1,61 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+//! Definitions for SVG filter effects.
+//!
+//! The `filter_effects` library builds on top of [`peniko`] and provides
+//! the [`FilterPrimitive`] vocabulary type to specify transformations as part of SVG filter graphs.
+//!
+//! This library doesn't include any code for computing these effects on the CPU or on the GPU,
+//! and doesn't include a definition of a filter graph.
+//! It only defines the effects themselves, and lets consumers decide how to compose
+//! and apply them.
+//!
+//! [`peniko`]: https://crates.io/crates/peniko
+
+// LINEBENDER LINT SET - lib.rs - v4
+// See https://linebender.org/wiki/canonical-lints/
+// These lints shouldn't apply to examples or tests.
+#![cfg_attr(not(test), warn(unused_crate_dependencies))]
+// These lints shouldn't apply to examples.
+#![warn(clippy::print_stdout, clippy::print_stderr)]
+// Targeting e.g. 32-bit means structs containing usize can give false positives for 64-bit.
+#![cfg_attr(target_pointer_width = "64", warn(clippy::trivially_copy_pass_by_ref))]
+// END LINEBENDER LINT SET
+#![cfg_attr(docsrs, feature(doc_cfg))]
+#![no_std]
+
+extern crate alloc;
+
+mod color_channel;
+mod composite_operator;
+mod convolution_kernel;
+mod diffuse_lighting;
+mod displacement_map;
+mod drop_shadow;
+mod edge_mode;
+mod filter_primitive;
+mod gaussian_blur;
+mod light_source;
+mod morphology;
+mod offset;
+mod specular_lighting;
+mod turbulence;
+
+pub use color_channel::*;
+pub use composite_operator::*;
+pub use convolution_kernel::*;
+pub use diffuse_lighting::*;
+pub use displacement_map::*;
+pub use drop_shadow::*;
+pub use edge_mode::*;
+pub use filter_primitive::*;
+pub use gaussian_blur::*;
+pub use light_source::*;
+pub use morphology::*;
+pub use offset::*;
+pub use specular_lighting::*;
+pub use turbulence::*;
+
+/// Re-export of the peniko library.
+pub use peniko;
diff --git a/filter_effects/src/light_source.rs b/filter_effects/src/light_source.rs
new file mode 100644
index 0000000..e85ec6e
--- /dev/null
+++ b/filter_effects/src/light_source.rs
@@ -0,0 +1,59 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+/// Light source configurations for lighting effects.
+///
+/// Defines different types of light sources used in diffuse and specular lighting
+/// filter primitives. Each type has different characteristics and use cases.
+#[derive(Debug, Clone, PartialEq)]
+pub enum LightSource {
+ /// Distant light source (infinitely far away, like the sun).
+ ///
+ /// All rays are parallel, creating uniform lighting across the surface.
+ /// Direction is specified using spherical coordinates (azimuth and elevation).
+ Distant {
+ /// Azimuth angle in degrees (0° = pointing right, 90° = pointing up).
+ /// Defines the horizontal direction of the light.
+ azimuth: f32,
+ /// Elevation angle in degrees (0° = horizon, 90° = directly overhead).
+ /// Defines the vertical angle of the light source.
+ elevation: f32,
+ },
+ /// Point light source at a specific 3D position.
+ ///
+ /// Light radiates uniformly in all directions from a single point.
+ /// Intensity decreases with distance. Like a light bulb.
+ Point {
+ /// Light source X coordinate in user space.
+ x: f32,
+ /// Light source Y coordinate in user space.
+ y: f32,
+ /// Light source Z coordinate (height above the surface).
+ /// Larger values create softer lighting across larger areas.
+ z: f32,
+ },
+ /// Spot light with position, direction, and cone angle.
+ ///
+ /// Light emanates from a point in a specific direction with limited spread.
+ /// Like a flashlight or stage spotlight with adjustable focus.
+ Spot {
+ /// Light source X coordinate in user space.
+ x: f32,
+ /// Light source Y coordinate in user space.
+ y: f32,
+ /// Light source Z coordinate (height above the surface).
+ z: f32,
+ /// X coordinate the spotlight is aimed at.
+ points_at_x: f32,
+ /// Y coordinate the spotlight is aimed at.
+ points_at_y: f32,
+ /// Z coordinate the spotlight is aimed at.
+ points_at_z: f32,
+ /// Specular exponent controlling the focus/sharpness of the spotlight beam.
+ /// Higher values create tighter, more focused beams.
+ specular_exponent: f32,
+ /// Optional cone angle in degrees limiting the spotlight spread.
+ /// If None, the light spreads based only on the specular exponent.
+ limiting_cone_angle: Option,
+ },
+}
diff --git a/filter_effects/src/morphology.rs b/filter_effects/src/morphology.rs
new file mode 100644
index 0000000..fa2335b
--- /dev/null
+++ b/filter_effects/src/morphology.rs
@@ -0,0 +1,34 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+/// Morphological operations (dilate/erode).
+///
+/// Expands (dilate) or contracts (erode) the shapes in the input image.
+/// Useful for creating outline effects or cleaning up edges.
+///
+/// See [`FilterPrimitive::Morphology`](crate::FilterPrimitive::Morphology).
+#[derive(Debug, Clone, PartialEq)]
+pub struct Morphology {
+ /// Morphological operator determining whether to erode or dilate.
+ pub operator: MorphologyOperator,
+ /// Operation radius in pixels. Larger values create stronger effects.
+ pub radius: f32,
+}
+
+/// Morphological operators for dilate/erode operations.
+///
+/// These operators modify the shape of objects by expanding or contracting them.
+/// They work by examining neighborhoods of pixels and applying min/max operations.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum MorphologyOperator {
+ /// Erode operation (shrink/thin shapes).
+ ///
+ /// Makes objects smaller by removing pixels at the edges. Takes the minimum
+ /// value in the neighborhood. Useful for removing noise or separating touching objects.
+ Erode,
+ /// Dilate operation (expand/thicken shapes).
+ ///
+ /// Makes objects larger by adding pixels at the edges. Takes the maximum
+ /// value in the neighborhood. Useful for filling holes or connecting nearby objects.
+ Dilate,
+}
diff --git a/filter_effects/src/offset.rs b/filter_effects/src/offset.rs
new file mode 100644
index 0000000..d92f458
--- /dev/null
+++ b/filter_effects/src/offset.rs
@@ -0,0 +1,30 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+/// Geometric offset/translation.
+///
+/// Shifts the input image by the specified offset. Useful for creating
+/// shadow effects or positioning elements in a filter graph.
+///
+/// See [`FilterPrimitive::Offset`](crate::FilterPrimitive::Offset).
+#[derive(Debug, Clone, PartialEq)]
+pub struct Offset {
+ /// Horizontal offset in pixels. Positive values shift right.
+ pub dx: f32,
+ /// Vertical offset in pixels. Positive values shift down.
+ pub dy: f32,
+}
+
+impl Offset {
+ /// Create offset with given values.
+ pub const fn new(dx: f32, dy: f32) -> Self {
+ Self { dx, dy }
+ }
+}
+
+impl From<(f32, f32)> for Offset {
+ fn from(value: (f32, f32)) -> Self {
+ let (dx, dy) = value;
+ Self { dx, dy }
+ }
+}
diff --git a/filter_effects/src/specular_lighting.rs b/filter_effects/src/specular_lighting.rs
new file mode 100644
index 0000000..d83025f
--- /dev/null
+++ b/filter_effects/src/specular_lighting.rs
@@ -0,0 +1,24 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+use crate::LightSource;
+
+/// Specular lighting simulation.
+///
+/// Creates a lighting effect by treating the input's alpha channel as a height map
+/// and calculating specular (shiny) reflection highlights from a light source.
+///
+/// See [`FilterPrimitive::SpecularLighting`](crate::FilterPrimitive::SpecularLighting).
+#[derive(Debug, Clone, PartialEq)]
+pub struct SpecularLighting {
+ /// Surface scale factor for converting alpha values to heights.
+ pub surface_scale: f32,
+ /// Specular reflection constant (ks). Controls highlight intensity.
+ pub specular_constant: f32,
+ /// Specular reflection exponent. Controls highlight sharpness (higher = sharper).
+ pub specular_exponent: f32,
+ /// Kernel unit length for gradient calculations in user space.
+ pub kernel_unit_length: f32,
+ /// Configuration of the light source (point, distant, or spot).
+ pub light_source: LightSource,
+}
diff --git a/filter_effects/src/turbulence.rs b/filter_effects/src/turbulence.rs
new file mode 100644
index 0000000..a2ce5ad
--- /dev/null
+++ b/filter_effects/src/turbulence.rs
@@ -0,0 +1,37 @@
+// Copyright 2026 the Peniko Authors
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+/// Generate Perlin noise/turbulence patterns.
+///
+/// Creates procedural noise patterns useful for textures, clouds,
+/// marble effects, and other organic-looking randomness.
+///
+/// See [`FilterPrimitive::Turbulence`](crate::FilterPrimitive::Turbulence).
+#[derive(Debug, Clone, PartialEq)]
+pub struct Turbulence {
+ /// Base frequency for noise generation. Higher values create finer detail.
+ pub base_frequency: f32,
+ /// Number of octaves for fractal noise. More octaves add finer detail.
+ pub num_octaves: u32,
+ /// Random seed for reproducible noise generation.
+ pub seed: u32,
+ /// Type of noise: smooth fractal or more chaotic turbulence.
+ pub turbulence_type: TurbulenceType,
+}
+
+/// Types of turbulence noise generation.
+///
+/// Determines the algorithm used for generating procedural noise patterns.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TurbulenceType {
+ /// Fractal noise (smooth, natural-looking Perlin noise).
+ ///
+ /// Creates smooth, continuous patterns suitable for natural textures
+ /// like clouds, marble, wood grain, or terrain.
+ FractalNoise,
+ /// Turbulence noise (more chaotic and energetic).
+ ///
+ /// Creates more chaotic patterns with sharper transitions,
+ /// suitable for fire, smoke, or turbulent effects.
+ Turbulence,
+}