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

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

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

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

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

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

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

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

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

            
57
impl ElementTrait for FeImage {
58
74
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
59
74
        self.base.parse_no_inputs(attrs, session);
60

            
61
238
        for (attr, value) in attrs.iter() {
62
164
            match attr.expanded() {
63
                expanded_name!("", "preserveAspectRatio") => {
64
19
                    set_attribute(&mut self.params.aspect, attr.parse(value), session);
65
                }
66

            
67
                // "path" is used by some older Adobe Illustrator versions
68
145
                ref a if is_href(a) || *a == expanded_name!("", "path") => {
69
74
                    set_href(a, &mut self.params.href, Some(value.to_string()));
70
                }
71

            
72
                _ => (),
73
            }
74
164
        }
75
74
    }
76
}
77

            
78
impl Image {
79
68
    pub fn render(
80
        &self,
81
        bounds_builder: BoundsBuilder,
82
        ctx: &FilterContext,
83
        acquired_nodes: &mut AcquiredNodes<'_>,
84
        draw_ctx: &mut DrawingCtx,
85
    ) -> Result<FilterOutput, FilterError> {
86
68
        let bounds = bounds_builder.compute(ctx);
87

            
88
68
        let surface = match &self.source {
89
            Source::None => return Err(FilterError::InvalidInput),
90

            
91
26
            Source::Node(node, ref name) => {
92
26
                if let Ok(acquired) = acquired_nodes.acquire_ref(node) {
93
25
                    rsvg_log!(draw_ctx.session(), "(feImage \"{}\"", name);
94
25
                    let res = self.render_node(
95
                        ctx,
96
                        acquired_nodes,
97
                        draw_ctx,
98
25
                        bounds.clipped,
99
25
                        acquired.get(),
100
25
                    );
101
25
                    rsvg_log!(draw_ctx.session(), ")");
102
25
                    res?
103
25
                } else {
104
1
                    return Err(FilterError::InvalidInput);
105
                }
106
26
            }
107

            
108
42
            Source::ExternalImage(ref href) => {
109
42
                self.render_external_image(ctx, acquired_nodes, draw_ctx, &bounds, href)?
110
42
            }
111
        };
112

            
113
67
        Ok(FilterOutput {
114
67
            surface,
115
67
            bounds: bounds.clipped.into(),
116
        })
117
68
    }
118

            
119
    /// Renders the filter if the source is an existing node.
120
25
    fn render_node(
121
        &self,
122
        ctx: &FilterContext,
123
        acquired_nodes: &mut AcquiredNodes<'_>,
124
        draw_ctx: &mut DrawingCtx,
125
        bounds: Rect,
126
        referenced_node: &Node,
127
    ) -> Result<SharedImageSurface, FilterError> {
128
        // https://www.w3.org/TR/filter-effects/#feImageElement
129
        //
130
        // The filters spec says, "... otherwise [rendering a referenced object], the
131
        // referenced resource is rendered according to the behavior of the use element."
132
        // I think this means that we use the same cascading mode as <use>, i.e. the
133
        // referenced object inherits its properties from the feImage element.
134
        let cascaded =
135
25
            CascadedValues::new_from_values(referenced_node, &self.feimage_values, None, None);
136

            
137
25
        let interpolation = Interpolation::from(self.feimage_values.image_rendering());
138

            
139
25
        let image = draw_ctx.draw_node_to_surface(
140
            referenced_node,
141
            acquired_nodes,
142
            &cascaded,
143
25
            ctx.paffine(),
144
25
            ctx.source_graphic().width(),
145
25
            ctx.source_graphic().height(),
146
        )?;
147

            
148
25
        let surface = ctx
149
            .source_graphic()
150
25
            .paint_image(bounds, &image, None, interpolation)?;
151

            
152
25
        Ok(surface)
153
25
    }
154

            
155
    /// Renders the filter if the source is an external image.
156
42
    fn render_external_image(
157
        &self,
158
        ctx: &FilterContext,
159
        acquired_nodes: &mut AcquiredNodes<'_>,
160
        draw_ctx: &DrawingCtx,
161
        bounds: &Bounds,
162
        url: &str,
163
    ) -> Result<SharedImageSurface, FilterError> {
164
42
        match acquired_nodes.lookup_resource(url) {
165
41
            Ok(Resource::Image(surface)) => {
166
41
                self.render_surface_from_raster_image(&surface, ctx, bounds)
167
41
            }
168

            
169
1
            Ok(Resource::Document(document)) => {
170
1
                self.render_surface_from_svg(&document, ctx, bounds, draw_ctx)
171
1
            }
172

            
173
            Err(e) => {
174
                rsvg_log!(
175
                    draw_ctx.session(),
176
                    "could not load image \"{}\" for feImage: {}",
177
                    url,
178
                    e
179
                );
180
                Err(FilterError::InvalidInput)
181
            }
182
        }
183
42
    }
184

            
185
42
    fn render_surface_from_raster_image(
186
        &self,
187
        image: &SharedImageSurface,
188
        ctx: &FilterContext,
189
        bounds: &Bounds,
190
    ) -> Result<SharedImageSurface, FilterError> {
191
84
        let rect = self.aspect.compute(
192
42
            &ViewBox::from(Rect::from_size(
193
42
                f64::from(image.width()),
194
42
                f64::from(image.height()),
195
            )),
196
42
            &bounds.unclipped,
197
        );
198

            
199
        // FIXME: overflow is not used but it should be
200
        // let overflow = self.feimage_values.overflow();
201
42
        let interpolation = Interpolation::from(self.feimage_values.image_rendering());
202

            
203
        let surface =
204
84
            ctx.source_graphic()
205
42
                .paint_image(bounds.clipped, image, Some(rect), interpolation)?;
206

            
207
42
        Ok(surface)
208
42
    }
209

            
210
1
    fn render_surface_from_svg(
211
        &self,
212
        document: &Document,
213
        ctx: &FilterContext,
214
        bounds: &Bounds,
215
        draw_ctx: &DrawingCtx,
216
    ) -> Result<SharedImageSurface, FilterError> {
217
        // Strategy:
218
        //
219
        // Render the document at the size needed for the filter primitive
220
        // subregion, and then paste that as if we were handling the case for a raster imge.
221
        //
222
        // Note that for feImage, x/y/width/height are *attributes*, not the geometry
223
        // properties from the normal <image> element , and have special handling:
224
        //
225
        // - They don't take "auto" as a value.  The defaults are "0 0 100% 100%" but those
226
        // are with respect to the filter primitive subregion.
227

            
228
1
        let x = bounds.x.unwrap_or(0.0);
229
1
        let y = bounds.y.unwrap_or(0.0);
230
1
        let w = bounds.width.unwrap_or(1.0); // default is 100%
231
1
        let h = bounds.height.unwrap_or(1.0);
232

            
233
        // https://www.w3.org/TR/filter-effects/#FilterPrimitiveSubRegion
234
        // "If the filter primitive subregion has a negative or zero width or height, the
235
        // effect of the filter primitive is disabled."
236
1
        if w <= 0.0 || h < 0.0 {
237
            // In this case just return an empty image the size of the SourceGraphic
238
            return Ok(SharedImageSurface::empty(
239
                ctx.source_graphic().width(),
240
                ctx.source_graphic().height(),
241
                SurfaceType::SRgb,
242
            )?);
243
        }
244

            
245
1
        let dest_rect = Rect {
246
1
            x0: bounds.clipped.x0 + bounds.clipped.width() * x,
247
1
            y0: bounds.clipped.y0 + bounds.clipped.height() * y,
248
1
            x1: bounds.clipped.x0 + bounds.clipped.width() * w,
249
1
            y1: bounds.clipped.y0 + bounds.clipped.height() * h,
250
        };
251

            
252
1
        let dest_size = dest_rect.size();
253

            
254
1
        let surface_dest_rect = Rect::from_size(dest_size.0, dest_size.1);
255

            
256
        // We use ceil() to avoid chopping off the last pixel if it is partially covered.
257
1
        let surface_width = checked_i32(dest_size.0.ceil())?;
258
1
        let surface_height = checked_i32(dest_size.1.ceil())?;
259
        let surface =
260
1
            cairo::ImageSurface::create(cairo::Format::ARgb32, surface_width, surface_height)?;
261

            
262
        {
263
1
            let cr = cairo::Context::new(&surface)?;
264

            
265
1
            let options = draw_ctx.rendering_options(SvgNesting::ReferencedFromImageElement);
266

            
267
2
            document.render_document(
268
1
                draw_ctx.session(),
269
                &cr,
270
1
                &cairo::Rectangle::from(surface_dest_rect),
271
                &options,
272
            )?;
273
1
        }
274

            
275
        // Now paste that image as a normal raster image
276

            
277
1
        let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
278

            
279
1
        self.render_surface_from_raster_image(&surface, ctx, bounds)
280
1
    }
281
}
282

            
283
impl FilterEffect for FeImage {
284
68
    fn resolve(
285
        &self,
286
        acquired_nodes: &mut AcquiredNodes<'_>,
287
        node: &Node,
288
    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
289
68
        let cascaded = CascadedValues::new_from_node(node);
290
68
        let feimage_values = cascaded.get().clone();
291

            
292
68
        let source = match self.params.href {
293
            None => Source::None,
294

            
295
68
            Some(ref s) => {
296
68
                if let Ok(node_id) = NodeId::parse(s) {
297
26
                    acquired_nodes
298
                        .acquire(&node_id)
299
26
                        .map(|acquired| Source::Node(acquired.get().clone(), s.clone()))
300
26
                        .unwrap_or(Source::None)
301
26
                } else {
302
42
                    Source::ExternalImage(s.to_string())
303
                }
304
68
            }
305
        };
306

            
307
136
        Ok(vec![ResolvedPrimitive {
308
68
            primitive: self.base.clone(),
309
68
            params: PrimitiveParams::Image(Image {
310
68
                aspect: self.params.aspect,
311
68
                source,
312
68
                feimage_values: Box::new(feimage_values),
313
            }),
314
        }])
315
68
    }
316
}