[librsvg: 2/3] Implement Sepia filter




commit 28d9eeea97a61864b1f95c05992ce56e90436eca
Author: John Ledbetter <john ledbetter gmail com>
Date:   Fri May 7 10:34:12 2021 -0400

    Implement Sepia filter
    
    The sepia filter takes a single optional argument, which is the
    proportion of sepia to apply.  This can be provided as either a value
    between 0.0 and 1.0, or a percentage 0-100%.
    
    The default value if none is given is 1.0.  If a value greater than
    1.0 is provided, it is treated as 1.0.  If a value less than zero is
    given, the default value is used.
    
    See:
    
    * https://www.w3.org/TR/filter-effects/#sepiaEquivalent
    * https://www.w3.org/TR/filter-effects/#funcdef-filter-sepia

 src/filter_func.rs          | 103 +++++++++++++++++++++++++++++++++++++++++++-
 src/filters/color_matrix.rs |   6 +--
 tests/src/filters.rs        |  61 ++++++++++++++++++++++++++
 3 files changed, 165 insertions(+), 5 deletions(-)
---
diff --git a/src/filter_func.rs b/src/filter_func.rs
index fe9f75fa..a0ebeea1 100644
--- a/src/filter_func.rs
+++ b/src/filter_func.rs
@@ -1,6 +1,5 @@
 use cssparser::Parser;
 
-use crate::coord_units::CoordUnits;
 use crate::drawing_ctx::DrawingCtx;
 use crate::error::*;
 use crate::filter::Filter;
@@ -9,8 +8,9 @@ use crate::filters::{
     ResolvedPrimitive,
 };
 use crate::length::*;
-use crate::parsers::Parse;
+use crate::parsers::{NumberOrPercentage, Parse};
 use crate::properties::ComputedValues;
+use crate::{coord_units::CoordUnits, filters::color_matrix::ColorMatrix};
 
 /// CSS Filter functions from the Filter Effects Module Level 1
 ///
@@ -18,6 +18,7 @@ use crate::properties::ComputedValues;
 #[derive(Debug, Clone, PartialEq)]
 pub enum FilterFunction {
     Blur(Blur),
+    Sepia(Sepia),
 }
 
 /// Parameters for the `blur()` filter function
@@ -28,6 +29,14 @@ pub struct Blur {
     std_deviation: Option<Length<Both>>,
 }
 
+/// Parameters for the `sepia()` filter function
+///
+/// https://www.w3.org/TR/filter-effects/#funcdef-filter-sepia
+#[derive(Debug, Clone, PartialEq)]
+pub struct Sepia {
+    proportion: Option<f64>,
+}
+
 fn parse_function<'i, F>(
     parser: &mut Parser<'i, '_>,
     name: &str,
@@ -55,6 +64,17 @@ fn parse_blur<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseEr
     }))
 }
 
+#[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)) {
+        Ok(NumberOrPercentage { value }) if value < 0.0 => None,
+        Ok(NumberOrPercentage { value }) => Some(value.clamp(0.0, 1.0)),
+        Err(_) => None,
+    };
+
+    Ok(FilterFunction::Sepia(Sepia { proportion }))
+}
+
 impl Blur {
     fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
         // The 0.0 default is from the spec
@@ -78,6 +98,39 @@ impl Blur {
     }
 }
 
+impl Sepia {
+    #[rustfmt::skip]
+    fn matrix(&self) -> nalgebra::Matrix5<f64> {
+        let p = self.proportion.unwrap_or(1.0);
+
+        nalgebra::Matrix5::new(
+            0.393 + 0.607 * (1.0 - p), 0.769 - 0.769 * (1.0 - p), 0.189 - 0.189 * (1.0 - p), 0.0, 0.0,
+            0.349 - 0.349 * (1.0 - p), 0.686 + 0.314 * (1.0 - p), 0.168 - 0.168 * (1.0 - p), 0.0, 0.0,
+            0.272 - 0.272 * (1.0 - p), 0.534 - 0.534 * (1.0 - p), 0.131 + 0.869 * (1.0 - 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 sepia = ResolvedPrimitive {
+            primitive: Primitive::default(),
+            params: PrimitiveParams::ColorMatrix(ColorMatrix {
+                matrix: self.matrix(),
+                ..ColorMatrix::default()
+            }),
+        }
+        .into_user_space(params);
+
+        FilterSpec {
+            user_space_filter,
+            primitives: vec![sepia],
+        }
+    }
+}
+
 impl Parse for FilterFunction {
     fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, crate::error::ParseError<'i>> {
         let loc = parser.current_source_location();
@@ -86,6 +139,10 @@ impl Parse for FilterFunction {
             return Ok(func);
         }
 
+        if let Ok(func) = parser.try_parse(|p| parse_function(p, "sepia", parse_sepia)) {
+            return Ok(func);
+        }
+
         return Err(loc.new_custom_error(ValueErrorKind::parse_error("expected filter function")));
     }
 }
@@ -104,6 +161,7 @@ impl FilterFunction {
 
         match self {
             FilterFunction::Blur(v) => Ok(v.to_filter_spec(&params)),
+            FilterFunction::Sepia(v) => Ok(v.to_filter_spec(&params)),
         }
     }
 }
@@ -129,9 +187,50 @@ mod tests {
         );
     }
 
+    #[test]
+    fn parses_sepia() {
+        assert_eq!(
+            FilterFunction::parse_str("sepia()").unwrap(),
+            FilterFunction::Sepia(Sepia { proportion: None })
+        );
+
+        assert_eq!(
+            FilterFunction::parse_str("sepia(80%)").unwrap(),
+            FilterFunction::Sepia(Sepia {
+                proportion: Some(0.80_f32.into())
+            })
+        );
+
+        assert_eq!(
+            FilterFunction::parse_str("sepia(0.52)").unwrap(),
+            FilterFunction::Sepia(Sepia {
+                proportion: Some(0.52_f32.into())
+            })
+        );
+
+        // values > 1.0 should be clamped to 1.0
+        assert_eq!(
+            FilterFunction::parse_str("sepia(1.5)").unwrap(),
+            FilterFunction::Sepia(Sepia {
+                proportion: Some(1.0)
+            })
+        );
+
+        // negative numbers are invalid.
+        assert_eq!(
+            FilterFunction::parse_str("sepia(-1)").unwrap(),
+            FilterFunction::Sepia(Sepia { proportion: None }),
+        );
+    }
+
     #[test]
     fn invalid_blur_yields_error() {
         assert!(FilterFunction::parse_str("blur(foo)").is_err());
         assert!(FilterFunction::parse_str("blur(42 43)").is_err());
     }
+
+    #[test]
+    fn invalid_sepia_yields_error() {
+        assert!(FilterFunction::parse_str("sepia(foo)").is_err());
+    }
 }
diff --git a/src/filters/color_matrix.rs b/src/filters/color_matrix.rs
index ac5f937b..030d576b 100644
--- a/src/filters/color_matrix.rs
+++ b/src/filters/color_matrix.rs
@@ -44,9 +44,9 @@ pub struct FeColorMatrix {
 /// Resolved `feColorMatrix` primitive for rendering.
 #[derive(Clone)]
 pub struct ColorMatrix {
-    in1: Input,
-    matrix: Matrix5<f64>,
-    color_interpolation_filters: ColorInterpolationFilters,
+    pub in1: Input,
+    pub matrix: Matrix5<f64>,
+    pub color_interpolation_filters: ColorInterpolationFilters,
 }
 
 impl Default for ColorMatrix {
diff --git a/tests/src/filters.rs b/tests/src/filters.rs
index fce32516..ca4a8fa4 100644
--- a/tests/src/filters.rs
+++ b/tests/src/filters.rs
@@ -155,3 +155,64 @@ fn blur_filter_func() {
         .compare(&output_surf)
         .evaluate(&output_surf, "blur_filter_func");
 }
+
+#[test]
+fn sepia_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="sepia(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="matrix"
+         values="0.5447500000000001 0.57675 0.14175 0 0
+                 0.26175 0.7645000000000001 0.126 0 0
+                 0.20400000000000001 0.4005 0.34825 0 0
+                 0 0 0 1 0"/>
+    </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, "sepia_filter_func");
+}


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