[librsvg: 4/9] Implement `brightness()` filter function.




commit 76c1e5eccdbe2e1ae87d50b4dcf7b0661f1f15b4
Author: John Ledbetter <john ledbetter gmail com>
Date:   Wed May 19 16:05:07 2021 -0400

    Implement `brightness()` filter function.
    
    Brightness accepts an optional number-or-percentage > 0.0 which
    describes the proportion of the transformation to apply.  0% means
    completely black, values over 100% mean brighter than the input.
    
    See:
    * https://www.w3.org/TR/filter-effects/#brightnessEquivalent
    * https://www.w3.org/TR/filter-effects/#funcdef-filter-brightness

 src/filter_func.rs   | 102 ++++++++++++++++++++++++++++++++++++++++++++-------
 tests/src/filters.rs |  24 ++++++++++++
 2 files changed, 113 insertions(+), 13 deletions(-)
---
diff --git a/src/filter_func.rs b/src/filter_func.rs
index 34c9a2a6..aaa29003 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),
+    Brightness(Brightness),
     Grayscale(Grayscale),
     Invert(Invert),
     Opacity(Opacity),
@@ -33,6 +34,14 @@ pub struct Blur {
     std_deviation: Option<Length<Both>>,
 }
 
+/// Parameters for the `brightness()` filter function
+///
+/// https://www.w3.org/TR/filter-effects/#funcdef-filter-brightness
+#[derive(Debug, Clone, PartialEq)]
+pub struct Brightness {
+    proportion: Option<f64>,
+}
+
 /// Parameters for the `grayscale()` filter function
 ///
 /// https://www.w3.org/TR/filter-effects/#funcdef-filter-grayscale
@@ -73,6 +82,22 @@ pub struct Sepia {
     proportion: Option<f64>,
 }
 
+/// Reads an optional number or percentage from the parser.
+/// Negative numbers are not allowed.
+fn parse_num_or_percentage<'i>(parser: &mut Parser<'i, '_>) -> Option<f64> {
+    match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
+        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
+        Ok(NumberOrPercentage { value }) => Some(value),
+        Err(_) => None,
+    }
+}
+
+/// Reads an optional number or percentage from the parser, returning a value clamped to [0, 1].
+/// Negative numbers are not allowed.
+fn parse_num_or_percentage_clamped<'i>(parser: &mut Parser<'i, '_>) -> Option<f64> {
+    parse_num_or_percentage(parser).map(|value| value.clamp(0.0, 1.0))
+}
+
 fn parse_function<'i, F>(
     parser: &mut Parser<'i, '_>,
     name: &str,
@@ -100,20 +125,11 @@ fn parse_blur<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseEr
     }))
 }
 
-/// Reads an optional number or percentage from the parser.
-/// Negative numbers are not allowed.
-fn parse_num_or_percentage<'i>(parser: &mut Parser<'i, '_>) -> Option<f64> {
-    match parser.try_parse(|p| NumberOrPercentage::parse(p)) {
-        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
-        Ok(NumberOrPercentage { value }) => Some(value),
-        Err(_) => None,
-    }
-}
+#[allow(clippy::unnecessary_wraps)]
+fn parse_brightness<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
+    let proportion = parse_num_or_percentage(parser);
 
-/// Reads an optional number or percentage from the parser, returning a value clamped to [0, 1].
-/// Negative numbers are not allowed.
-fn parse_num_or_percentage_clamped<'i>(parser: &mut Parser<'i, '_>) -> Option<f64> {
-    parse_num_or_percentage(parser).map(|value| value.clamp(0.0, 1.0))
+    Ok(FilterFunction::Brightness(Brightness { proportion }))
 }
 
 #[allow(clippy::unnecessary_wraps)]
@@ -174,6 +190,44 @@ impl Blur {
     }
 }
 
+impl Brightness {
+    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
+        let user_space_filter = Filter::default().to_user_space(params);
+        let slope = self.proportion.unwrap_or(1.0);
+
+        let brightness = ResolvedPrimitive {
+            primitive: Primitive::default(),
+            params: PrimitiveParams::ComponentTransfer(component_transfer::ComponentTransfer {
+                functions: component_transfer::Functions {
+                    r: component_transfer::FeFuncR {
+                        function_type: component_transfer::FunctionType::Linear,
+                        slope,
+                        ..component_transfer::FeFuncR::default()
+                    },
+                    g: component_transfer::FeFuncG {
+                        function_type: component_transfer::FunctionType::Linear,
+                        slope,
+                        ..component_transfer::FeFuncG::default()
+                    },
+                    b: component_transfer::FeFuncB {
+                        function_type: component_transfer::FunctionType::Linear,
+                        slope,
+                        ..component_transfer::FeFuncB::default()
+                    },
+                    ..component_transfer::Functions::default()
+                },
+                ..component_transfer::ComponentTransfer::default()
+            }),
+        }
+        .into_user_space(params);
+
+        FilterSpec {
+            user_space_filter,
+            primitives: vec![brightness],
+        }
+    }
+}
+
 impl Grayscale {
     fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
         // grayscale is implemented as the inverse of a saturate operation,
@@ -324,6 +378,7 @@ impl Parse for FilterFunction {
         let loc = parser.current_source_location();
         let fns: Vec<(&str, &dyn Fn(&mut Parser<'i, '_>) -> _)> = vec![
             ("blur", &parse_blur),
+            ("brightness", &parse_brightness),
             ("grayscale", &parse_grayscale),
             ("invert", &parse_invert),
             ("opacity", &parse_opacity),
@@ -355,6 +410,7 @@ impl FilterFunction {
 
         match self {
             FilterFunction::Blur(v) => Ok(v.to_filter_spec(&params)),
+            FilterFunction::Brightness(v) => Ok(v.to_filter_spec(&params)),
             FilterFunction::Grayscale(v) => Ok(v.to_filter_spec(&params)),
             FilterFunction::Invert(v) => Ok(v.to_filter_spec(&params)),
             FilterFunction::Opacity(v) => Ok(v.to_filter_spec(&params)),
@@ -385,6 +441,21 @@ mod tests {
         );
     }
 
+    #[test]
+    fn parses_brightness() {
+        assert_eq!(
+            FilterFunction::parse_str("brightness()").unwrap(),
+            FilterFunction::Brightness(Brightness { proportion: None })
+        );
+
+        assert_eq!(
+            FilterFunction::parse_str("brightness(50%)").unwrap(),
+            FilterFunction::Brightness(Brightness {
+                proportion: Some(0.50_f32.into()),
+            })
+        );
+    }
+
     #[test]
     fn parses_grayscale() {
         assert_eq!(
@@ -487,6 +558,11 @@ mod tests {
         assert!(FilterFunction::parse_str("blur(42 43)").is_err());
     }
 
+    #[test]
+    fn invalid_brightness_yields_error() {
+        assert!(FilterFunction::parse_str("brightness(foo)").is_err());
+    }
+
     #[test]
     fn invalid_grayscale_yields_error() {
         assert!(FilterFunction::parse_str("grayscale(foo)").is_err());
diff --git a/tests/src/filters.rs b/tests/src/filters.rs
index 7217f127..0c59b4e6 100644
--- a/tests/src/filters.rs
+++ b/tests/src/filters.rs
@@ -158,6 +158,30 @@ test_compare_render_output!(
 "##,
 );
 
+test_compare_render_output!(
+brightness_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="brightness(125%)"/>
+</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">
+      <feComponentTransfer>
+        <feFuncR type="linear" slope="1.25" />
+        <feFuncG type="linear" slope="1.25" />
+        <feFuncB type="linear" slope="1.25" />
+      </feComponentTransfer>
+    </filter>
+  </defs>
+
+  <rect x="100" y="100" width="200" height="200" fill="lime" filter="url(#filter)"/>
+</svg>
+"##,
+);
+
 test_compare_render_output!(
     grayscale_filter_func,
     br##"<?xml version="1.0" encoding="UTF-8"?>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]