rsvg/filters/
merge.rs

1use markup5ever::{expanded_name, local_name, ns};
2
3use crate::document::AcquiredNodes;
4use crate::element::{set_attribute, ElementData, ElementTrait};
5use crate::node::{CascadedValues, Node, NodeBorrow};
6use crate::parsers::ParseValue;
7use crate::properties::ColorInterpolationFilters;
8use crate::rect::IRect;
9use crate::session::Session;
10use crate::surface_utils::shared_surface::{Operator, SharedImageSurface, SurfaceType};
11use crate::xml::Attributes;
12
13use super::bounds::BoundsBuilder;
14use super::context::{FilterContext, FilterOutput};
15use super::{
16    FilterEffect, FilterError, FilterResolveError, Input, InputRequirements, Primitive,
17    PrimitiveParams, ResolvedPrimitive,
18};
19
20/// The `feMerge` filter primitive.
21pub struct FeMerge {
22    base: Primitive,
23}
24
25/// The `<feMergeNode>` element.
26#[derive(Clone, Default)]
27pub struct FeMergeNode {
28    in1: Input,
29}
30
31/// Resolved `feMerge` primitive for rendering.
32pub struct Merge {
33    pub merge_nodes: Vec<MergeNode>,
34}
35
36/// Resolved `feMergeNode` for rendering.
37#[derive(Debug, Default, PartialEq)]
38pub struct MergeNode {
39    pub in1: Input,
40    pub color_interpolation_filters: ColorInterpolationFilters,
41}
42
43impl Default for FeMerge {
44    /// Constructs a new `Merge` with empty properties.
45    #[inline]
46    fn default() -> FeMerge {
47        FeMerge {
48            base: Default::default(),
49        }
50    }
51}
52
53impl ElementTrait for FeMerge {
54    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
55        self.base.parse_no_inputs(attrs, session);
56    }
57}
58
59impl ElementTrait for FeMergeNode {
60    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
61        for (attr, value) in attrs.iter() {
62            if let expanded_name!("", "in") = attr.expanded() {
63                set_attribute(&mut self.in1, attr.parse(value), session);
64            }
65        }
66    }
67}
68
69impl MergeNode {
70    fn render(
71        &self,
72        ctx: &FilterContext,
73        bounds: IRect,
74        output_surface: Option<SharedImageSurface>,
75    ) -> Result<SharedImageSurface, FilterError> {
76        let input = ctx.get_input(&self.in1, self.color_interpolation_filters)?;
77
78        if output_surface.is_none() {
79            return Ok(input.surface().clone());
80        }
81
82        input
83            .surface()
84            .compose(&output_surface.unwrap(), bounds, Operator::Over)
85            .map_err(FilterError::CairoError)
86    }
87}
88
89impl Merge {
90    pub fn render(
91        &self,
92        bounds_builder: BoundsBuilder,
93        ctx: &FilterContext,
94    ) -> Result<FilterOutput, FilterError> {
95        // Compute the filter bounds, taking each feMergeNode's input into account.
96        let mut bounds_builder = bounds_builder;
97        for merge_node in &self.merge_nodes {
98            let input = ctx.get_input(&merge_node.in1, merge_node.color_interpolation_filters)?;
99            bounds_builder = bounds_builder.add_input(&input);
100        }
101
102        let bounds: IRect = bounds_builder.compute(ctx).clipped.into();
103
104        // Now merge them all.
105        let mut output_surface = None;
106        for merge_node in &self.merge_nodes {
107            output_surface = merge_node.render(ctx, bounds, output_surface).ok();
108        }
109
110        let surface = match output_surface {
111            Some(s) => s,
112            None => SharedImageSurface::empty(
113                ctx.source_graphic().width(),
114                ctx.source_graphic().height(),
115                SurfaceType::AlphaOnly,
116            )?,
117        };
118
119        Ok(FilterOutput { surface, bounds })
120    }
121
122    pub fn get_input_requirements(&self) -> InputRequirements {
123        self.merge_nodes
124            .iter()
125            .map(|mn| mn.in1.get_requirements())
126            .fold(InputRequirements::default(), |a, b| a.fold(b))
127    }
128}
129
130impl FilterEffect for FeMerge {
131    fn resolve(
132        &self,
133        _acquired_nodes: &mut AcquiredNodes<'_>,
134        node: &Node,
135    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
136        Ok(vec![ResolvedPrimitive {
137            primitive: self.base.clone(),
138            params: PrimitiveParams::Merge(Merge {
139                merge_nodes: resolve_merge_nodes(node)?,
140            }),
141        }])
142    }
143}
144
145/// Takes a feMerge and walks its children to produce a list of feMergeNode arguments.
146fn resolve_merge_nodes(node: &Node) -> Result<Vec<MergeNode>, FilterResolveError> {
147    let mut merge_nodes = Vec::new();
148
149    for child in node.children().filter(|c| c.is_element()) {
150        let cascaded = CascadedValues::new_from_node(&child);
151        let values = cascaded.get();
152
153        if let ElementData::FeMergeNode(merge_node) = &*child.borrow_element_data() {
154            merge_nodes.push(MergeNode {
155                in1: merge_node.in1.clone(),
156                color_interpolation_filters: values.color_interpolation_filters(),
157            });
158        }
159    }
160
161    Ok(merge_nodes)
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    use crate::borrow_element_as;
169    use crate::document::Document;
170
171    #[test]
172    fn extracts_parameters() {
173        let document = Document::load_from_bytes(
174            br#"<?xml version="1.0" encoding="UTF-8"?>
175<svg xmlns="http://www.w3.org/2000/svg">
176  <filter id="filter">
177    <feMerge id="merge">
178      <feMergeNode in="SourceGraphic"/>
179      <feMergeNode in="SourceAlpha" color-interpolation-filters="sRGB"/>
180    </feMerge>
181  </filter>
182</svg>
183"#,
184        );
185        let mut acquired_nodes = AcquiredNodes::new(&document, None::<gio::Cancellable>);
186
187        let node = document.lookup_internal_node("merge").unwrap();
188        let merge = borrow_element_as!(node, FeMerge);
189        let resolved = merge.resolve(&mut acquired_nodes, &node).unwrap();
190        let ResolvedPrimitive { params, .. } = resolved.first().unwrap();
191        let params = match params {
192            PrimitiveParams::Merge(m) => m,
193            _ => unreachable!(),
194        };
195        assert_eq!(
196            &params.merge_nodes[..],
197            vec![
198                MergeNode {
199                    in1: Input::SourceGraphic,
200                    color_interpolation_filters: Default::default(),
201                },
202                MergeNode {
203                    in1: Input::SourceAlpha,
204                    color_interpolation_filters: ColorInterpolationFilters::Srgb,
205                },
206            ]
207        );
208    }
209}