rsvg/filters/
context.rs

1use std::collections::HashMap;
2use std::rc::Rc;
3
4use crate::bbox::BoundingBox;
5use crate::coord_units::CoordUnits;
6use crate::filter::UserSpaceFilter;
7use crate::parsers::CustomIdent;
8use crate::properties::ColorInterpolationFilters;
9use crate::rect::{IRect, Rect};
10use crate::session::Session;
11use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
12use crate::transform::Transform;
13
14use super::error::FilterError;
15use super::{FilterPlan, Input};
16
17/// A filter primitive output.
18#[derive(Debug, Clone)]
19pub struct FilterOutput {
20    /// The surface after the filter primitive was applied.
21    pub surface: SharedImageSurface,
22
23    /// The filter primitive subregion.
24    pub bounds: IRect,
25}
26
27/// A filter primitive result.
28#[derive(Debug, Clone)]
29pub struct FilterResult {
30    /// The name of this result: the value of the `result` attribute.
31    pub name: Option<CustomIdent>,
32
33    /// The output.
34    pub output: FilterOutput,
35}
36
37/// An input to a filter primitive.
38#[derive(Debug, Clone)]
39pub enum FilterInput {
40    /// One of the standard inputs.
41    StandardInput(SharedImageSurface),
42    /// Output of another filter primitive.
43    PrimitiveOutput(FilterOutput),
44}
45
46/// Context for rendering a single [`crate::filters::FilterSpec`].
47///
48/// Rendering a [`crate::filters::FilterSpec`] involves keeping track of possibly-named results
49/// for each filter primitive (e.g. those that have an `output` attribute).  This struct
50/// maintains that information, plus all the extra data used during filtering.
51pub struct FilterContext {
52    /// Immutable values used during the execution of a `filter` property.
53    plan: Rc<FilterPlan>,
54
55    /// The source graphic surface.
56    source_surface: SharedImageSurface,
57
58    /// Output of the last filter primitive.
59    last_result: Option<FilterOutput>,
60    /// Surfaces of the previous filter primitives by name.
61    previous_results: HashMap<CustomIdent, FilterOutput>,
62
63    /// Primtive units
64    primitive_units: CoordUnits,
65    /// The filter effects region.
66    effects_region: Rect,
67
68    /// The filter element affine matrix.
69    ///
70    /// If `filterUnits == userSpaceOnUse`, equal to the drawing context matrix, so, for example,
71    /// if the target node is in a group with `transform="translate(30, 20)"`, this will be equal
72    /// to a matrix that translates to 30, 20 (and does not scale). Note that the target node
73    /// bounding box isn't included in the computations in this case.
74    ///
75    /// If `filterUnits == objectBoundingBox`, equal to the target node bounding box matrix
76    /// multiplied by the drawing context matrix, so, for example, if the target node is in a group
77    /// with `transform="translate(30, 20)"` and also has `x="1", y="1", width="50", height="50"`,
78    /// this will be equal to a matrix that translates to 31, 21 and scales to 50, 50.
79    ///
80    /// This is to be used in conjunction with setting the viewbox size to account for the scaling.
81    /// For `filterUnits == userSpaceOnUse`, the viewbox will have the actual resolution size, and
82    /// for `filterUnits == objectBoundingBox`, the viewbox will have the size of 1, 1.
83    _affine: Transform,
84
85    /// The filter primitive affine matrix.
86    ///
87    /// See the comments for `_affine`, they largely apply here.
88    paffine: Transform,
89}
90
91impl FilterContext {
92    /// Creates a new `FilterContext`.
93    pub fn new(
94        filter: &UserSpaceFilter,
95        plan: Rc<FilterPlan>,
96        source_surface: SharedImageSurface,
97        node_bbox: BoundingBox,
98    ) -> Result<Self, FilterError> {
99        // The rect can be empty (for example, if the filter is applied to an empty group).
100        // However, with userSpaceOnUse it's still possible to create images with a filter.
101        let bbox_rect = node_bbox.rect.unwrap_or_default();
102
103        let affine = match filter.filter_units {
104            CoordUnits::UserSpaceOnUse => *plan.viewport.transform,
105            CoordUnits::ObjectBoundingBox => Transform::new_unchecked(
106                bbox_rect.width(),
107                0.0,
108                0.0,
109                bbox_rect.height(),
110                bbox_rect.x0,
111                bbox_rect.y0,
112            )
113            .post_transform(&plan.viewport.transform),
114        };
115
116        let paffine = match filter.primitive_units {
117            CoordUnits::UserSpaceOnUse => *plan.viewport.transform,
118            CoordUnits::ObjectBoundingBox => Transform::new_unchecked(
119                bbox_rect.width(),
120                0.0,
121                0.0,
122                bbox_rect.height(),
123                bbox_rect.x0,
124                bbox_rect.y0,
125            )
126            .post_transform(&plan.viewport.transform),
127        };
128
129        if !(affine.is_invertible() && paffine.is_invertible()) {
130            return Err(FilterError::InvalidParameter(
131                "transform is not invertible".to_string(),
132            ));
133        }
134
135        let effects_region = {
136            let mut bbox = BoundingBox::new();
137            let other_bbox = BoundingBox::new()
138                .with_transform(affine)
139                .with_rect(filter.rect);
140
141            // At this point all of the previous viewbox and matrix business gets converted to pixel
142            // coordinates in the final surface, because bbox is created with an identity transform.
143            bbox.insert(&other_bbox);
144
145            // Finally, clip to the width and height of our surface.
146            let (width, height) = (source_surface.width(), source_surface.height());
147            let rect = Rect::from_size(f64::from(width), f64::from(height));
148            let other_bbox = BoundingBox::new().with_rect(rect);
149            bbox.clip(&other_bbox);
150
151            bbox.rect.unwrap()
152        };
153
154        Ok(Self {
155            plan,
156            source_surface,
157            last_result: None,
158            previous_results: HashMap::new(),
159            primitive_units: filter.primitive_units,
160            effects_region,
161            _affine: affine,
162            paffine,
163        })
164    }
165
166    pub fn session(&self) -> &Session {
167        &self.plan.session
168    }
169
170    /// Returns the surface corresponding to the source graphic.
171    #[inline]
172    pub fn source_graphic(&self) -> &SharedImageSurface {
173        &self.source_surface
174    }
175
176    /// Returns the surface corresponding to the background image snapshot.
177    fn background_image(&self) -> SharedImageSurface {
178        self.plan.background_image.clone().expect(
179            "the calling DrawingCtx should have passed a background_image to the FilterPlan",
180        )
181    }
182
183    /// Returns a surface filled with the current stroke's paint, for `StrokePaint` inputs in primitives.
184    ///
185    /// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#attr-valuedef-in-strokepaint>
186    fn stroke_paint_image(&self) -> SharedImageSurface {
187        self.plan.stroke_paint_image.clone().expect(
188            "the calling DrawingCtx should have passed a stroke_paint_image to the FilterPlan",
189        )
190    }
191
192    /// Returns a surface filled with the current fill's paint, for `FillPaint` inputs in primitives.
193    ///
194    /// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#attr-valuedef-in-fillpaint>
195    fn fill_paint_image(&self) -> SharedImageSurface {
196        self.plan.fill_paint_image.clone().expect(
197            "the calling DrawingCtx should have passed a fill_paint_image to the FilterPlan",
198        )
199    }
200
201    /// Converts this `FilterContext` into the surface corresponding to the output of the filter
202    /// chain.
203    ///
204    /// The returned surface is in the sRGB color space.
205    // TODO: sRGB conversion should probably be done by the caller.
206    #[inline]
207    pub fn into_output(self) -> Result<SharedImageSurface, cairo::Error> {
208        match self.last_result {
209            Some(FilterOutput { surface, bounds }) => surface.to_srgb(bounds),
210            None => SharedImageSurface::empty(
211                self.source_surface.width(),
212                self.source_surface.height(),
213                SurfaceType::AlphaOnly,
214            ),
215        }
216    }
217
218    /// Stores a filter primitive result into the context.
219    #[inline]
220    pub fn store_result(&mut self, result: FilterResult) {
221        if let Some(name) = result.name {
222            self.previous_results.insert(name, result.output.clone());
223        }
224
225        self.last_result = Some(result.output);
226    }
227
228    /// Returns the paffine matrix.
229    #[inline]
230    pub fn paffine(&self) -> Transform {
231        self.paffine
232    }
233
234    /// Returns the primitive units.
235    #[inline]
236    pub fn primitive_units(&self) -> CoordUnits {
237        self.primitive_units
238    }
239
240    /// Returns the filter effects region.
241    #[inline]
242    pub fn effects_region(&self) -> Rect {
243        self.effects_region
244    }
245
246    /// Get a filter primitive's default input as if its `in=\"...\"` were not specified.
247    ///
248    /// Per <https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-primitive-in>,
249    /// "References to non-existent results will be treated as if no result was
250    /// specified".  That is, fall back to the last result in the filter chain, or if this
251    /// is the first in the chain, just use SourceGraphic.
252    fn get_unspecified_input(&self) -> FilterInput {
253        if let Some(output) = self.last_result.as_ref() {
254            FilterInput::PrimitiveOutput(output.clone())
255        } else {
256            FilterInput::StandardInput(self.source_graphic().clone())
257        }
258    }
259
260    /// Retrieves the filter input surface according to the SVG rules.
261    fn get_input_raw(&self, in_: &Input) -> Result<FilterInput, FilterError> {
262        match *in_ {
263            Input::Unspecified => Ok(self.get_unspecified_input()),
264
265            Input::SourceGraphic => Ok(FilterInput::StandardInput(self.source_graphic().clone())),
266
267            Input::SourceAlpha => self
268                .source_graphic()
269                .extract_alpha(self.effects_region().into())
270                .map_err(FilterError::CairoError)
271                .map(FilterInput::StandardInput),
272
273            Input::BackgroundImage => Ok(FilterInput::StandardInput(self.background_image())),
274
275            Input::BackgroundAlpha => self
276                .background_image()
277                .extract_alpha(self.effects_region().into())
278                .map_err(FilterError::CairoError)
279                .map(FilterInput::StandardInput),
280
281            Input::FillPaint => Ok(FilterInput::StandardInput(self.fill_paint_image())),
282
283            Input::StrokePaint => Ok(FilterInput::StandardInput(self.stroke_paint_image())),
284
285            Input::FilterOutput(ref name) => {
286                let input = match self.previous_results.get(name).cloned() {
287                    Some(filter_output) => {
288                        // Happy path: we found a previous primitive's named output, so pass it on.
289                        FilterInput::PrimitiveOutput(filter_output)
290                    }
291
292                    None => {
293                        // Fallback path: we didn't find a primitive's output by the
294                        // specified name, so fall back to using an unspecified output.
295                        // Per the spec, "References to non-existent results will be
296                        // treated as if no result was specified." -
297                        // https://drafts.fxtf.org/filter-effects/#element-attrdef-filter-primitive-in
298                        self.get_unspecified_input()
299                    }
300                };
301
302                Ok(input)
303            }
304        }
305    }
306
307    /// Retrieves the filter input surface according to the SVG rules.
308    ///
309    /// The surface will be converted to the color space specified by `color_interpolation_filters`.
310    pub fn get_input(
311        &self,
312        in_: &Input,
313        color_interpolation_filters: ColorInterpolationFilters,
314    ) -> Result<FilterInput, FilterError> {
315        let raw = self.get_input_raw(in_)?;
316
317        // Convert the input surface to the desired format.
318        let (surface, bounds) = match raw {
319            FilterInput::StandardInput(ref surface) => (surface, self.effects_region().into()),
320            FilterInput::PrimitiveOutput(FilterOutput {
321                ref surface,
322                ref bounds,
323            }) => (surface, *bounds),
324        };
325
326        let surface = match color_interpolation_filters {
327            ColorInterpolationFilters::Auto => Ok(surface.clone()),
328            ColorInterpolationFilters::LinearRgb => surface.to_linear_rgb(bounds),
329            ColorInterpolationFilters::Srgb => surface.to_srgb(bounds),
330        };
331
332        surface
333            .map_err(FilterError::CairoError)
334            .map(|surface| match raw {
335                FilterInput::StandardInput(_) => FilterInput::StandardInput(surface),
336                FilterInput::PrimitiveOutput(ref output) => {
337                    FilterInput::PrimitiveOutput(FilterOutput { surface, ..*output })
338                }
339            })
340    }
341}
342
343impl FilterInput {
344    /// Retrieves the surface from `FilterInput`.
345    #[inline]
346    pub fn surface(&self) -> &SharedImageSurface {
347        match *self {
348            FilterInput::StandardInput(ref surface) => surface,
349            FilterInput::PrimitiveOutput(FilterOutput { ref surface, .. }) => surface,
350        }
351    }
352}