rsvg/filters/
image.rs

1use markup5ever::{expanded_name, local_name, ns};
2
3use crate::aspect_ratio::AspectRatio;
4use crate::document::{AcquiredNodes, Document, NodeId, Resource};
5use crate::drawing_ctx::{DrawingCtx, SvgNesting};
6use crate::element::{set_attribute, ElementTrait};
7use crate::href::{is_href, set_href};
8use crate::image::checked_i32;
9use crate::node::{CascadedValues, Node};
10use crate::parsers::ParseValue;
11use crate::properties::ComputedValues;
12use crate::rect::Rect;
13use crate::rsvg_log;
14use crate::session::Session;
15use crate::surface_utils::shared_surface::{Interpolation, SharedImageSurface, SurfaceType};
16use crate::transform::ValidTransform;
17use crate::viewbox::ViewBox;
18use crate::xml::Attributes;
19
20use super::bounds::{Bounds, BoundsBuilder};
21use super::context::{FilterContext, FilterOutput};
22use super::{
23    FilterEffect, FilterError, FilterResolveError, InputRequirements, Primitive, PrimitiveParams,
24    ResolvedPrimitive,
25};
26
27/// The `feImage` filter primitive.
28#[derive(Default)]
29pub struct FeImage {
30    base: Primitive,
31    params: ImageParams,
32}
33
34#[derive(Clone, Default)]
35struct ImageParams {
36    aspect: AspectRatio,
37    href: Option<String>,
38}
39
40/// Resolved `feImage` primitive for rendering.
41pub struct Image {
42    aspect: AspectRatio,
43    source: Source,
44    feimage_values: Box<ComputedValues>,
45}
46
47/// What a feImage references for rendering.
48enum Source {
49    /// Nothing is referenced; ignore the filter.
50    None,
51
52    /// Reference to a node.
53    Node(Node, String),
54
55    /// Reference to an external image.  This is just a URL.
56    ExternalImage(String),
57}
58
59impl ElementTrait for FeImage {
60    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
61        self.base.parse_no_inputs(attrs, session);
62
63        for (attr, value) in attrs.iter() {
64            match attr.expanded() {
65                expanded_name!("", "preserveAspectRatio") => {
66                    set_attribute(&mut self.params.aspect, attr.parse(value), session);
67                }
68
69                // "path" is used by some older Adobe Illustrator versions
70                ref a if is_href(a) || *a == expanded_name!("", "path") => {
71                    set_href(a, &mut self.params.href, Some(value.to_string()));
72                }
73
74                _ => (),
75            }
76        }
77    }
78}
79
80impl Image {
81    pub fn render(
82        &self,
83        bounds_builder: BoundsBuilder,
84        ctx: &FilterContext,
85        acquired_nodes: &mut AcquiredNodes<'_>,
86        draw_ctx: &mut DrawingCtx,
87    ) -> Result<FilterOutput, FilterError> {
88        let bounds = bounds_builder.compute(ctx);
89
90        let surface = match &self.source {
91            Source::None => return Err(FilterError::InvalidInput),
92
93            Source::Node(node, ref name) => {
94                if let Ok(acquired) = acquired_nodes.acquire_ref(node) {
95                    rsvg_log!(ctx.session(), "(feImage \"{}\"", name);
96                    let res = self.render_node(
97                        ctx,
98                        acquired_nodes,
99                        draw_ctx,
100                        bounds.clipped,
101                        acquired.get(),
102                    );
103                    rsvg_log!(ctx.session(), ")");
104                    res?
105                } else {
106                    return Err(FilterError::InvalidInput);
107                }
108            }
109
110            Source::ExternalImage(ref href) => {
111                self.render_external_image(ctx, acquired_nodes, draw_ctx, &bounds, href)?
112            }
113        };
114
115        Ok(FilterOutput {
116            surface,
117            bounds: bounds.clipped.into(),
118        })
119    }
120
121    pub fn get_input_requirements(&self) -> InputRequirements {
122        InputRequirements::default()
123    }
124
125    /// Renders the filter if the source is an existing node.
126    fn render_node(
127        &self,
128        ctx: &FilterContext,
129        acquired_nodes: &mut AcquiredNodes<'_>,
130        draw_ctx: &mut DrawingCtx,
131        bounds: Rect,
132        referenced_node: &Node,
133    ) -> Result<SharedImageSurface, FilterError> {
134        // https://www.w3.org/TR/filter-effects/#feImageElement
135        //
136        // The filters spec says, "... otherwise [rendering a referenced object], the
137        // referenced resource is rendered according to the behavior of the use element."
138        // I think this means that we use the same cascading mode as <use>, i.e. the
139        // referenced object inherits its properties from the feImage element.
140        let cascaded =
141            CascadedValues::new_from_values(referenced_node, &self.feimage_values, None, None);
142
143        let interpolation = Interpolation::from(self.feimage_values.image_rendering());
144
145        let paffine = ValidTransform::try_from(ctx.paffine())?;
146
147        let image = draw_ctx.draw_node_to_surface(
148            referenced_node,
149            acquired_nodes,
150            &cascaded,
151            paffine,
152            ctx.source_graphic().width(),
153            ctx.source_graphic().height(),
154        )?;
155
156        let surface = ctx
157            .source_graphic()
158            .paint_image(bounds, &image, None, interpolation)?;
159
160        Ok(surface)
161    }
162
163    /// Renders the filter if the source is an external image.
164    fn render_external_image(
165        &self,
166        ctx: &FilterContext,
167        acquired_nodes: &mut AcquiredNodes<'_>,
168        draw_ctx: &DrawingCtx,
169        bounds: &Bounds,
170        url: &str,
171    ) -> Result<SharedImageSurface, FilterError> {
172        match acquired_nodes.lookup_resource(url) {
173            Ok(Resource::Image(surface)) => {
174                self.render_surface_from_raster_image(&surface, ctx, bounds)
175            }
176
177            Ok(Resource::Document(document)) => {
178                self.render_surface_from_svg(&document, ctx, bounds, draw_ctx)
179            }
180
181            Err(e) => {
182                rsvg_log!(
183                    ctx.session(),
184                    "could not load image \"{}\" for feImage: {}",
185                    url,
186                    e
187                );
188                Err(FilterError::InvalidInput)
189            }
190        }
191    }
192
193    fn render_surface_from_raster_image(
194        &self,
195        image: &SharedImageSurface,
196        ctx: &FilterContext,
197        bounds: &Bounds,
198    ) -> Result<SharedImageSurface, FilterError> {
199        let rect = self.aspect.compute(
200            &ViewBox::from(Rect::from_size(
201                f64::from(image.width()),
202                f64::from(image.height()),
203            )),
204            &bounds.unclipped,
205        );
206
207        // FIXME: overflow is not used but it should be
208        // let overflow = self.feimage_values.overflow();
209        let interpolation = Interpolation::from(self.feimage_values.image_rendering());
210
211        let surface =
212            ctx.source_graphic()
213                .paint_image(bounds.clipped, image, Some(rect), interpolation)?;
214
215        Ok(surface)
216    }
217
218    fn render_surface_from_svg(
219        &self,
220        document: &Document,
221        ctx: &FilterContext,
222        bounds: &Bounds,
223        draw_ctx: &DrawingCtx,
224    ) -> Result<SharedImageSurface, FilterError> {
225        // Strategy:
226        //
227        // Render the document at the size needed for the filter primitive
228        // subregion, and then paste that as if we were handling the case for a raster imge.
229        //
230        // Note that for feImage, x/y/width/height are *attributes*, not the geometry
231        // properties from the normal <image> element , and have special handling:
232        //
233        // - They don't take "auto" as a value.  The defaults are "0 0 100% 100%" but those
234        // are with respect to the filter primitive subregion.
235
236        let x = bounds.x.unwrap_or(0.0);
237        let y = bounds.y.unwrap_or(0.0);
238        let w = bounds.width.unwrap_or(1.0); // default is 100%
239        let h = bounds.height.unwrap_or(1.0);
240
241        // https://www.w3.org/TR/filter-effects/#FilterPrimitiveSubRegion
242        // "If the filter primitive subregion has a negative or zero width or height, the
243        // effect of the filter primitive is disabled."
244        if w <= 0.0 || h < 0.0 {
245            // In this case just return an empty image the size of the SourceGraphic
246            return Ok(SharedImageSurface::empty(
247                ctx.source_graphic().width(),
248                ctx.source_graphic().height(),
249                SurfaceType::SRgb,
250            )?);
251        }
252
253        let dest_rect = Rect {
254            x0: bounds.clipped.x0 + bounds.clipped.width() * x,
255            y0: bounds.clipped.y0 + bounds.clipped.height() * y,
256            x1: bounds.clipped.x0 + bounds.clipped.width() * w,
257            y1: bounds.clipped.y0 + bounds.clipped.height() * h,
258        };
259
260        let dest_size = dest_rect.size();
261
262        let surface_dest_rect = Rect::from_size(dest_size.0, dest_size.1);
263
264        // We use ceil() to avoid chopping off the last pixel if it is partially covered.
265        let surface_width = checked_i32(dest_size.0.ceil())?;
266        let surface_height = checked_i32(dest_size.1.ceil())?;
267        let surface =
268            cairo::ImageSurface::create(cairo::Format::ARgb32, surface_width, surface_height)?;
269
270        {
271            let cr = cairo::Context::new(&surface)?;
272
273            let options = draw_ctx.rendering_options(SvgNesting::ReferencedFromImageElement);
274
275            document.render_document(&cr, &cairo::Rectangle::from(surface_dest_rect), &options)?;
276        }
277
278        // Now paste that image as a normal raster image
279
280        let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
281
282        self.render_surface_from_raster_image(&surface, ctx, bounds)
283    }
284}
285
286impl FilterEffect for FeImage {
287    fn resolve(
288        &self,
289        acquired_nodes: &mut AcquiredNodes<'_>,
290        node: &Node,
291    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
292        let cascaded = CascadedValues::new_from_node(node);
293        let feimage_values = cascaded.get().clone();
294
295        let source = match self.params.href {
296            None => Source::None,
297
298            Some(ref s) => {
299                if let Ok(node_id) = NodeId::parse(s) {
300                    acquired_nodes
301                        .acquire(&node_id)
302                        .map(|acquired| Source::Node(acquired.get().clone(), s.clone()))
303                        .unwrap_or(Source::None)
304                } else {
305                    Source::ExternalImage(s.to_string())
306                }
307            }
308        };
309
310        Ok(vec![ResolvedPrimitive {
311            primitive: self.base.clone(),
312            params: PrimitiveParams::Image(Image {
313                aspect: self.params.aspect,
314                source,
315                feimage_values: Box::new(feimage_values),
316            }),
317        }])
318    }
319}