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#[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
40pub struct Image {
42 aspect: AspectRatio,
43 source: Source,
44 feimage_values: Box<ComputedValues>,
45}
46
47enum Source {
49 None,
51
52 Node(Node, String),
54
55 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 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 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 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 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 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 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); let h = bounds.height.unwrap_or(1.0);
240
241 if w <= 0.0 || h < 0.0 {
245 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 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 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}