diff options
Diffstat (limited to 'rsvg/src/filters/image.rs')
-rw-r--r-- | rsvg/src/filters/image.rs | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/rsvg/src/filters/image.rs b/rsvg/src/filters/image.rs new file mode 100644 index 00000000..eaeb08f9 --- /dev/null +++ b/rsvg/src/filters/image.rs @@ -0,0 +1,211 @@ +use markup5ever::{expanded_name, local_name, namespace_url, ns}; + +use crate::aspect_ratio::AspectRatio; +use crate::document::{AcquiredNodes, NodeId}; +use crate::drawing_ctx::DrawingCtx; +use crate::element::{set_attribute, ElementTrait}; +use crate::href::{is_href, set_href}; +use crate::node::{CascadedValues, Node}; +use crate::parsers::ParseValue; +use crate::properties::ComputedValues; +use crate::rect::Rect; +use crate::session::Session; +use crate::surface_utils::shared_surface::SharedImageSurface; +use crate::viewbox::ViewBox; +use crate::xml::Attributes; + +use super::bounds::{Bounds, BoundsBuilder}; +use super::context::{FilterContext, FilterOutput}; +use super::{ + FilterEffect, FilterError, FilterResolveError, Primitive, PrimitiveParams, ResolvedPrimitive, +}; + +/// The `feImage` filter primitive. +#[derive(Default)] +pub struct FeImage { + base: Primitive, + params: ImageParams, +} + +#[derive(Clone, Default)] +struct ImageParams { + aspect: AspectRatio, + href: Option<String>, +} + +/// Resolved `feImage` primitive for rendering. +pub struct Image { + aspect: AspectRatio, + source: Source, + feimage_values: Box<ComputedValues>, +} + +/// What a feImage references for rendering. +enum Source { + /// Nothing is referenced; ignore the filter. + None, + + /// Reference to a node. + Node(Node), + + /// Reference to an external image. This is just a URL. + ExternalImage(String), +} + +impl Image { + /// Renders the filter if the source is an existing node. + fn render_node( + &self, + ctx: &FilterContext, + acquired_nodes: &mut AcquiredNodes<'_>, + draw_ctx: &mut DrawingCtx, + bounds: Rect, + referenced_node: &Node, + ) -> Result<SharedImageSurface, FilterError> { + // https://www.w3.org/TR/filter-effects/#feImageElement + // + // The filters spec says, "... otherwise [rendering a referenced object], the + // referenced resource is rendered according to the behavior of the use element." + // I think this means that we use the same cascading mode as <use>, i.e. the + // referenced object inherits its properties from the feImage element. + let cascaded = + CascadedValues::new_from_values(referenced_node, &self.feimage_values, None, None); + + let image = draw_ctx.draw_node_to_surface( + referenced_node, + acquired_nodes, + &cascaded, + ctx.paffine(), + ctx.source_graphic().width(), + ctx.source_graphic().height(), + )?; + + let surface = ctx.source_graphic().paint_image(bounds, &image, None)?; + + Ok(surface) + } + + /// Renders the filter if the source is an external image. + fn render_external_image( + &self, + ctx: &FilterContext, + acquired_nodes: &mut AcquiredNodes<'_>, + _draw_ctx: &DrawingCtx, + bounds: &Bounds, + url: &str, + ) -> Result<SharedImageSurface, FilterError> { + // FIXME: translate the error better here + let image = acquired_nodes + .lookup_image(url) + .map_err(|_| FilterError::InvalidInput)?; + + let rect = self.aspect.compute( + &ViewBox::from(Rect::from_size( + f64::from(image.width()), + f64::from(image.height()), + )), + &bounds.unclipped, + ); + + let surface = ctx + .source_graphic() + .paint_image(bounds.clipped, &image, Some(rect))?; + + Ok(surface) + } +} + +impl ElementTrait for FeImage { + fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { + self.base.parse_no_inputs(attrs, session); + + for (attr, value) in attrs.iter() { + match attr.expanded() { + expanded_name!("", "preserveAspectRatio") => { + set_attribute(&mut self.params.aspect, attr.parse(value), session); + } + + // "path" is used by some older Adobe Illustrator versions + ref a if is_href(a) || *a == expanded_name!("", "path") => { + set_href(a, &mut self.params.href, Some(value.to_string())); + } + + _ => (), + } + } + } +} + +impl Image { + pub fn render( + &self, + bounds_builder: BoundsBuilder, + ctx: &FilterContext, + acquired_nodes: &mut AcquiredNodes<'_>, + draw_ctx: &mut DrawingCtx, + ) -> Result<FilterOutput, FilterError> { + let bounds = bounds_builder.compute(ctx); + + let surface = match &self.source { + Source::None => return Err(FilterError::InvalidInput), + + Source::Node(node) => { + if let Ok(acquired) = acquired_nodes.acquire_ref(node) { + self.render_node( + ctx, + acquired_nodes, + draw_ctx, + bounds.clipped, + acquired.get(), + )? + } else { + return Err(FilterError::InvalidInput); + } + } + + Source::ExternalImage(ref href) => { + self.render_external_image(ctx, acquired_nodes, draw_ctx, &bounds, href)? + } + }; + + Ok(FilterOutput { + surface, + bounds: bounds.clipped.into(), + }) + } +} + +impl FilterEffect for FeImage { + fn resolve( + &self, + acquired_nodes: &mut AcquiredNodes<'_>, + node: &Node, + ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> { + let cascaded = CascadedValues::new_from_node(node); + let feimage_values = cascaded.get().clone(); + + let source = match self.params.href { + None => Source::None, + + Some(ref s) => { + if let Ok(node_id) = NodeId::parse(s) { + acquired_nodes + .acquire(&node_id) + .map(|acquired| Source::Node(acquired.get().clone())) + .unwrap_or(Source::None) + } else { + Source::ExternalImage(s.to_string()) + } + } + }; + + Ok(vec![ResolvedPrimitive { + primitive: self.base.clone(), + params: PrimitiveParams::Image(Image { + aspect: self.params.aspect, + source, + feimage_values: Box::new(feimage_values), + }), + }]) + } +} |