summaryrefslogtreecommitdiff
path: root/rsvg/src/filters/merge.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rsvg/src/filters/merge.rs')
-rw-r--r--rsvg/src/filters/merge.rs217
1 files changed, 217 insertions, 0 deletions
diff --git a/rsvg/src/filters/merge.rs b/rsvg/src/filters/merge.rs
new file mode 100644
index 00000000..0f762fdd
--- /dev/null
+++ b/rsvg/src/filters/merge.rs
@@ -0,0 +1,217 @@
+use markup5ever::{expanded_name, local_name, namespace_url, ns};
+
+use crate::document::AcquiredNodes;
+use crate::drawing_ctx::DrawingCtx;
+use crate::element::{set_attribute, ElementData, ElementTrait};
+use crate::node::{CascadedValues, Node, NodeBorrow};
+use crate::parsers::ParseValue;
+use crate::properties::ColorInterpolationFilters;
+use crate::rect::IRect;
+use crate::session::Session;
+use crate::surface_utils::shared_surface::{Operator, SharedImageSurface, SurfaceType};
+use crate::xml::Attributes;
+
+use super::bounds::BoundsBuilder;
+use super::context::{FilterContext, FilterOutput};
+use super::{
+ FilterEffect, FilterError, FilterResolveError, Input, Primitive, PrimitiveParams,
+ ResolvedPrimitive,
+};
+
+/// The `feMerge` filter primitive.
+pub struct FeMerge {
+ base: Primitive,
+}
+
+/// The `<feMergeNode>` element.
+#[derive(Clone, Default)]
+pub struct FeMergeNode {
+ in1: Input,
+}
+
+/// Resolved `feMerge` primitive for rendering.
+pub struct Merge {
+ pub merge_nodes: Vec<MergeNode>,
+}
+
+/// Resolved `feMergeNode` for rendering.
+#[derive(Debug, Default, PartialEq)]
+pub struct MergeNode {
+ pub in1: Input,
+ pub color_interpolation_filters: ColorInterpolationFilters,
+}
+
+impl Default for FeMerge {
+ /// Constructs a new `Merge` with empty properties.
+ #[inline]
+ fn default() -> FeMerge {
+ FeMerge {
+ base: Default::default(),
+ }
+ }
+}
+
+impl ElementTrait for FeMerge {
+ fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
+ self.base.parse_no_inputs(attrs, session);
+ }
+}
+
+impl ElementTrait for FeMergeNode {
+ fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
+ for (attr, value) in attrs.iter() {
+ if let expanded_name!("", "in") = attr.expanded() {
+ set_attribute(&mut self.in1, attr.parse(value), session);
+ }
+ }
+ }
+}
+
+impl MergeNode {
+ fn render(
+ &self,
+ ctx: &FilterContext,
+ acquired_nodes: &mut AcquiredNodes<'_>,
+ draw_ctx: &mut DrawingCtx,
+ bounds: IRect,
+ output_surface: Option<SharedImageSurface>,
+ ) -> Result<SharedImageSurface, FilterError> {
+ let input = ctx.get_input(
+ acquired_nodes,
+ draw_ctx,
+ &self.in1,
+ self.color_interpolation_filters,
+ )?;
+
+ if output_surface.is_none() {
+ return Ok(input.surface().clone());
+ }
+
+ input
+ .surface()
+ .compose(&output_surface.unwrap(), bounds, Operator::Over)
+ .map_err(FilterError::CairoError)
+ }
+}
+
+impl Merge {
+ pub fn render(
+ &self,
+ bounds_builder: BoundsBuilder,
+ ctx: &FilterContext,
+ acquired_nodes: &mut AcquiredNodes<'_>,
+ draw_ctx: &mut DrawingCtx,
+ ) -> Result<FilterOutput, FilterError> {
+ // Compute the filter bounds, taking each feMergeNode's input into account.
+ let mut bounds_builder = bounds_builder;
+ for merge_node in &self.merge_nodes {
+ let input = ctx.get_input(
+ acquired_nodes,
+ draw_ctx,
+ &merge_node.in1,
+ merge_node.color_interpolation_filters,
+ )?;
+ bounds_builder = bounds_builder.add_input(&input);
+ }
+
+ let bounds: IRect = bounds_builder.compute(ctx).clipped.into();
+
+ // Now merge them all.
+ let mut output_surface = None;
+ for merge_node in &self.merge_nodes {
+ output_surface = merge_node
+ .render(ctx, acquired_nodes, draw_ctx, bounds, output_surface)
+ .ok();
+ }
+
+ let surface = match output_surface {
+ Some(s) => s,
+ None => SharedImageSurface::empty(
+ ctx.source_graphic().width(),
+ ctx.source_graphic().height(),
+ SurfaceType::AlphaOnly,
+ )?,
+ };
+
+ Ok(FilterOutput { surface, bounds })
+ }
+}
+
+impl FilterEffect for FeMerge {
+ fn resolve(
+ &self,
+ _acquired_nodes: &mut AcquiredNodes<'_>,
+ node: &Node,
+ ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
+ Ok(vec![ResolvedPrimitive {
+ primitive: self.base.clone(),
+ params: PrimitiveParams::Merge(Merge {
+ merge_nodes: resolve_merge_nodes(node)?,
+ }),
+ }])
+ }
+}
+
+/// Takes a feMerge and walks its children to produce a list of feMergeNode arguments.
+fn resolve_merge_nodes(node: &Node) -> Result<Vec<MergeNode>, FilterResolveError> {
+ let mut merge_nodes = Vec::new();
+
+ for child in node.children().filter(|c| c.is_element()) {
+ let cascaded = CascadedValues::new_from_node(&child);
+ let values = cascaded.get();
+
+ if let ElementData::FeMergeNode(merge_node) = &*child.borrow_element_data() {
+ merge_nodes.push(MergeNode {
+ in1: merge_node.in1.clone(),
+ color_interpolation_filters: values.color_interpolation_filters(),
+ });
+ }
+ }
+
+ Ok(merge_nodes)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::document::Document;
+
+ #[test]
+ fn extracts_parameters() {
+ let document = Document::load_from_bytes(
+ br#"<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+ <filter id="filter">
+ <feMerge id="merge">
+ <feMergeNode in="SourceGraphic"/>
+ <feMergeNode in="SourceAlpha" color-interpolation-filters="sRGB"/>
+ </feMerge>
+ </filter>
+</svg>
+"#,
+ );
+ let mut acquired_nodes = AcquiredNodes::new(&document);
+
+ let node = document.lookup_internal_node("merge").unwrap();
+ let merge = borrow_element_as!(node, FeMerge);
+ let resolved = merge.resolve(&mut acquired_nodes, &node).unwrap();
+ let ResolvedPrimitive { params, .. } = resolved.first().unwrap();
+ let params = match params {
+ PrimitiveParams::Merge(m) => m,
+ _ => unreachable!(),
+ };
+ assert_eq!(
+ &params.merge_nodes[..],
+ vec![
+ MergeNode {
+ in1: Input::SourceGraphic,
+ color_interpolation_filters: Default::default(),
+ },
+ MergeNode {
+ in1: Input::SourceAlpha,
+ color_interpolation_filters: ColorInterpolationFilters::Srgb,
+ },
+ ]
+ );
+ }
+}