[librsvg: 1/3] Implement drop-shadow filter
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 1/3] Implement drop-shadow filter
- Date: Sat, 29 May 2021 00:23:04 +0000 (UTC)
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(¶ms)),
FilterFunction::Brightness(v) => Ok(v.to_filter_spec(¶ms)),
FilterFunction::Contrast(v) => Ok(v.to_filter_spec(¶ms)),
+ FilterFunction::DropShadow(v) => Ok(v.to_filter_spec(¶ms, values.color().0)),
FilterFunction::Grayscale(v) => Ok(v.to_filter_spec(¶ms)),
FilterFunction::Invert(v) => Ok(v.to_filter_spec(¶ms)),
FilterFunction::Opacity(v) => Ok(v.to_filter_spec(¶ms)),
@@ -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]