1use cssparser::{BasicParseError, Parser};
4use markup5ever::{expanded_name, local_name, ns};
5use std::rc::Rc;
6use std::time::Instant;
7
8use crate::bbox::BoundingBox;
9use crate::document::AcquiredNodes;
10use crate::drawing_ctx::{DrawingCtx, Viewport};
11use crate::element::{set_attribute, ElementTrait};
12use crate::error::{InternalRenderingError, ParseError};
13use crate::filter::UserSpaceFilter;
14use crate::length::*;
15use crate::node::Node;
16use crate::parse_identifiers;
17use crate::parsers::{CustomIdent, Parse, ParseValue};
18use crate::properties::ColorInterpolationFilters;
19use crate::rsvg_log;
20use crate::session::Session;
21use crate::surface_utils::{
22 shared_surface::{SharedImageSurface, SurfaceType},
23 EdgeMode,
24};
25use crate::xml::Attributes;
26
27mod bounds;
28use self::bounds::BoundsBuilder;
29
30pub mod context;
31use self::context::{FilterContext, FilterOutput, FilterResult};
32
33mod error;
34use self::error::FilterError;
35pub use self::error::FilterResolveError;
36
37pub trait FilterEffect: ElementTrait {
39 fn resolve(
40 &self,
41 acquired_nodes: &mut AcquiredNodes<'_>,
42 node: &Node,
43 ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError>;
44}
45
46pub mod blend;
47pub mod color_matrix;
48pub mod component_transfer;
49pub mod composite;
50pub mod convolve_matrix;
51pub mod displacement_map;
52pub mod drop_shadow;
53pub mod flood;
54pub mod gaussian_blur;
55pub mod image;
56pub mod lighting;
57pub mod merge;
58pub mod morphology;
59pub mod offset;
60pub mod tile;
61pub mod turbulence;
62
63pub struct FilterSpec {
68 pub name: String,
70
71 pub user_space_filter: UserSpaceFilter,
73
74 pub primitives: Vec<UserSpacePrimitive>,
76}
77
78pub struct FilterPlan {
93 session: Session,
94
95 pub viewport: Viewport,
97
98 background_image: Option<SharedImageSurface>,
100
101 stroke_paint_image: Option<SharedImageSurface>,
105
106 fill_paint_image: Option<SharedImageSurface>,
110}
111
112impl FilterPlan {
113 pub fn new(
114 session: &Session,
115 viewport: Viewport,
116 requirements: InputRequirements,
117 background_image: Option<SharedImageSurface>,
118 stroke_paint_image: Option<SharedImageSurface>,
119 fill_paint_image: Option<SharedImageSurface>,
120 ) -> Result<FilterPlan, Box<InternalRenderingError>> {
121 assert_eq!(
122 requirements.needs_background_image || requirements.needs_background_alpha,
123 background_image.is_some()
124 );
125
126 assert_eq!(
127 requirements.needs_stroke_paint_image,
128 stroke_paint_image.is_some()
129 );
130
131 assert_eq!(
132 requirements.needs_fill_paint_image,
133 fill_paint_image.is_some()
134 );
135
136 Ok(FilterPlan {
137 session: session.clone(),
138 viewport,
139 background_image,
140 stroke_paint_image,
141 fill_paint_image,
142 })
143 }
144}
145
146#[derive(Debug, Default, PartialEq)]
157pub struct InputRequirements {
158 pub needs_source_alpha: bool,
159 pub needs_background_image: bool,
160 pub needs_background_alpha: bool,
161 pub needs_stroke_paint_image: bool,
162 pub needs_fill_paint_image: bool,
163}
164
165impl InputRequirements {
166 pub fn new_from_filter_specs(specs: &[FilterSpec]) -> InputRequirements {
167 specs
168 .iter()
169 .flat_map(|spec| spec.primitives.iter())
170 .map(|primitive| primitive.params.get_input_requirements())
171 .fold(InputRequirements::default(), |a, b| a.fold(b))
172 }
173
174 #[rustfmt::skip]
175 fn fold(self, r: InputRequirements) -> InputRequirements {
176 InputRequirements {
177 needs_source_alpha: self.needs_source_alpha || r.needs_source_alpha,
178 needs_background_image: self.needs_background_image || r.needs_background_image,
179 needs_background_alpha: self.needs_background_alpha || r.needs_background_alpha,
180 needs_stroke_paint_image: self.needs_stroke_paint_image || r.needs_stroke_paint_image,
181 needs_fill_paint_image: self.needs_fill_paint_image || r.needs_fill_paint_image,
182 }
183 }
184}
185
186pub enum PrimitiveParams {
193 Blend(blend::Blend),
194 ColorMatrix(color_matrix::ColorMatrix),
195 ComponentTransfer(component_transfer::ComponentTransfer),
196 Composite(composite::Composite),
197 ConvolveMatrix(convolve_matrix::ConvolveMatrix),
198 DiffuseLighting(lighting::DiffuseLighting),
199 DisplacementMap(displacement_map::DisplacementMap),
200 Flood(flood::Flood),
201 GaussianBlur(gaussian_blur::GaussianBlur),
202 Image(image::Image),
203 Merge(merge::Merge),
204 Morphology(morphology::Morphology),
205 Offset(offset::Offset),
206 SpecularLighting(lighting::SpecularLighting),
207 Tile(tile::Tile),
208 Turbulence(turbulence::Turbulence),
209}
210
211impl PrimitiveParams {
212 #[rustfmt::skip]
214 fn name(&self) -> &'static str {
215 use PrimitiveParams::*;
216 match self {
217 Blend(..) => "feBlend",
218 ColorMatrix(..) => "feColorMatrix",
219 ComponentTransfer(..) => "feComponentTransfer",
220 Composite(..) => "feComposite",
221 ConvolveMatrix(..) => "feConvolveMatrix",
222 DiffuseLighting(..) => "feDiffuseLighting",
223 DisplacementMap(..) => "feDisplacementMap",
224 Flood(..) => "feFlood",
225 GaussianBlur(..) => "feGaussianBlur",
226 Image(..) => "feImage",
227 Merge(..) => "feMerge",
228 Morphology(..) => "feMorphology",
229 Offset(..) => "feOffset",
230 SpecularLighting(..) => "feSpecularLighting",
231 Tile(..) => "feTile",
232 Turbulence(..) => "feTurbulence",
233 }
234 }
235
236 #[rustfmt::skip]
237 fn get_input_requirements(&self) -> InputRequirements {
238 use PrimitiveParams::*;
239 match self {
240 Blend(p) => p.get_input_requirements(),
241 ColorMatrix(p) => p.get_input_requirements(),
242 ComponentTransfer(p) => p.get_input_requirements(),
243 Composite(p) => p.get_input_requirements(),
244 ConvolveMatrix(p) => p.get_input_requirements(),
245 DiffuseLighting(p) => p.get_input_requirements(),
246 DisplacementMap(p) => p.get_input_requirements(),
247 Flood(p) => p.get_input_requirements(),
248 GaussianBlur(p) => p.get_input_requirements(),
249 Image(p) => p.get_input_requirements(),
250 Merge(p) => p.get_input_requirements(),
251 Morphology(p) => p.get_input_requirements(),
252 Offset(p) => p.get_input_requirements(),
253 SpecularLighting(p) => p.get_input_requirements(),
254 Tile(p) => p.get_input_requirements(),
255 Turbulence(p) => p.get_input_requirements(),
256 }
257 }
258}
259
260#[derive(Default, Clone)]
262pub struct Primitive {
263 pub x: Option<Length<Horizontal>>,
264 pub y: Option<Length<Vertical>>,
265 pub width: Option<ULength<Horizontal>>,
266 pub height: Option<ULength<Vertical>>,
267 pub result: Option<CustomIdent>,
268}
269
270pub struct ResolvedPrimitive {
271 pub primitive: Primitive,
272 pub params: PrimitiveParams,
273}
274
275pub struct UserSpacePrimitive {
277 x: Option<f64>,
278 y: Option<f64>,
279 width: Option<f64>,
280 height: Option<f64>,
281 result: Option<CustomIdent>,
282
283 params: PrimitiveParams,
284}
285
286#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
288pub enum Input {
289 #[default]
290 Unspecified,
291 SourceGraphic,
292 SourceAlpha,
293 BackgroundImage,
294 BackgroundAlpha,
295 FillPaint,
296 StrokePaint,
297 FilterOutput(CustomIdent),
298}
299
300impl Input {
301 pub fn get_requirements(&self) -> InputRequirements {
302 use Input::*;
303
304 let mut reqs = InputRequirements::default();
305
306 match self {
307 SourceAlpha => reqs.needs_source_alpha = true,
308 BackgroundImage => reqs.needs_background_image = true,
309 BackgroundAlpha => reqs.needs_background_alpha = true,
310 FillPaint => reqs.needs_fill_paint_image = true,
311 StrokePaint => reqs.needs_stroke_paint_image = true,
312 _ => (),
313 }
314
315 reqs
316 }
317}
318
319impl Parse for Input {
320 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
321 parser
322 .try_parse(|p| {
323 parse_identifiers!(
324 p,
325 "SourceGraphic" => Input::SourceGraphic,
326 "SourceAlpha" => Input::SourceAlpha,
327 "BackgroundImage" => Input::BackgroundImage,
328 "BackgroundAlpha" => Input::BackgroundAlpha,
329 "FillPaint" => Input::FillPaint,
330 "StrokePaint" => Input::StrokePaint,
331 )
332 })
333 .or_else(|_: BasicParseError<'_>| {
334 let ident = CustomIdent::parse(parser)?;
335 Ok(Input::FilterOutput(ident))
336 })
337 }
338}
339
340impl ResolvedPrimitive {
341 pub fn into_user_space(self, params: &NormalizeParams) -> UserSpacePrimitive {
342 let x = self.primitive.x.map(|l| l.to_user(params));
343 let y = self.primitive.y.map(|l| l.to_user(params));
344 let width = self.primitive.width.map(|l| l.to_user(params));
345 let height = self.primitive.height.map(|l| l.to_user(params));
346
347 UserSpacePrimitive {
348 x,
349 y,
350 width,
351 height,
352 result: self.primitive.result,
353 params: self.params,
354 }
355 }
356}
357
358impl UserSpacePrimitive {
359 #[inline]
361 fn get_bounds(&self, ctx: &FilterContext) -> BoundsBuilder {
362 BoundsBuilder::new(self.x, self.y, self.width, self.height, ctx.paffine())
363 }
364}
365
366impl Primitive {
367 fn parse_standard_attributes(
368 &mut self,
369 attrs: &Attributes,
370 session: &Session,
371 ) -> (Input, Input) {
372 let mut input_1 = Input::Unspecified;
373 let mut input_2 = Input::Unspecified;
374
375 for (attr, value) in attrs.iter() {
376 match attr.expanded() {
377 expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session),
378 expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session),
379 expanded_name!("", "width") => {
380 set_attribute(&mut self.width, attr.parse(value), session)
381 }
382 expanded_name!("", "height") => {
383 set_attribute(&mut self.height, attr.parse(value), session)
384 }
385 expanded_name!("", "result") => {
386 set_attribute(&mut self.result, attr.parse(value), session)
387 }
388 expanded_name!("", "in") => set_attribute(&mut input_1, attr.parse(value), session),
389 expanded_name!("", "in2") => {
390 set_attribute(&mut input_2, attr.parse(value), session)
391 }
392 _ => (),
393 }
394 }
395
396 (input_1, input_2)
397 }
398
399 pub fn parse_no_inputs(&mut self, attrs: &Attributes, session: &Session) {
400 let (_, _) = self.parse_standard_attributes(attrs, session);
401 }
402
403 pub fn parse_one_input(&mut self, attrs: &Attributes, session: &Session) -> Input {
404 let (input_1, _) = self.parse_standard_attributes(attrs, session);
405 input_1
406 }
407
408 pub fn parse_two_inputs(&mut self, attrs: &Attributes, session: &Session) -> (Input, Input) {
409 self.parse_standard_attributes(attrs, session)
410 }
411}
412
413pub fn render(
415 plan: Rc<FilterPlan>,
416 filter: &FilterSpec,
417 source_surface: SharedImageSurface,
418 acquired_nodes: &mut AcquiredNodes<'_>,
419 draw_ctx: &mut DrawingCtx,
420 node_bbox: &BoundingBox,
421) -> Result<SharedImageSurface, Box<InternalRenderingError>> {
422 let session = draw_ctx.session().clone();
423
424 let surface_width = source_surface.width();
425 let surface_height = source_surface.height();
426
427 FilterContext::new(&filter.user_space_filter, plan, source_surface, *node_bbox)
428 .and_then(|mut filter_ctx| {
429 rsvg_log!(
431 session,
432 "(filter \"{}\" with effects_region={:?}",
433 filter.name,
434 filter_ctx.effects_region()
435 );
436 for user_space_primitive in &filter.primitives {
437 let start = Instant::now();
438
439 match render_primitive(user_space_primitive, &filter_ctx, acquired_nodes, draw_ctx)
440 {
441 Ok(output) => {
442 let elapsed = start.elapsed();
443 rsvg_log!(
444 session,
445 "(rendered filter primitive {} in {} seconds)",
446 user_space_primitive.params.name(),
447 elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) / 1e9
448 );
449
450 filter_ctx.store_result(FilterResult {
451 name: user_space_primitive.result.clone(),
452 output,
453 });
454 }
455
456 Err(err) => {
457 rsvg_log!(
458 session,
459 "(filter primitive {} returned an error: {})",
460 user_space_primitive.params.name(),
461 err
462 );
463
464 rsvg_log!(session, ")");
466
467 if let FilterError::CairoError(status) = err {
469 return Err(FilterError::CairoError(status));
470 }
471 }
472 }
473 }
474
475 rsvg_log!(session, ")");
477
478 Ok(filter_ctx.into_output()?)
479 })
480 .or_else(|err| match err {
481 FilterError::CairoError(status) => {
482 Err(Box::new(InternalRenderingError::from(status)))
484 }
485
486 _ => {
487 Ok(SharedImageSurface::empty(
489 surface_width,
490 surface_height,
491 SurfaceType::AlphaOnly,
492 )?)
493 }
494 })
495}
496
497#[rustfmt::skip]
498fn render_primitive(
499 primitive: &UserSpacePrimitive,
500 ctx: &FilterContext,
501 acquired_nodes: &mut AcquiredNodes<'_>,
502 draw_ctx: &mut DrawingCtx,
503) -> Result<FilterOutput, FilterError> {
504 use PrimitiveParams::*;
505
506 let bounds_builder = primitive.get_bounds(ctx);
507
508 match primitive.params {
513 Blend(ref p) => p.render(bounds_builder, ctx),
514 ColorMatrix(ref p) => p.render(bounds_builder, ctx),
515 ComponentTransfer(ref p) => p.render(bounds_builder, ctx),
516 Composite(ref p) => p.render(bounds_builder, ctx),
517 ConvolveMatrix(ref p) => p.render(bounds_builder, ctx),
518 DiffuseLighting(ref p) => p.render(bounds_builder, ctx),
519 DisplacementMap(ref p) => p.render(bounds_builder, ctx),
520 Flood(ref p) => p.render(bounds_builder, ctx),
521 GaussianBlur(ref p) => p.render(bounds_builder, ctx),
522 Image(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
523 Merge(ref p) => p.render(bounds_builder, ctx),
524 Morphology(ref p) => p.render(bounds_builder, ctx),
525 Offset(ref p) => p.render(bounds_builder, ctx),
526 SpecularLighting(ref p) => p.render(bounds_builder, ctx),
527 Tile(ref p) => p.render(bounds_builder, ctx),
528 Turbulence(ref p) => p.render(bounds_builder, ctx),
529 }
530}
531
532impl From<ColorInterpolationFilters> for SurfaceType {
533 fn from(c: ColorInterpolationFilters) -> Self {
534 match c {
535 ColorInterpolationFilters::LinearRgb => SurfaceType::LinearRgb,
536 _ => SurfaceType::SRgb,
537 }
538 }
539}
540
541impl Parse for EdgeMode {
542 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
543 Ok(parse_identifiers!(
544 parser,
545 "duplicate" => EdgeMode::Duplicate,
546 "wrap" => EdgeMode::Wrap,
547 "none" => EdgeMode::None,
548 )?)
549 }
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555
556 use crate::color::{Color, RGBA};
557 use crate::document::Document;
558 use crate::dpi::Dpi;
559 use crate::node::NodeBorrow;
560 use crate::properties::Filter;
561
562 fn get_input_requirements_for_node(document: &Document, node_id: &str) -> InputRequirements {
563 let node = document.lookup_internal_node(node_id).unwrap();
564 let elt = node.borrow_element();
565 let values = elt.get_computed_values();
566
567 let session = Session::default();
568 let mut acquired_nodes = AcquiredNodes::new(&document, None::<gio::Cancellable>);
569
570 let viewport = Viewport::new(Dpi::new(96.0, 96.0), 100.0, 100.0);
571
572 let filter = values.filter();
573
574 let filter_list = match filter {
575 Filter::None => {
576 panic!("the referenced node should have a filter property that is not 'none'")
577 }
578 Filter::List(filter_list) => filter_list,
579 };
580
581 let params = NormalizeParams::new(&values, &viewport);
582
583 let filter_specs = filter_list
584 .iter()
585 .map(|filter_value| {
586 filter_value.to_filter_spec(
587 &mut acquired_nodes,
588 ¶ms,
589 Color::Rgba(RGBA::new(0, 0, 0, 1.0)),
590 &viewport,
591 &session,
592 "rect",
593 )
594 })
595 .collect::<Result<Vec<FilterSpec>, _>>()
596 .unwrap();
597
598 InputRequirements::new_from_filter_specs(&filter_specs)
599 }
600
601 fn input_requirements_with_only_source_alpha() -> InputRequirements {
602 InputRequirements {
603 needs_source_alpha: true,
604 needs_background_image: false,
605 needs_background_alpha: false,
606 needs_stroke_paint_image: false,
607 needs_fill_paint_image: false,
608 }
609 }
610
611 #[test]
612 fn detects_source_alpha() {
613 let document = Document::load_from_bytes(include_bytes!("test_input_requirements.svg"));
614
615 assert_eq!(
616 get_input_requirements_for_node(&document, "rect_1"),
617 input_requirements_with_only_source_alpha(),
618 );
619
620 assert_eq!(
621 get_input_requirements_for_node(&document, "rect_2"),
622 input_requirements_with_only_source_alpha(),
623 );
624
625 assert_eq!(
626 get_input_requirements_for_node(&document, "rect_3"),
627 InputRequirements {
628 needs_source_alpha: false,
629 needs_background_image: true,
630 needs_background_alpha: true,
631 needs_stroke_paint_image: true,
632 needs_fill_paint_image: true,
633 }
634 );
635 }
636}