[librsvg: 1/9] Implement grayscale filter function
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 1/9] Implement grayscale filter function
- Date: Wed, 26 May 2021 15:04:44 +0000 (UTC)
commit bab15965189812a2b120c575aca6166cff3c7578
Author: John Ledbetter <john ledbetter gmail com>
Date: Sat May 15 13:34:51 2021 -0400
Implement grayscale filter function
The grayscale function takes a single optional `<number-percentage>`
argument which is the proportion of the conversion.
The default value is 1, which is completely grayscale.
The color matrix operation to convert to grayscale is the same as a
saturation conversion with an argument of (1 - x).
* https://www.w3.org/TR/filter-effects/#funcdef-filter-grayscale
* https://www.w3.org/TR/filter-effects/#grayscaleEquivalent
src/filter_func.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/src/filters.rs | 20 +++++++++++++++++++
2 files changed, 75 insertions(+)
---
diff --git a/src/filter_func.rs b/src/filter_func.rs
index d6c835d3..8ce3725e 100644
--- a/src/filter_func.rs
+++ b/src/filter_func.rs
@@ -18,6 +18,7 @@ use crate::{drawing_ctx::DrawingCtx, filters::component_transfer};
#[derive(Debug, Clone, PartialEq)]
pub enum FilterFunction {
Blur(Blur),
+ Grayscale(Grayscale),
Opacity(Opacity),
Saturate(Saturate),
Sepia(Sepia),
@@ -31,6 +32,14 @@ pub struct Blur {
std_deviation: Option<Length<Both>>,
}
+/// Parameters for the `grayscale()` filter function
+///
+/// https://www.w3.org/TR/filter-effects/#funcdef-filter-grayscale
+#[derive(Debug, Clone, PartialEq)]
+pub struct Grayscale {
+ proportion: Option<f64>,
+}
+
/// Parameters for the `opacity()` filter function
///
/// https://www.w3.org/TR/filter-effects/#funcdef-filter-opacity
@@ -82,6 +91,17 @@ fn parse_blur<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseEr
}))
}
+#[allow(clippy::unnecessary_wraps)]
+fn parse_grayscale<'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.clamp(0.0, 1.0)),
+ Err(_) => None,
+ };
+
+ Ok(FilterFunction::Grayscale(Grayscale { proportion }))
+}
+
#[allow(clippy::unnecessary_wraps)]
fn parse_opacity<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
let proportion = match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
@@ -138,6 +158,19 @@ impl Blur {
}
}
+impl Grayscale {
+ fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
+ // grayscale is implemented as the inverse of a saturate operation,
+ // with the input clamped to the range [0, 1] by the parser.
+ let p = 1.0 - self.proportion.unwrap_or(1.0);
+ let saturate = Saturate {
+ proportion: Some(p),
+ };
+
+ saturate.to_filter_spec(params)
+ }
+}
+
impl Opacity {
fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
let p = self.proportion.unwrap_or(1.0);
@@ -237,6 +270,7 @@ impl Parse for FilterFunction {
let loc = parser.current_source_location();
let fns: Vec<(&str, &dyn Fn(&mut Parser<'i, '_>) -> _)> = vec![
("blur", &parse_blur),
+ ("grayscale", &parse_grayscale),
("opacity", &parse_opacity),
("saturate", &parse_saturate),
("sepia", &parse_sepia),
@@ -266,6 +300,7 @@ impl FilterFunction {
match self {
FilterFunction::Blur(v) => Ok(v.to_filter_spec(¶ms)),
+ FilterFunction::Grayscale(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)),
@@ -294,6 +329,21 @@ mod tests {
);
}
+ #[test]
+ fn parses_grayscale() {
+ assert_eq!(
+ FilterFunction::parse_str("grayscale()").unwrap(),
+ FilterFunction::Grayscale(Grayscale { proportion: None })
+ );
+
+ assert_eq!(
+ FilterFunction::parse_str("grayscale(50%)").unwrap(),
+ FilterFunction::Grayscale(Grayscale {
+ proportion: Some(0.50_f32.into()),
+ })
+ );
+ }
+
#[test]
fn parses_opacity() {
assert_eq!(
@@ -366,6 +416,11 @@ mod tests {
assert!(FilterFunction::parse_str("blur(42 43)").is_err());
}
+ #[test]
+ fn invalid_grayscale_yields_error() {
+ assert!(FilterFunction::parse_str("grayscale(foo)").is_err());
+ }
+
#[test]
fn invalid_opacity_yields_error() {
assert!(FilterFunction::parse_str("opacity(foo)").is_err());
diff --git a/tests/src/filters.rs b/tests/src/filters.rs
index c283c74e..868bbed4 100644
--- a/tests/src/filters.rs
+++ b/tests/src/filters.rs
@@ -158,6 +158,26 @@ test_compare_render_output!(
"##,
);
+test_compare_render_output!(
+ grayscale_filter_func,
+ 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="grayscale(0.75)"/>
+</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.25" />
+ </filter>
+ </defs>
+
+ <rect x="100" y="100" width="200" height="200" fill="lime" filter="url(#filter)"/>
+</svg>
+"##,
+);
+
test_compare_render_output!(
opacity_filter_func,
br##"<?xml version="1.0" encoding="UTF-8"?>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]