[librsvg: 2/4] Implement saturate filter function
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 2/4] Implement saturate filter function
- Date: Thu, 13 May 2021 16:04:18 +0000 (UTC)
commit 638d5a919b8898316ebcbc82b4e1d73067c30aa5
Author: John Ledbetter <john ledbetter gmail com>
Date: Wed May 12 10:34:11 2021 -0400
Implement saturate filter function
`saturate()` takes a single optional `number-percentage` argument,
similar to `sepia()`; the value is allowed to be any positive number;
values greater than 1 increase the saturation, while numbers less than
1 desaturate.
Saturate is implemented as a `FeColorMatrix`.
See:
* https://www.w3.org/TR/filter-effects/#funcdef-filter-saturate
* https://www.w3.org/TR/filter-effects/#saturateEquivalent
src/filter_func.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/src/filters.rs | 59 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 134 insertions(+)
---
diff --git a/src/filter_func.rs b/src/filter_func.rs
index ca7bc7c6..d6c835d3 100644
--- a/src/filter_func.rs
+++ b/src/filter_func.rs
@@ -19,6 +19,7 @@ use crate::{drawing_ctx::DrawingCtx, filters::component_transfer};
pub enum FilterFunction {
Blur(Blur),
Opacity(Opacity),
+ Saturate(Saturate),
Sepia(Sepia),
}
@@ -38,6 +39,14 @@ pub struct Opacity {
proportion: Option<f64>,
}
+/// Parameters for the `saturate()` filter function
+///
+/// https://www.w3.org/TR/filter-effects/#funcdef-filter-saturate
+#[derive(Debug, Clone, PartialEq)]
+pub struct Saturate {
+ proportion: Option<f64>,
+}
+
/// Parameters for the `sepia()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-sepia
@@ -84,6 +93,17 @@ fn parse_opacity<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, Pars
Ok(FilterFunction::Opacity(Opacity { proportion }))
}
+#[allow(clippy::unnecessary_wraps)]
+fn parse_saturate<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
+ let proportion = match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
+ Ok(NumberOrPercentage { value }) if value < 0.0 => None,
+ Ok(NumberOrPercentage { value }) => Some(value),
+ Err(_) => None,
+ };
+
+ Ok(FilterFunction::Saturate(Saturate { proportion }))
+}
+
#[allow(clippy::unnecessary_wraps)]
fn parse_sepia<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
let proportion = match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
@@ -146,6 +166,39 @@ impl Opacity {
}
}
+impl Saturate {
+ #[rustfmt::skip]
+ fn matrix(&self) -> nalgebra::Matrix5<f64> {
+ let p = self.proportion.unwrap_or(1.0);
+
+ nalgebra::Matrix5::new(
+ 0.213 + 0.787 * p, 0.715 - 0.715 * p, 0.072 - 0.072 * p, 0.0, 0.0,
+ 0.213 - 0.213 * p, 0.715 + 0.285 * p, 0.072 - 0.072 * p, 0.0, 0.0,
+ 0.213 - 0.213 * p, 0.715 - 0.715 * p, 0.072 + 0.928 * p, 0.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, 0.0, 1.0,
+ )
+ }
+
+ fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
+ let user_space_filter = Filter::default().to_user_space(params);
+
+ let saturate = ResolvedPrimitive {
+ primitive: Primitive::default(),
+ params: PrimitiveParams::ColorMatrix(ColorMatrix {
+ matrix: self.matrix(),
+ ..ColorMatrix::default()
+ }),
+ }
+ .into_user_space(params);
+
+ FilterSpec {
+ user_space_filter,
+ primitives: vec![saturate],
+ }
+ }
+}
+
impl Sepia {
#[rustfmt::skip]
fn matrix(&self) -> nalgebra::Matrix5<f64> {
@@ -185,6 +238,7 @@ impl Parse for FilterFunction {
let fns: Vec<(&str, &dyn Fn(&mut Parser<'i, '_>) -> _)> = vec![
("blur", &parse_blur),
("opacity", &parse_opacity),
+ ("saturate", &parse_saturate),
("sepia", &parse_sepia),
];
@@ -213,6 +267,7 @@ impl FilterFunction {
match self {
FilterFunction::Blur(v) => Ok(v.to_filter_spec(¶ms)),
FilterFunction::Opacity(v) => Ok(v.to_filter_spec(¶ms)),
+ FilterFunction::Saturate(v) => Ok(v.to_filter_spec(¶ms)),
FilterFunction::Sepia(v) => Ok(v.to_filter_spec(¶ms)),
}
}
@@ -254,6 +309,21 @@ mod tests {
);
}
+ #[test]
+ fn parses_saturate() {
+ assert_eq!(
+ FilterFunction::parse_str("saturate()").unwrap(),
+ FilterFunction::Saturate(Saturate { proportion: None })
+ );
+
+ assert_eq!(
+ FilterFunction::parse_str("saturate(50%)").unwrap(),
+ FilterFunction::Saturate(Saturate {
+ proportion: Some(0.50_f32.into()),
+ })
+ );
+ }
+
#[test]
fn parses_sepia() {
assert_eq!(
@@ -301,6 +371,11 @@ mod tests {
assert!(FilterFunction::parse_str("opacity(foo)").is_err());
}
+ #[test]
+ fn invalid_saturate_yields_error() {
+ assert!(FilterFunction::parse_str("saturate(foo)").is_err());
+ }
+
#[test]
fn invalid_sepia_yields_error() {
assert!(FilterFunction::parse_str("sepia(foo)").is_err());
diff --git a/tests/src/filters.rs b/tests/src/filters.rs
index 66fac65a..61e89927 100644
--- a/tests/src/filters.rs
+++ b/tests/src/filters.rs
@@ -163,6 +163,7 @@ fn opacity_filter_func() {
let svg = load_svg(
br##"<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400"
height="400">
+ <rect x="100" y="100" width="200" height="200" fill="red"/>
<rect x="100" y="100" width="200" height="200" fill="lime" filter="opacity(0.75)"/>
</svg>
"##,
@@ -192,6 +193,7 @@ fn opacity_filter_func() {
</filter>
</defs>
+ <rect x="100" y="100" width="200" height="200" fill="red"/>
<rect x="100" y="100" width="200" height="200" fill="lime" filter="url(#filter)"/>
</svg>
"##,
@@ -215,6 +217,63 @@ fn opacity_filter_func() {
.evaluate(&output_surf, "opacity_filter_func");
}
+#[test]
+fn saturate_filter_func() {
+ // Create an element with a filter function, and compare it to the
+ // supposed equivalent using the <filter> element.
+ let svg = load_svg(
+ br##"<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400"
height="400">
+ <rect x="100" y="100" width="200" height="200" fill="lime" filter="saturate(0.75)"/>
+</svg>
+"##,
+ ).unwrap();
+
+ let output_surf = render_document(
+ &svg,
+ SurfaceSize(400, 400),
+ |_| (),
+ cairo::Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: 400.0,
+ height: 400.0,
+ },
+ )
+ .unwrap();
+
+ let reference = load_svg(
+ br##"<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400"
height="400">
+ <defs>
+ <filter id="filter">
+ <feColorMatrix type="saturate" values="0.75" />
+ </filter>
+ </defs>
+
+ <rect x="100" y="100" width="200" height="200" fill="lime" filter="url(#filter)"/>
+</svg>
+"##,
+ ).unwrap();
+
+ let reference_surf = render_document(
+ &reference,
+ SurfaceSize(400, 400),
+ |_| (),
+ cairo::Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: 400.0,
+ height: 400.0,
+ },
+ )
+ .unwrap();
+
+ Reference::from_surface(reference_surf.into_image_surface().unwrap())
+ .compare(&output_surf)
+ .evaluate(&output_surf, "saturate_filter_func");
+}
+
#[test]
fn sepia_filter_func() {
// Create an element with a filter function, and compare it to the
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]