1
use markup5ever::{expanded_name, local_name, namespace_url, ns};
2

            
3
use crate::aspect_ratio::AspectRatio;
4
use crate::document::{AcquiredNodes, NodeId};
5
use crate::drawing_ctx::DrawingCtx;
6
use crate::element::{set_attribute, ElementTrait};
7
use crate::href::{is_href, set_href};
8
use crate::node::{CascadedValues, Node};
9
use crate::parsers::ParseValue;
10
use crate::properties::ComputedValues;
11
use crate::rect::Rect;
12
use crate::rsvg_log;
13
use crate::session::Session;
14
use crate::surface_utils::shared_surface::{Interpolation, SharedImageSurface};
15
use crate::viewbox::ViewBox;
16
use crate::xml::Attributes;
17

            
18
use super::bounds::{Bounds, BoundsBuilder};
19
use super::context::{FilterContext, FilterOutput};
20
use super::{
21
    FilterEffect, FilterError, FilterResolveError, Primitive, PrimitiveParams, ResolvedPrimitive,
22
};
23

            
24
/// The `feImage` filter primitive.
25
72
#[derive(Default)]
26
pub struct FeImage {
27
72
    base: Primitive,
28
72
    params: ImageParams,
29
}
30

            
31
144
#[derive(Clone, Default)]
32
struct ImageParams {
33
72
    aspect: AspectRatio,
34
72
    href: Option<String>,
35
}
36

            
37
/// Resolved `feImage` primitive for rendering.
38
pub struct Image {
39
    aspect: AspectRatio,
40
    source: Source,
41
    feimage_values: Box<ComputedValues>,
42
}
43

            
44
/// What a feImage references for rendering.
45
enum Source {
46
    /// Nothing is referenced; ignore the filter.
47
    None,
48

            
49
    /// Reference to a node.
50
    Node(Node, String),
51

            
52
    /// Reference to an external image.  This is just a URL.
53
    ExternalImage(String),
54
}
55

            
56
impl Image {
57
    /// Renders the filter if the source is an existing node.
58
25
    fn render_node(
59
        &self,
60
        ctx: &FilterContext,
61
        acquired_nodes: &mut AcquiredNodes<'_>,
62
        draw_ctx: &mut DrawingCtx,
63
        bounds: Rect,
64
        referenced_node: &Node,
65
    ) -> Result<SharedImageSurface, FilterError> {
66
        // https://www.w3.org/TR/filter-effects/#feImageElement
67
        //
68
        // The filters spec says, "... otherwise [rendering a referenced object], the
69
        // referenced resource is rendered according to the behavior of the use element."
70
        // I think this means that we use the same cascading mode as <use>, i.e. the
71
        // referenced object inherits its properties from the feImage element.
72
        let cascaded =
73
25
            CascadedValues::new_from_values(referenced_node, &self.feimage_values, None, None);
74

            
75
25
        let interpolation = Interpolation::from(self.feimage_values.image_rendering());
76

            
77
25
        let image = draw_ctx.draw_node_to_surface(
78
            referenced_node,
79
            acquired_nodes,
80
            &cascaded,
81
25
            ctx.paffine(),
82
25
            ctx.source_graphic().width(),
83
25
            ctx.source_graphic().height(),
84
        )?;
85

            
86
25
        let surface = ctx
87
            .source_graphic()
88
25
            .paint_image(bounds, &image, None, interpolation)?;
89

            
90
25
        Ok(surface)
91
25
    }
92

            
93
    /// Renders the filter if the source is an external image.
94
41
    fn render_external_image(
95
        &self,
96
        ctx: &FilterContext,
97
        acquired_nodes: &mut AcquiredNodes<'_>,
98
        _draw_ctx: &DrawingCtx,
99
        bounds: &Bounds,
100
        url: &str,
101
    ) -> Result<SharedImageSurface, FilterError> {
102
        // FIXME: translate the error better here
103
41
        let image = acquired_nodes
104
            .lookup_image(url)
105
            .map_err(|_| FilterError::InvalidInput)?;
106

            
107
82
        let rect = self.aspect.compute(
108
41
            &ViewBox::from(Rect::from_size(
109
41
                f64::from(image.width()),
110
41
                f64::from(image.height()),
111
            )),
112
41
            &bounds.unclipped,
113
        );
114

            
115
41
        let interpolation = Interpolation::from(self.feimage_values.image_rendering());
116

            
117
        let surface =
118
41
            ctx.source_graphic()
119
41
                .paint_image(bounds.clipped, &image, Some(rect), interpolation)?;
120

            
121
41
        Ok(surface)
122
41
    }
123
}
124

            
125
impl ElementTrait for FeImage {
126
72
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
127
72
        self.base.parse_no_inputs(attrs, session);
128

            
129
234
        for (attr, value) in attrs.iter() {
130
162
            match attr.expanded() {
131
                expanded_name!("", "preserveAspectRatio") => {
132
19
                    set_attribute(&mut self.params.aspect, attr.parse(value), session);
133
                }
134

            
135
                // "path" is used by some older Adobe Illustrator versions
136
143
                ref a if is_href(a) || *a == expanded_name!("", "path") => {
137
72
                    set_href(a, &mut self.params.href, Some(value.to_string()));
138
                }
139

            
140
                _ => (),
141
            }
142
162
        }
143
72
    }
144
}
145

            
146
impl Image {
147
67
    pub fn render(
148
        &self,
149
        bounds_builder: BoundsBuilder,
150
        ctx: &FilterContext,
151
        acquired_nodes: &mut AcquiredNodes<'_>,
152
        draw_ctx: &mut DrawingCtx,
153
    ) -> Result<FilterOutput, FilterError> {
154
67
        let bounds = bounds_builder.compute(ctx);
155

            
156
67
        let surface = match &self.source {
157
            Source::None => return Err(FilterError::InvalidInput),
158

            
159
26
            Source::Node(node, ref name) => {
160
26
                if let Ok(acquired) = acquired_nodes.acquire_ref(node) {
161
25
                    rsvg_log!(draw_ctx.session(), "(feImage \"{}\"", name);
162
25
                    let res = self.render_node(
163
                        ctx,
164
                        acquired_nodes,
165
                        draw_ctx,
166
25
                        bounds.clipped,
167
25
                        acquired.get(),
168
25
                    );
169
25
                    rsvg_log!(draw_ctx.session(), ")");
170
25
                    res?
171
25
                } else {
172
1
                    return Err(FilterError::InvalidInput);
173
                }
174
26
            }
175

            
176
41
            Source::ExternalImage(ref href) => {
177
41
                self.render_external_image(ctx, acquired_nodes, draw_ctx, &bounds, href)?
178
41
            }
179
        };
180

            
181
66
        Ok(FilterOutput {
182
66
            surface,
183
66
            bounds: bounds.clipped.into(),
184
        })
185
67
    }
186
}
187

            
188
impl FilterEffect for FeImage {
189
67
    fn resolve(
190
        &self,
191
        acquired_nodes: &mut AcquiredNodes<'_>,
192
        node: &Node,
193
    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
194
67
        let cascaded = CascadedValues::new_from_node(node);
195
67
        let feimage_values = cascaded.get().clone();
196

            
197
67
        let source = match self.params.href {
198
            None => Source::None,
199

            
200
67
            Some(ref s) => {
201
67
                if let Ok(node_id) = NodeId::parse(s) {
202
26
                    acquired_nodes
203
                        .acquire(&node_id)
204
52
                        .map(|acquired| Source::Node(acquired.get().clone(), s.clone()))
205
26
                        .unwrap_or(Source::None)
206
26
                } else {
207
41
                    Source::ExternalImage(s.to_string())
208
                }
209
67
            }
210
        };
211

            
212
134
        Ok(vec![ResolvedPrimitive {
213
67
            primitive: self.base.clone(),
214
67
            params: PrimitiveParams::Image(Image {
215
67
                aspect: self.params.aspect,
216
67
                source,
217
67
                feimage_values: Box::new(feimage_values),
218
            }),
219
        }])
220
67
    }
221
}