[librsvg: 1/2] implement hue-rotate filter




commit 83e731b583b86ffe9fb813b9f23411c9e9d1b3b5
Author: John Ledbetter <john ledbetter gmail com>
Date:   Tue Jun 1 11:15:51 2021 -0400

    implement hue-rotate filter
    
    hue-rotate() takes a single optional argument which is either a
    literal 0 or an angle; the default value is zero degrees.

 src/filter_func.rs          | 67 +++++++++++++++++++++++++++++++++++++++++++++
 src/filters/color_matrix.rs | 50 ++++++++++++++++-----------------
 tests/src/filters.rs        | 20 ++++++++++++++
 3 files changed, 111 insertions(+), 26 deletions(-)
---
diff --git a/src/filter_func.rs b/src/filter_func.rs
index 593d7c65..ea98731f 100644
--- a/src/filter_func.rs
+++ b/src/filter_func.rs
@@ -16,6 +16,7 @@
 
 use cssparser::{Color, Parser, RGBA};
 
+use crate::angle::Angle;
 use crate::coord_units::CoordUnits;
 use crate::drawing_ctx::DrawingCtx;
 use crate::error::*;
@@ -46,6 +47,7 @@ pub enum FilterFunction {
     Contrast(Contrast),
     DropShadow(DropShadow),
     Grayscale(Grayscale),
+    HueRotate(HueRotate),
     Invert(Invert),
     Opacity(Opacity),
     Saturate(Saturate),
@@ -95,6 +97,14 @@ pub struct Grayscale {
     proportion: Option<f64>,
 }
 
+/// Parameters for the `hue-rotate()` filter function
+///
+/// https://www.w3.org/TR/filter-effects/#funcdef-filter-huerotate
+#[derive(Debug, Clone, PartialEq)]
+pub struct HueRotate {
+    angle: Option<Angle>,
+}
+
 /// Parameters for the `invert()` filter function
 ///
 /// https://www.w3.org/TR/filter-effects/#funcdef-filter-invert
@@ -222,6 +232,13 @@ fn parse_grayscale<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, Pa
     Ok(FilterFunction::Grayscale(Grayscale { proportion }))
 }
 
+#[allow(clippy::unnecessary_wraps)]
+fn parse_huerotate<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
+    let angle = parser.try_parse(|p| Angle::parse(p)).ok();
+
+    Ok(FilterFunction::HueRotate(HueRotate { angle }))
+}
+
 #[allow(clippy::unnecessary_wraps)]
 fn parse_invert<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
     let proportion = parse_num_or_percentage_clamped(parser);
@@ -443,6 +460,27 @@ impl Grayscale {
     }
 }
 
+impl HueRotate {
+    fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
+        let rads = self.angle.map(|a| a.radians()).unwrap_or(0.0);
+        let user_space_filter = Filter::default().to_user_space(params);
+
+        let huerotate = ResolvedPrimitive {
+            primitive: Primitive::default(),
+            params: PrimitiveParams::ColorMatrix(ColorMatrix {
+                matrix: ColorMatrix::hue_rotate_matrix(rads),
+                ..ColorMatrix::default()
+            }),
+        }
+        .into_user_space(params);
+
+        FilterSpec {
+            user_space_filter,
+            primitives: vec![huerotate],
+        }
+    }
+}
+
 impl Invert {
     fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
         let p = self.proportion.unwrap_or(1.0);
@@ -584,6 +622,7 @@ impl Parse for FilterFunction {
             ("contrast", &parse_contrast),
             ("drop-shadow", &parse_dropshadow),
             ("grayscale", &parse_grayscale),
+            ("hue-rotate", &parse_huerotate),
             ("invert", &parse_invert),
             ("opacity", &parse_opacity),
             ("saturate", &parse_saturate),
@@ -618,6 +657,7 @@ impl FilterFunction {
             FilterFunction::Contrast(v) => Ok(v.to_filter_spec(&params)),
             FilterFunction::DropShadow(v) => Ok(v.to_filter_spec(&params, values.color().0)),
             FilterFunction::Grayscale(v) => Ok(v.to_filter_spec(&params)),
+            FilterFunction::HueRotate(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)),
             FilterFunction::Saturate(v) => Ok(v.to_filter_spec(&params)),
@@ -755,6 +795,28 @@ mod tests {
         );
     }
 
+    #[test]
+    fn parses_huerotate() {
+        assert_eq!(
+            FilterFunction::parse_str("hue-rotate()").unwrap(),
+            FilterFunction::HueRotate(HueRotate { angle: None })
+        );
+
+        assert_eq!(
+            FilterFunction::parse_str("hue-rotate(0)").unwrap(),
+            FilterFunction::HueRotate(HueRotate {
+                angle: Some(Angle::new(0.0))
+            })
+        );
+
+        assert_eq!(
+            FilterFunction::parse_str("hue-rotate(128deg)").unwrap(),
+            FilterFunction::HueRotate(HueRotate {
+                angle: Some(Angle::from_degrees(128.0))
+            })
+        );
+    }
+
     #[test]
     fn parses_invert() {
         assert_eq!(
@@ -865,6 +927,11 @@ mod tests {
         assert!(FilterFunction::parse_str("grayscale(foo)").is_err());
     }
 
+    #[test]
+    fn invalid_huerotate_yields_error() {
+        assert!(FilterFunction::parse_str("hue-rotate(foo)").is_err());
+    }
+
     #[test]
     fn invalid_invert_yields_error() {
         assert!(FilterFunction::parse_str("invert(foo)").is_err());
diff --git a/src/filters/color_matrix.rs b/src/filters/color_matrix.rs
index 030d576b..ee372481 100644
--- a/src/filters/color_matrix.rs
+++ b/src/filters/color_matrix.rs
@@ -114,32 +114,7 @@ impl SetAttributes for FeColorMatrix {
                     }
                     OperationType::HueRotate => {
                         let degrees: f64 = attr.parse(value)?;
-                        let (sin, cos) = degrees.to_radians().sin_cos();
-
-                        let a = Matrix3::new(
-                            0.213, 0.715, 0.072,
-                            0.213, 0.715, 0.072,
-                            0.213, 0.715, 0.072,
-                        );
-
-                        let b = Matrix3::new(
-                             0.787, -0.715, -0.072,
-                            -0.213,  0.285, -0.072,
-                            -0.213, -0.715,  0.928,
-                        );
-
-                        let c = Matrix3::new(
-                            -0.213, -0.715,  0.928,
-                             0.143,  0.140, -0.283,
-                            -0.787,  0.715,  0.072,
-                        );
-
-                        let top_left = a + b * cos + c * sin;
-
-                        let mut matrix = top_left.fixed_resize(0.0);
-                        matrix[(3, 3)] = 1.0;
-                        matrix[(4, 4)] = 1.0;
-                        matrix
+                        ColorMatrix::hue_rotate_matrix(degrees.to_radians())
                     }
                 };
 
@@ -215,6 +190,29 @@ impl ColorMatrix {
             bounds,
         })
     }
+
+    pub fn hue_rotate_matrix(radians: f64) -> Matrix5<f64> {
+        let (sin, cos) = radians.sin_cos();
+
+        let a = Matrix3::new(
+            0.213, 0.715, 0.072, 0.213, 0.715, 0.072, 0.213, 0.715, 0.072,
+        );
+
+        let b = Matrix3::new(
+            0.787, -0.715, -0.072, -0.213, 0.285, -0.072, -0.213, -0.715, 0.928,
+        );
+
+        let c = Matrix3::new(
+            -0.213, -0.715, 0.928, 0.143, 0.140, -0.283, -0.787, 0.715, 0.072,
+        );
+
+        let top_left = a + b * cos + c * sin;
+
+        let mut matrix = top_left.fixed_resize(0.0);
+        matrix[(3, 3)] = 1.0;
+        matrix[(4, 4)] = 1.0;
+        matrix
+    }
 }
 
 impl FilterEffect for FeColorMatrix {
diff --git a/tests/src/filters.rs b/tests/src/filters.rs
index a3e63767..b698bd13 100644
--- a/tests/src/filters.rs
+++ b/tests/src/filters.rs
@@ -253,6 +253,26 @@ test_compare_render_output!(
 "##,
 );
 
+test_compare_render_output!(
+    huerotate_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="green" filter="hue-rotate(128deg)"/>
+</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="hueRotate" values="128" />
+    </filter>
+  </defs>
+
+  <rect x="100" y="100" width="200" height="200" fill="green" filter="url(#filter)"/>
+</svg>
+"##,
+);
+
 test_compare_render_output!(
     invert_filter_func,
     br##"<?xml version="1.0" encoding="UTF-8"?>


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