[librsvg: 1/3] Implement drop-shadow filter




commit 23c67df26929d49fb61f818fc90aa231852b79ef
Author: John Ledbetter <john ledbetter gmail com>
Date:   Wed May 26 13:17:21 2021 -0400

    Implement drop-shadow filter
    
    drop-shadow accepts:
    
    * color which describes the shadow color; default value is the color
      attribute.
    * two lengths, specifying the dx and dy offset of the shadow.  Default
      if missing is zero.
    * a third length, specifying the standard deviation of the shadow
      blur.  Default if missing is zero.
    
    This requires a much more complicated set of filter primitives than
    previously implemented filters; see
    https://www.w3.org/TR/filter-effects/#dropshadowEquivalent for the set
    of primitives required.

 src/filter_func.rs       | 169 +++++++++++++++++++++++++++++++++++++++++++++--
 src/filters/composite.rs |  18 ++---
 src/filters/flood.rs     |   2 +-
 src/filters/merge.rs     |  10 +--
 src/filters/mod.rs       |  10 +--
 src/filters/offset.rs    |   6 +-
 src/parsers.rs           |   2 +-
 tests/src/filters.rs     |  27 ++++++++
 8 files changed, 214 insertions(+), 30 deletions(-)
---
diff --git a/src/filter_func.rs b/src/filter_func.rs
index a50484cc..3365b5ac 100644
--- a/src/filter_func.rs
+++ b/src/filter_func.rs
@@ -1,16 +1,24 @@
-use cssparser::Parser;
+use cssparser::{Color, Parser, RGBA};
 
+use crate::coord_units::CoordUnits;
+use crate::drawing_ctx::DrawingCtx;
 use crate::error::*;
 use crate::filter::Filter;
 use crate::filters::{
-    gaussian_blur::GaussianBlur, FilterResolveError, FilterSpec, Primitive, PrimitiveParams,
-    ResolvedPrimitive,
+    color_matrix::ColorMatrix,
+    component_transfer,
+    composite::{Composite, Operator},
+    flood::Flood,
+    gaussian_blur::GaussianBlur,
+    merge::{Merge, MergeNode},
+    offset::Offset,
+    FilterResolveError, FilterSpec, Input, Primitive, PrimitiveParams, ResolvedPrimitive,
 };
 use crate::length::*;
-use crate::parsers::{NumberOrPercentage, Parse};
+use crate::paint_server::resolve_color;
+use crate::parsers::{CustomIdent, NumberOrPercentage, Parse};
 use crate::properties::ComputedValues;
-use crate::{coord_units::CoordUnits, filters::color_matrix::ColorMatrix};
-use crate::{drawing_ctx::DrawingCtx, filters::component_transfer};
+use crate::unit_interval::UnitInterval;
 
 /// CSS Filter functions from the Filter Effects Module Level 1
 ///
@@ -20,6 +28,7 @@ pub enum FilterFunction {
     Blur(Blur),
     Brightness(Brightness),
     Contrast(Contrast),
+    DropShadow(DropShadow),
     Grayscale(Grayscale),
     Invert(Invert),
     Opacity(Opacity),
@@ -51,6 +60,17 @@ pub struct Contrast {
     proportion: Option<f64>,
 }
 
+/// Parameters for the `drop-shadow()` filter function
+///
+/// https://www.w3.org/TR/filter-effects/#funcdef-filter-drop-shadow
+#[derive(Debug, Clone, PartialEq)]
+pub struct DropShadow {
+    color: Option<Color>,
+    dx: Option<Length<Horizontal>>,
+    dy: Option<Length<Vertical>>,
+    std_deviation: Option<ULength<Both>>,
+}
+
 /// Parameters for the `grayscale()` filter function
 ///
 /// https://www.w3.org/TR/filter-effects/#funcdef-filter-grayscale
@@ -144,6 +164,21 @@ fn parse_contrast<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, Par
     Ok(FilterFunction::Contrast(Contrast { proportion }))
 }
 
+#[allow(clippy::unnecessary_wraps)]
+fn parse_dropshadow<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
+    let color = parser.try_parse(|p| Color::parse(p)).ok();
+    let dx = parser.try_parse(|p| Length::parse(p)).ok();
+    let dy = parser.try_parse(|p| Length::parse(p)).ok();
+    let std_deviation = parser.try_parse(|p| ULength::parse(p)).ok();
+
+    Ok(FilterFunction::DropShadow(DropShadow {
+        color,
+        dx,
+        dy,
+        std_deviation,
+    }))
+}
+
 #[allow(clippy::unnecessary_wraps)]
 fn parse_grayscale<'i>(parser: &mut Parser<'i, '_>) -> Result<FilterFunction, ParseError<'i>> {
     let proportion = parse_num_or_percentage_clamped(parser);
@@ -282,6 +317,83 @@ impl Contrast {
     }
 }
 
+impl DropShadow {
+    /// Converts a DropShadow into the set of filter element primitives.
+    ///
+    /// See https://www.w3.org/TR/filter-effects/#dropshadowEquivalent
+    fn to_filter_spec(&self, params: &NormalizeParams, default_color: RGBA) -> FilterSpec {
+        let user_space_filter = Filter::default().to_user_space(params);
+        let dx = self.dx.map(|l| l.to_user(params)).unwrap_or(0.0);
+        let dy = self.dy.map(|l| l.to_user(params)).unwrap_or(0.0);
+        let std_dev = self.std_deviation.map(|l| l.to_user(params)).unwrap_or(0.0);
+        let color = self
+            .color
+            .as_ref()
+            .map(|c| resolve_color(c, UnitInterval::clamp(1.0), default_color))
+            .unwrap_or(default_color);
+
+        let offsetblur = CustomIdent("offsetblur".to_string());
+
+        let gaussian_blur = ResolvedPrimitive {
+            primitive: Primitive::default(),
+            params: PrimitiveParams::GaussianBlur(GaussianBlur {
+                in1: Input::SourceAlpha,
+                std_deviation: (std_dev, std_dev),
+                ..GaussianBlur::default()
+            }),
+        }
+        .into_user_space(params);
+
+        let offset = ResolvedPrimitive {
+            primitive: Primitive {
+                result: Some(offsetblur.clone()),
+                ..Primitive::default()
+            },
+            params: PrimitiveParams::Offset(Offset {
+                in1: Input::default(),
+                dx,
+                dy,
+            }),
+        }
+        .into_user_space(params);
+
+        let flood = ResolvedPrimitive {
+            primitive: Primitive::default(),
+            params: PrimitiveParams::Flood(Flood { color }),
+        }
+        .into_user_space(params);
+
+        let composite = ResolvedPrimitive {
+            primitive: Primitive::default(),
+            params: PrimitiveParams::Composite(Composite {
+                in2: Input::FilterOutput(offsetblur),
+                operator: Operator::In,
+                ..Composite::default()
+            }),
+        }
+        .into_user_space(params);
+
+        let merge = ResolvedPrimitive {
+            primitive: Primitive::default(),
+            params: PrimitiveParams::Merge(Merge {
+                merge_nodes: vec![
+                    MergeNode::default(),
+                    MergeNode {
+                        in1: Input::SourceGraphic,
+                        ..MergeNode::default()
+                    },
+                ],
+            }),
+        }
+        .into_user_space(params);
+
+        FilterSpec {
+            user_space_filter,
+            primitives: vec![gaussian_blur, offset, flood, composite, merge],
+        }
+    }
+}
+
 impl Grayscale {
     fn to_filter_spec(&self, params: &NormalizeParams) -> FilterSpec {
         // grayscale is implemented as the inverse of a saturate operation,
@@ -434,6 +546,7 @@ impl Parse for FilterFunction {
             ("blur", &parse_blur),
             ("brightness", &parse_brightness),
             ("contrast", &parse_contrast),
+            ("drop-shadow", &parse_dropshadow),
             ("grayscale", &parse_grayscale),
             ("invert", &parse_invert),
             ("opacity", &parse_opacity),
@@ -467,6 +580,7 @@ impl FilterFunction {
             FilterFunction::Blur(v) => Ok(v.to_filter_spec(&params)),
             FilterFunction::Brightness(v) => Ok(v.to_filter_spec(&params)),
             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::Invert(v) => Ok(v.to_filter_spec(&params)),
             FilterFunction::Opacity(v) => Ok(v.to_filter_spec(&params)),
@@ -527,6 +641,49 @@ mod tests {
         );
     }
 
+    #[test]
+    fn parses_dropshadow() {
+        assert_eq!(
+            FilterFunction::parse_str("drop-shadow(4px 5px)").unwrap(),
+            FilterFunction::DropShadow(DropShadow {
+                color: None,
+                dx: Some(Length::new(4.0, LengthUnit::Px)),
+                dy: Some(Length::new(5.0, LengthUnit::Px)),
+                std_deviation: None,
+            })
+        );
+
+        assert_eq!(
+            FilterFunction::parse_str("drop-shadow(#ff0000 4px 5px 32px)").unwrap(),
+            FilterFunction::DropShadow(DropShadow {
+                color: Some(Color::RGBA(RGBA {
+                    red: 255,
+                    green: 0,
+                    blue: 0,
+                    alpha: 255
+                })),
+                dx: Some(Length::new(4.0, LengthUnit::Px)),
+                dy: Some(Length::new(5.0, LengthUnit::Px)),
+                std_deviation: Some(ULength::new(32.0, LengthUnit::Px)),
+            })
+        );
+
+        assert_eq!(
+            FilterFunction::parse_str("drop-shadow(#ff0000)").unwrap(),
+            FilterFunction::DropShadow(DropShadow {
+                color: Some(Color::RGBA(RGBA {
+                    red: 255,
+                    green: 0,
+                    blue: 0,
+                    alpha: 255
+                })),
+                dx: None,
+                dy: None,
+                std_deviation: None,
+            })
+        );
+    }
+
     #[test]
     fn parses_grayscale() {
         assert_eq!(
diff --git a/src/filters/composite.rs b/src/filters/composite.rs
index 7ffffe0d..feae2cf1 100644
--- a/src/filters/composite.rs
+++ b/src/filters/composite.rs
@@ -20,7 +20,7 @@ use super::{
 
 /// Enumeration of the possible compositing operations.
 #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
-enum Operator {
+pub enum Operator {
     Over,
     In,
     Out,
@@ -41,14 +41,14 @@ pub struct FeComposite {
 /// Resolved `feComposite` primitive for rendering.
 #[derive(Clone, Default)]
 pub struct Composite {
-    in1: Input,
-    in2: Input,
-    operator: Operator,
-    k1: f64,
-    k2: f64,
-    k3: f64,
-    k4: f64,
-    color_interpolation_filters: ColorInterpolationFilters,
+    pub in1: Input,
+    pub in2: Input,
+    pub operator: Operator,
+    pub k1: f64,
+    pub k2: f64,
+    pub k3: f64,
+    pub k4: f64,
+    pub color_interpolation_filters: ColorInterpolationFilters,
 }
 
 impl SetAttributes for FeComposite {
diff --git a/src/filters/flood.rs b/src/filters/flood.rs
index 8a305be9..a6ee723c 100644
--- a/src/filters/flood.rs
+++ b/src/filters/flood.rs
@@ -20,7 +20,7 @@ pub struct FeFlood {
 
 /// Resolved `feFlood` primitive for rendering.
 pub struct Flood {
-    color: cssparser::RGBA,
+    pub color: cssparser::RGBA,
 }
 
 impl SetAttributes for FeFlood {
diff --git a/src/filters/merge.rs b/src/filters/merge.rs
index 5458f0d6..0e4ad8a4 100644
--- a/src/filters/merge.rs
+++ b/src/filters/merge.rs
@@ -30,14 +30,14 @@ pub struct FeMergeNode {
 
 /// Resolved `feMerge` primitive for rendering.
 pub struct Merge {
-    merge_nodes: Vec<MergeNode>,
+    pub merge_nodes: Vec<MergeNode>,
 }
 
 /// Resolved `feMergeNode` for rendering.
-#[derive(Debug, PartialEq)]
-struct MergeNode {
-    in1: Input,
-    color_interpolation_filters: ColorInterpolationFilters,
+#[derive(Debug, Default, PartialEq)]
+pub struct MergeNode {
+    pub in1: Input,
+    pub color_interpolation_filters: ColorInterpolationFilters,
 }
 
 impl Default for FeMerge {
diff --git a/src/filters/mod.rs b/src/filters/mod.rs
index 802f4833..f2016fb4 100644
--- a/src/filters/mod.rs
+++ b/src/filters/mod.rs
@@ -117,11 +117,11 @@ impl PrimitiveParams {
 /// The base filter primitive node containing common properties.
 #[derive(Default, Clone)]
 pub struct Primitive {
-    x: Option<Length<Horizontal>>,
-    y: Option<Length<Vertical>>,
-    width: Option<ULength<Horizontal>>,
-    height: Option<ULength<Vertical>>,
-    result: Option<CustomIdent>,
+    pub x: Option<Length<Horizontal>>,
+    pub y: Option<Length<Vertical>>,
+    pub width: Option<ULength<Horizontal>>,
+    pub height: Option<ULength<Vertical>>,
+    pub result: Option<CustomIdent>,
 }
 
 pub struct ResolvedPrimitive {
diff --git a/src/filters/offset.rs b/src/filters/offset.rs
index cb13e6f7..119ec297 100644
--- a/src/filters/offset.rs
+++ b/src/filters/offset.rs
@@ -26,9 +26,9 @@ pub struct FeOffset {
 /// Resolved `feOffset` primitive for rendering.
 #[derive(Clone, Default)]
 pub struct Offset {
-    in1: Input,
-    dx: f64,
-    dy: f64,
+    pub in1: Input,
+    pub dx: f64,
+    pub dy: f64,
 }
 
 impl SetAttributes for FeOffset {
diff --git a/src/parsers.rs b/src/parsers.rs
index 528d0de7..8230cb44 100644
--- a/src/parsers.rs
+++ b/src/parsers.rs
@@ -221,7 +221,7 @@ macro_rules! parse_identifiers {
 
 /// https://www.w3.org/TR/css-values-4/#custom-idents
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct CustomIdent(String);
+pub struct CustomIdent(pub String);
 
 impl Parse for CustomIdent {
     fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
diff --git a/tests/src/filters.rs b/tests/src/filters.rs
index 668dec21..a3e63767 100644
--- a/tests/src/filters.rs
+++ b/tests/src/filters.rs
@@ -206,6 +206,33 @@ br##"<?xml version="1.0" encoding="UTF-8"?>
 "##,
 );
 
+test_compare_render_output!(
+    dropshadow_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="drop-shadow(#ff0000 1px 4px 6px)"/>
+</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">
+      <feGaussianBlur in="SourceAlpha" stdDeviation="6" />
+      <feOffset dx="1" dy="4" result="offsetblur" />
+      <feFlood flood-color="#ff0000" />
+      <feComposite in2="offsetblur" operator="in" />
+      <feMerge>
+        <feMergeNode />
+        <feMergeNode in="SourceGraphic" />
+      </feMerge>
+    </filter>
+  </defs>
+
+  <rect x="100" y="100" width="200" height="200" fill="green" 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]