rsvg/filters/
lighting.rs

1//! Lighting filters and light nodes.
2
3use float_cmp::approx_eq;
4use markup5ever::{expanded_name, local_name, ns};
5use nalgebra::{Vector2, Vector3};
6use num_traits::identities::Zero;
7use rayon::prelude::*;
8use std::cmp::max;
9
10use crate::color::{Color, RGBA, color_to_rgba, resolve_color};
11use crate::document::AcquiredNodes;
12use crate::element::{ElementData, ElementTrait, set_attribute};
13use crate::filters::{
14    FilterEffect, FilterError, FilterResolveError, Input, InputRequirements, Primitive,
15    PrimitiveParams, ResolvedPrimitive,
16    bounds::BoundsBuilder,
17    context::{FilterContext, FilterOutput},
18};
19use crate::node::{CascadedValues, Node, NodeBorrow};
20use crate::parsers::{NonNegative, ParseValue};
21use crate::properties::ColorInterpolationFilters;
22use crate::rect::IRect;
23use crate::session::Session;
24use crate::surface_utils::{
25    ImageSurfaceDataExt, Pixel,
26    shared_surface::{ExclusiveImageSurface, SharedImageSurface, SurfaceType},
27};
28use crate::transform::Transform;
29use crate::unit_interval::UnitInterval;
30use crate::util::clamp;
31use crate::xml::Attributes;
32
33use super::convolve_matrix::KernelUnitLength;
34
35/// The `feDiffuseLighting` filter primitives.
36#[derive(Default)]
37pub struct FeDiffuseLighting {
38    base: Primitive,
39    params: DiffuseLightingParams,
40}
41
42#[derive(Clone)]
43pub struct DiffuseLightingParams {
44    in1: Input,
45    surface_scale: f64,
46    kernel_unit_length: KernelUnitLength,
47    diffuse_constant: NonNegative,
48}
49
50impl Default for DiffuseLightingParams {
51    fn default() -> Self {
52        Self {
53            in1: Default::default(),
54            surface_scale: 1.0,
55            kernel_unit_length: KernelUnitLength::default(),
56            diffuse_constant: NonNegative(1.0),
57        }
58    }
59}
60
61/// The `feSpecularLighting` filter primitives.
62#[derive(Default)]
63pub struct FeSpecularLighting {
64    base: Primitive,
65    params: SpecularLightingParams,
66}
67
68#[derive(Clone)]
69pub struct SpecularLightingParams {
70    in1: Input,
71    surface_scale: f64,
72    kernel_unit_length: KernelUnitLength,
73    specular_constant: NonNegative,
74    specular_exponent: f64,
75}
76
77impl Default for SpecularLightingParams {
78    fn default() -> Self {
79        Self {
80            in1: Default::default(),
81            surface_scale: 1.0,
82            kernel_unit_length: KernelUnitLength::default(),
83            specular_constant: NonNegative(1.0),
84            specular_exponent: 1.0,
85        }
86    }
87}
88
89/// Resolved `feDiffuseLighting` primitive for rendering.
90pub struct DiffuseLighting {
91    params: DiffuseLightingParams,
92    light: Light,
93}
94
95/// Resolved `feSpecularLighting` primitive for rendering.
96pub struct SpecularLighting {
97    params: SpecularLightingParams,
98    light: Light,
99}
100
101/// A light source before applying affine transformations, straight out of the SVG.
102#[derive(Debug, PartialEq)]
103enum UntransformedLightSource {
104    Distant(FeDistantLight),
105    Point(FePointLight),
106    Spot(FeSpotLight),
107}
108
109/// A light source with affine transformations applied.
110enum LightSource {
111    Distant {
112        azimuth: f64,
113        elevation: f64,
114    },
115    Point {
116        origin: Vector3<f64>,
117    },
118    Spot {
119        origin: Vector3<f64>,
120        direction: Vector3<f64>,
121        specular_exponent: f64,
122        limiting_cone_angle: Option<f64>,
123    },
124}
125
126impl UntransformedLightSource {
127    fn transform(&self, paffine: Transform) -> LightSource {
128        match *self {
129            UntransformedLightSource::Distant(ref l) => l.transform(),
130            UntransformedLightSource::Point(ref l) => l.transform(paffine),
131            UntransformedLightSource::Spot(ref l) => l.transform(paffine),
132        }
133    }
134}
135
136struct Light {
137    source: UntransformedLightSource,
138    lighting_color: Color,
139    color_interpolation_filters: ColorInterpolationFilters,
140}
141
142/// Returns the color and unit (or null) vector from the image sample to the light.
143#[inline]
144fn color_and_vector(
145    lighting_color: &RGBA,
146    source: &LightSource,
147    x: f64,
148    y: f64,
149    z: f64,
150) -> (RGBA, Vector3<f64>) {
151    let vector = match *source {
152        LightSource::Distant { azimuth, elevation } => {
153            let azimuth = azimuth.to_radians();
154            let elevation = elevation.to_radians();
155            Vector3::new(
156                azimuth.cos() * elevation.cos(),
157                azimuth.sin() * elevation.cos(),
158                elevation.sin(),
159            )
160        }
161        LightSource::Point { origin } | LightSource::Spot { origin, .. } => {
162            let mut v = origin - Vector3::new(x, y, z);
163            let _ = v.try_normalize_mut(0.0);
164            v
165        }
166    };
167
168    let color = match *source {
169        LightSource::Spot {
170            direction,
171            specular_exponent,
172            limiting_cone_angle,
173            ..
174        } => {
175            let transparent_color = RGBA::new(0, 0, 0, 0.0);
176            let minus_l_dot_s = -vector.dot(&direction);
177            match limiting_cone_angle {
178                _ if minus_l_dot_s <= 0.0 => transparent_color,
179                Some(a) if minus_l_dot_s < a.to_radians().cos() => transparent_color,
180                _ => {
181                    let factor = minus_l_dot_s.powf(specular_exponent);
182                    let compute = |x| (clamp(f64::from(x) * factor, 0.0, 255.0) + 0.5) as u8;
183
184                    RGBA {
185                        red: compute(lighting_color.red),
186                        green: compute(lighting_color.green),
187                        blue: compute(lighting_color.blue),
188                        alpha: 1.0,
189                    }
190                }
191            }
192        }
193        _ => *lighting_color,
194    };
195
196    (color, vector)
197}
198
199#[derive(Clone, Debug, Default, PartialEq)]
200pub struct FeDistantLight {
201    azimuth: f64,
202    elevation: f64,
203}
204
205impl FeDistantLight {
206    fn transform(&self) -> LightSource {
207        LightSource::Distant {
208            azimuth: self.azimuth,
209            elevation: self.elevation,
210        }
211    }
212}
213
214impl ElementTrait for FeDistantLight {
215    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
216        for (attr, value) in attrs.iter() {
217            match attr.expanded() {
218                expanded_name!("", "azimuth") => {
219                    set_attribute(&mut self.azimuth, attr.parse(value), session)
220                }
221                expanded_name!("", "elevation") => {
222                    set_attribute(&mut self.elevation, attr.parse(value), session)
223                }
224                _ => (),
225            }
226        }
227    }
228}
229
230#[derive(Clone, Debug, Default, PartialEq)]
231pub struct FePointLight {
232    x: f64,
233    y: f64,
234    z: f64,
235}
236
237impl FePointLight {
238    fn transform(&self, paffine: Transform) -> LightSource {
239        let (x, y) = paffine.transform_point(self.x, self.y);
240        let z = transform_dist(paffine, self.z);
241
242        LightSource::Point {
243            origin: Vector3::new(x, y, z),
244        }
245    }
246}
247
248impl ElementTrait for FePointLight {
249    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
250        for (attr, value) in attrs.iter() {
251            match attr.expanded() {
252                expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session),
253                expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session),
254                expanded_name!("", "z") => set_attribute(&mut self.z, attr.parse(value), session),
255                _ => (),
256            }
257        }
258    }
259}
260
261#[derive(Clone, Debug, PartialEq)]
262pub struct FeSpotLight {
263    x: f64,
264    y: f64,
265    z: f64,
266    points_at_x: f64,
267    points_at_y: f64,
268    points_at_z: f64,
269    specular_exponent: f64,
270    limiting_cone_angle: Option<f64>,
271}
272
273// We need this because, per the spec, the initial values for all fields are 0.0
274// except for specular_exponent, which is 1.
275impl Default for FeSpotLight {
276    fn default() -> FeSpotLight {
277        FeSpotLight {
278            x: 0.0,
279            y: 0.0,
280            z: 0.0,
281            points_at_x: 0.0,
282            points_at_y: 0.0,
283            points_at_z: 0.0,
284            specular_exponent: 1.0,
285            limiting_cone_angle: None,
286        }
287    }
288}
289
290impl FeSpotLight {
291    fn transform(&self, paffine: Transform) -> LightSource {
292        let (x, y) = paffine.transform_point(self.x, self.y);
293        let z = transform_dist(paffine, self.z);
294        let (points_at_x, points_at_y) =
295            paffine.transform_point(self.points_at_x, self.points_at_y);
296        let points_at_z = transform_dist(paffine, self.points_at_z);
297
298        let origin = Vector3::new(x, y, z);
299        let mut direction = Vector3::new(points_at_x, points_at_y, points_at_z) - origin;
300        let _ = direction.try_normalize_mut(0.0);
301
302        LightSource::Spot {
303            origin,
304            direction,
305            specular_exponent: self.specular_exponent,
306            limiting_cone_angle: self.limiting_cone_angle,
307        }
308    }
309}
310
311impl ElementTrait for FeSpotLight {
312    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
313        for (attr, value) in attrs.iter() {
314            match attr.expanded() {
315                expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session),
316                expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session),
317                expanded_name!("", "z") => set_attribute(&mut self.z, attr.parse(value), session),
318                expanded_name!("", "pointsAtX") => {
319                    set_attribute(&mut self.points_at_x, attr.parse(value), session)
320                }
321                expanded_name!("", "pointsAtY") => {
322                    set_attribute(&mut self.points_at_y, attr.parse(value), session)
323                }
324                expanded_name!("", "pointsAtZ") => {
325                    set_attribute(&mut self.points_at_z, attr.parse(value), session)
326                }
327
328                expanded_name!("", "specularExponent") => {
329                    set_attribute(&mut self.specular_exponent, attr.parse(value), session);
330                }
331
332                expanded_name!("", "limitingConeAngle") => {
333                    set_attribute(&mut self.limiting_cone_angle, attr.parse(value), session);
334                }
335
336                _ => (),
337            }
338        }
339    }
340}
341
342/// Applies the `primitiveUnits` coordinate transformation to a non-x or y distance.
343#[inline]
344fn transform_dist(t: Transform, d: f64) -> f64 {
345    d * (t.xx.powi(2) + t.yy.powi(2)).sqrt() / std::f64::consts::SQRT_2
346}
347
348impl ElementTrait for FeDiffuseLighting {
349    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
350        self.params.in1 = self.base.parse_one_input(attrs, session);
351
352        for (attr, value) in attrs.iter() {
353            match attr.expanded() {
354                expanded_name!("", "surfaceScale") => {
355                    set_attribute(&mut self.params.surface_scale, attr.parse(value), session);
356                }
357                expanded_name!("", "kernelUnitLength") => {
358                    set_attribute(
359                        &mut self.params.kernel_unit_length,
360                        attr.parse(value),
361                        session,
362                    );
363                }
364                expanded_name!("", "diffuseConstant") => {
365                    set_attribute(
366                        &mut self.params.diffuse_constant,
367                        attr.parse(value),
368                        session,
369                    );
370                }
371                _ => (),
372            }
373        }
374    }
375}
376
377impl DiffuseLighting {
378    #[inline]
379    fn compute_factor(&self, normal: Normal, light_vector: Vector3<f64>) -> f64 {
380        let k = if normal.normal.is_zero() {
381            // Common case of (0, 0, 1) normal.
382            light_vector.z
383        } else {
384            let mut n = normal
385                .normal
386                .map(|x| f64::from(x) * self.params.surface_scale / 255.);
387            n.component_mul_assign(&normal.factor);
388            let normal = Vector3::new(n.x, n.y, 1.0);
389
390            normal.dot(&light_vector) / normal.norm()
391        };
392
393        self.params.diffuse_constant.0 * k
394    }
395}
396
397impl ElementTrait for FeSpecularLighting {
398    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
399        self.params.in1 = self.base.parse_one_input(attrs, session);
400
401        for (attr, value) in attrs.iter() {
402            match attr.expanded() {
403                expanded_name!("", "surfaceScale") => {
404                    set_attribute(&mut self.params.surface_scale, attr.parse(value), session);
405                }
406                expanded_name!("", "kernelUnitLength") => {
407                    set_attribute(
408                        &mut self.params.kernel_unit_length,
409                        attr.parse(value),
410                        session,
411                    );
412                }
413                expanded_name!("", "specularConstant") => {
414                    set_attribute(
415                        &mut self.params.specular_constant,
416                        attr.parse(value),
417                        session,
418                    );
419                }
420                expanded_name!("", "specularExponent") => {
421                    set_attribute(
422                        &mut self.params.specular_exponent,
423                        attr.parse(value),
424                        session,
425                    );
426                }
427                _ => (),
428            }
429        }
430    }
431}
432
433impl SpecularLighting {
434    #[inline]
435    fn compute_factor(&self, normal: Normal, light_vector: Vector3<f64>) -> f64 {
436        let h = light_vector + Vector3::new(0.0, 0.0, 1.0);
437        let h_norm = h.norm();
438
439        if h_norm == 0.0 {
440            return 0.0;
441        }
442
443        let n_dot_h = if normal.normal.is_zero() {
444            // Common case of (0, 0, 1) normal.
445            h.z / h_norm
446        } else {
447            let mut n = normal
448                .normal
449                .map(|x| f64::from(x) * self.params.surface_scale / 255.);
450            n.component_mul_assign(&normal.factor);
451            let normal = Vector3::new(n.x, n.y, 1.0);
452            normal.dot(&h) / normal.norm() / h_norm
453        };
454
455        if approx_eq!(f64, self.params.specular_exponent, 1.0) {
456            self.params.specular_constant.0 * n_dot_h
457        } else {
458            self.params.specular_constant.0 * n_dot_h.powf(self.params.specular_exponent)
459        }
460    }
461}
462
463macro_rules! impl_lighting_filter {
464    ($lighting_type:ty, $params_name:ident, $alpha_func:ident) => {
465        impl $params_name {
466            pub fn render(
467                &self,
468                bounds_builder: BoundsBuilder,
469                ctx: &FilterContext,
470            ) -> Result<FilterOutput, FilterError> {
471                let input_1 =
472                    ctx.get_input(&self.params.in1, self.light.color_interpolation_filters)?;
473                let mut bounds: IRect = bounds_builder
474                    .add_input(&input_1)
475                    .compute(ctx)
476                    .clipped
477                    .into();
478                let original_bounds = bounds;
479
480                let scale = self
481                    .params
482                    .kernel_unit_length
483                    .0
484                    .map(|(dx, dy)| ctx.paffine().transform_distance(dx, dy));
485
486                let mut input_surface = input_1.surface().clone();
487
488                if let Some((ox, oy)) = scale {
489                    // Scale the input surface to match kernel_unit_length.
490                    let (new_surface, new_bounds) =
491                        input_surface.scale(bounds, 1.0 / ox, 1.0 / oy)?;
492
493                    input_surface = new_surface;
494                    bounds = new_bounds;
495                }
496
497                let (bounds_w, bounds_h) = bounds.size();
498
499                // Check if the surface is too small for normal computation. This case is
500                // unspecified; WebKit doesn't render anything in this case.
501                if bounds_w < 2 || bounds_h < 2 {
502                    return Err(FilterError::LightingInputTooSmall);
503                }
504
505                let (ox, oy) = scale.unwrap_or((1.0, 1.0));
506
507                let source = self.light.source.transform(ctx.paffine());
508
509                let mut surface = ExclusiveImageSurface::new(
510                    input_surface.width(),
511                    input_surface.height(),
512                    SurfaceType::from(self.light.color_interpolation_filters),
513                )?;
514
515                let lighting_color = color_to_rgba(&self.light.lighting_color);
516
517                {
518                    let output_stride = surface.stride() as usize;
519                    let mut output_data = surface.data();
520                    let output_slice = &mut *output_data;
521
522                    let compute_output_pixel =
523                        |output_slice: &mut [u8], base_y, x, y, normal: Normal| {
524                            let pixel = input_surface.get_pixel(x, y);
525
526                            let scaled_x = f64::from(x) * ox;
527                            let scaled_y = f64::from(y) * oy;
528                            let z = f64::from(pixel.a) / 255.0 * self.params.surface_scale;
529
530                            let (color, vector) =
531                                color_and_vector(&lighting_color, &source, scaled_x, scaled_y, z);
532
533                            // compute the factor just once for the three colors
534                            let factor = self.compute_factor(normal, vector);
535                            let compute =
536                                |x| (clamp(factor * f64::from(x), 0.0, 255.0) + 0.5) as u8;
537
538                            let r = compute(color.red);
539                            let g = compute(color.green);
540                            let b = compute(color.blue);
541                            let a = $alpha_func(r, g, b);
542
543                            let output_pixel = Pixel { r, g, b, a };
544
545                            output_slice.set_pixel(output_stride, output_pixel, x, y - base_y);
546                        };
547
548                    // Top left.
549                    compute_output_pixel(
550                        output_slice,
551                        0,
552                        bounds.x0 as u32,
553                        bounds.y0 as u32,
554                        Normal::top_left(&input_surface, bounds),
555                    );
556
557                    // Top right.
558                    compute_output_pixel(
559                        output_slice,
560                        0,
561                        bounds.x1 as u32 - 1,
562                        bounds.y0 as u32,
563                        Normal::top_right(&input_surface, bounds),
564                    );
565
566                    // Bottom left.
567                    compute_output_pixel(
568                        output_slice,
569                        0,
570                        bounds.x0 as u32,
571                        bounds.y1 as u32 - 1,
572                        Normal::bottom_left(&input_surface, bounds),
573                    );
574
575                    // Bottom right.
576                    compute_output_pixel(
577                        output_slice,
578                        0,
579                        bounds.x1 as u32 - 1,
580                        bounds.y1 as u32 - 1,
581                        Normal::bottom_right(&input_surface, bounds),
582                    );
583
584                    if bounds_w >= 3 {
585                        // Top row.
586                        for x in bounds.x0 as u32 + 1..bounds.x1 as u32 - 1 {
587                            compute_output_pixel(
588                                output_slice,
589                                0,
590                                x,
591                                bounds.y0 as u32,
592                                Normal::top_row(&input_surface, bounds, x),
593                            );
594                        }
595
596                        // Bottom row.
597                        for x in bounds.x0 as u32 + 1..bounds.x1 as u32 - 1 {
598                            compute_output_pixel(
599                                output_slice,
600                                0,
601                                x,
602                                bounds.y1 as u32 - 1,
603                                Normal::bottom_row(&input_surface, bounds, x),
604                            );
605                        }
606                    }
607
608                    if bounds_h >= 3 {
609                        // Left column.
610                        for y in bounds.y0 as u32 + 1..bounds.y1 as u32 - 1 {
611                            compute_output_pixel(
612                                output_slice,
613                                0,
614                                bounds.x0 as u32,
615                                y,
616                                Normal::left_column(&input_surface, bounds, y),
617                            );
618                        }
619
620                        // Right column.
621                        for y in bounds.y0 as u32 + 1..bounds.y1 as u32 - 1 {
622                            compute_output_pixel(
623                                output_slice,
624                                0,
625                                bounds.x1 as u32 - 1,
626                                y,
627                                Normal::right_column(&input_surface, bounds, y),
628                            );
629                        }
630                    }
631
632                    if bounds_w >= 3 && bounds_h >= 3 {
633                        // Interior pixels.
634                        let first_row = bounds.y0 as u32 + 1;
635                        let one_past_last_row = bounds.y1 as u32 - 1;
636                        let first_pixel = (first_row as usize) * output_stride;
637                        let one_past_last_pixel = (one_past_last_row as usize) * output_stride;
638
639                        output_slice[first_pixel..one_past_last_pixel]
640                            .par_chunks_mut(output_stride)
641                            .zip(first_row..one_past_last_row)
642                            .for_each(|(slice, y)| {
643                                for x in bounds.x0 as u32 + 1..bounds.x1 as u32 - 1 {
644                                    compute_output_pixel(
645                                        slice,
646                                        y,
647                                        x,
648                                        y,
649                                        Normal::interior(&input_surface, bounds, x, y),
650                                    );
651                                }
652                            });
653                    }
654                }
655
656                let mut surface = surface.share()?;
657
658                if let Some((ox, oy)) = scale {
659                    // Scale the output surface back.
660                    surface = surface.scale_to(
661                        ctx.source_graphic().width(),
662                        ctx.source_graphic().height(),
663                        original_bounds,
664                        ox,
665                        oy,
666                    )?;
667
668                    bounds = original_bounds;
669                }
670
671                Ok(FilterOutput { surface, bounds })
672            }
673
674            pub fn get_input_requirements(&self) -> InputRequirements {
675                self.params.in1.get_requirements()
676            }
677        }
678
679        impl FilterEffect for $lighting_type {
680            fn resolve(
681                &self,
682                _acquired_nodes: &mut AcquiredNodes<'_>,
683                node: &Node,
684            ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
685                let mut sources = node.children().rev().filter(|c| {
686                    c.is_element()
687                        && matches!(
688                            *c.borrow_element_data(),
689                            ElementData::FeDistantLight(_)
690                                | ElementData::FePointLight(_)
691                                | ElementData::FeSpotLight(_)
692                        )
693                });
694
695                let source_node = sources.next();
696                if source_node.is_none() || sources.next().is_some() {
697                    return Err(FilterResolveError::InvalidLightSourceCount);
698                }
699
700                let source_node = source_node.unwrap();
701
702                let source = match &*source_node.borrow_element_data() {
703                    ElementData::FeDistantLight(l) => {
704                        UntransformedLightSource::Distant((**l).clone())
705                    }
706                    ElementData::FePointLight(l) => UntransformedLightSource::Point((**l).clone()),
707                    ElementData::FeSpotLight(l) => UntransformedLightSource::Spot((**l).clone()),
708                    _ => unreachable!(),
709                };
710
711                let cascaded = CascadedValues::new_from_node(node);
712                let values = cascaded.get();
713
714                Ok(vec![ResolvedPrimitive {
715                    primitive: self.base.clone(),
716                    params: PrimitiveParams::$params_name($params_name {
717                        params: self.params.clone(),
718                        light: Light {
719                            source,
720                            lighting_color: resolve_color(
721                                &values.lighting_color().0,
722                                UnitInterval::clamp(1.0),
723                                &values.color().0,
724                            ),
725                            color_interpolation_filters: values.color_interpolation_filters(),
726                        },
727                    }),
728                }])
729            }
730        }
731    };
732}
733
734const fn diffuse_alpha(_r: u8, _g: u8, _b: u8) -> u8 {
735    255
736}
737
738fn specular_alpha(r: u8, g: u8, b: u8) -> u8 {
739    max(max(r, g), b)
740}
741
742impl_lighting_filter!(FeDiffuseLighting, DiffuseLighting, diffuse_alpha);
743
744impl_lighting_filter!(FeSpecularLighting, SpecularLighting, specular_alpha);
745
746/// 2D normal and factor stored separately.
747///
748/// The normal needs to be multiplied by `surface_scale * factor / 255` and
749/// normalized with 1 as the z component.
750/// pub for the purpose of accessing this from benchmarks.
751#[derive(Debug, Clone, Copy)]
752pub struct Normal {
753    pub factor: Vector2<f64>,
754    pub normal: Vector2<i16>,
755}
756
757impl Normal {
758    #[inline]
759    fn new(factor_x: f64, nx: i16, factor_y: f64, ny: i16) -> Normal {
760        // Negative nx and ny to account for the different coordinate system.
761        Normal {
762            factor: Vector2::new(factor_x, factor_y),
763            normal: Vector2::new(-nx, -ny),
764        }
765    }
766
767    /// Computes and returns the normal vector for the top left pixel for light filters.
768    #[inline]
769    pub fn top_left(surface: &SharedImageSurface, bounds: IRect) -> Normal {
770        // Surface needs to be at least 2×2.
771        assert!(bounds.width() >= 2);
772        assert!(bounds.height() >= 2);
773
774        let get = |x, y| i16::from(surface.get_pixel(x, y).a);
775        let (x, y) = (bounds.x0 as u32, bounds.y0 as u32);
776
777        let center = get(x, y);
778        let right = get(x + 1, y);
779        let bottom = get(x, y + 1);
780        let bottom_right = get(x + 1, y + 1);
781
782        Self::new(
783            2. / 3.,
784            -2 * center + 2 * right - bottom + bottom_right,
785            2. / 3.,
786            -2 * center - right + 2 * bottom + bottom_right,
787        )
788    }
789
790    /// Computes and returns the normal vector for the top row pixels for light filters.
791    #[inline]
792    pub fn top_row(surface: &SharedImageSurface, bounds: IRect, x: u32) -> Normal {
793        assert!(x as i32 > bounds.x0);
794        assert!((x as i32) + 1 < bounds.x1);
795        assert!(bounds.height() >= 2);
796
797        let get = |x, y| i16::from(surface.get_pixel(x, y).a);
798        let y = bounds.y0 as u32;
799
800        let left = get(x - 1, y);
801        let center = get(x, y);
802        let right = get(x + 1, y);
803        let bottom_left = get(x - 1, y + 1);
804        let bottom = get(x, y + 1);
805        let bottom_right = get(x + 1, y + 1);
806
807        Self::new(
808            1. / 3.,
809            -2 * left + 2 * right - bottom_left + bottom_right,
810            1. / 2.,
811            -left - 2 * center - right + bottom_left + 2 * bottom + bottom_right,
812        )
813    }
814
815    /// Computes and returns the normal vector for the top right pixel for light filters.
816    #[inline]
817    pub fn top_right(surface: &SharedImageSurface, bounds: IRect) -> Normal {
818        // Surface needs to be at least 2×2.
819        assert!(bounds.width() >= 2);
820        assert!(bounds.height() >= 2);
821
822        let get = |x, y| i16::from(surface.get_pixel(x, y).a);
823        let (x, y) = (bounds.x1 as u32 - 1, bounds.y0 as u32);
824
825        let left = get(x - 1, y);
826        let center = get(x, y);
827        let bottom_left = get(x - 1, y + 1);
828        let bottom = get(x, y + 1);
829
830        Self::new(
831            2. / 3.,
832            -2 * left + 2 * center - bottom_left + bottom,
833            2. / 3.,
834            -left - 2 * center + bottom_left + 2 * bottom,
835        )
836    }
837
838    /// Computes and returns the normal vector for the left column pixels for light filters.
839    #[inline]
840    pub fn left_column(surface: &SharedImageSurface, bounds: IRect, y: u32) -> Normal {
841        assert!(y as i32 > bounds.y0);
842        assert!((y as i32) + 1 < bounds.y1);
843        assert!(bounds.width() >= 2);
844
845        let get = |x, y| i16::from(surface.get_pixel(x, y).a);
846        let x = bounds.x0 as u32;
847
848        let top = get(x, y - 1);
849        let top_right = get(x + 1, y - 1);
850        let center = get(x, y);
851        let right = get(x + 1, y);
852        let bottom = get(x, y + 1);
853        let bottom_right = get(x + 1, y + 1);
854
855        Self::new(
856            1. / 2.,
857            -top + top_right - 2 * center + 2 * right - bottom + bottom_right,
858            1. / 3.,
859            -2 * top - top_right + 2 * bottom + bottom_right,
860        )
861    }
862
863    /// Computes and returns the normal vector for the interior pixels for light filters.
864    #[inline]
865    pub fn interior(surface: &SharedImageSurface, bounds: IRect, x: u32, y: u32) -> Normal {
866        assert!(x as i32 > bounds.x0);
867        assert!((x as i32) + 1 < bounds.x1);
868        assert!(y as i32 > bounds.y0);
869        assert!((y as i32) + 1 < bounds.y1);
870
871        let get = |x, y| i16::from(surface.get_pixel(x, y).a);
872
873        let top_left = get(x - 1, y - 1);
874        let top = get(x, y - 1);
875        let top_right = get(x + 1, y - 1);
876        let left = get(x - 1, y);
877        let right = get(x + 1, y);
878        let bottom_left = get(x - 1, y + 1);
879        let bottom = get(x, y + 1);
880        let bottom_right = get(x + 1, y + 1);
881
882        Self::new(
883            1. / 4.,
884            -top_left + top_right - 2 * left + 2 * right - bottom_left + bottom_right,
885            1. / 4.,
886            -top_left - 2 * top - top_right + bottom_left + 2 * bottom + bottom_right,
887        )
888    }
889
890    /// Computes and returns the normal vector for the right column pixels for light filters.
891    #[inline]
892    pub fn right_column(surface: &SharedImageSurface, bounds: IRect, y: u32) -> Normal {
893        assert!(y as i32 > bounds.y0);
894        assert!((y as i32) + 1 < bounds.y1);
895        assert!(bounds.width() >= 2);
896
897        let get = |x, y| i16::from(surface.get_pixel(x, y).a);
898        let x = bounds.x1 as u32 - 1;
899
900        let top_left = get(x - 1, y - 1);
901        let top = get(x, y - 1);
902        let left = get(x - 1, y);
903        let center = get(x, y);
904        let bottom_left = get(x - 1, y + 1);
905        let bottom = get(x, y + 1);
906
907        Self::new(
908            1. / 2.,
909            -top_left + top - 2 * left + 2 * center - bottom_left + bottom,
910            1. / 3.,
911            -top_left - 2 * top + bottom_left + 2 * bottom,
912        )
913    }
914
915    /// Computes and returns the normal vector for the bottom left pixel for light filters.
916    #[inline]
917    pub fn bottom_left(surface: &SharedImageSurface, bounds: IRect) -> Normal {
918        // Surface needs to be at least 2×2.
919        assert!(bounds.width() >= 2);
920        assert!(bounds.height() >= 2);
921
922        let get = |x, y| i16::from(surface.get_pixel(x, y).a);
923        let (x, y) = (bounds.x0 as u32, bounds.y1 as u32 - 1);
924
925        let top = get(x, y - 1);
926        let top_right = get(x + 1, y - 1);
927        let center = get(x, y);
928        let right = get(x + 1, y);
929
930        Self::new(
931            2. / 3.,
932            -top + top_right - 2 * center + 2 * right,
933            2. / 3.,
934            -2 * top - top_right + 2 * center + right,
935        )
936    }
937
938    /// Computes and returns the normal vector for the bottom row pixels for light filters.
939    #[inline]
940    pub fn bottom_row(surface: &SharedImageSurface, bounds: IRect, x: u32) -> Normal {
941        assert!(x as i32 > bounds.x0);
942        assert!((x as i32) + 1 < bounds.x1);
943        assert!(bounds.height() >= 2);
944
945        let get = |x, y| i16::from(surface.get_pixel(x, y).a);
946        let y = bounds.y1 as u32 - 1;
947
948        let top_left = get(x - 1, y - 1);
949        let top = get(x, y - 1);
950        let top_right = get(x + 1, y - 1);
951        let left = get(x - 1, y);
952        let center = get(x, y);
953        let right = get(x + 1, y);
954
955        Self::new(
956            1. / 3.,
957            -top_left + top_right - 2 * left + 2 * right,
958            1. / 2.,
959            -top_left - 2 * top - top_right + left + 2 * center + right,
960        )
961    }
962
963    /// Computes and returns the normal vector for the bottom right pixel for light filters.
964    #[inline]
965    pub fn bottom_right(surface: &SharedImageSurface, bounds: IRect) -> Normal {
966        // Surface needs to be at least 2×2.
967        assert!(bounds.width() >= 2);
968        assert!(bounds.height() >= 2);
969
970        let get = |x, y| i16::from(surface.get_pixel(x, y).a);
971        let (x, y) = (bounds.x1 as u32 - 1, bounds.y1 as u32 - 1);
972
973        let top_left = get(x - 1, y - 1);
974        let top = get(x, y - 1);
975        let left = get(x - 1, y);
976        let center = get(x, y);
977
978        Self::new(
979            2. / 3.,
980            -top_left + top - 2 * left + 2 * center,
981            2. / 3.,
982            -top_left - 2 * top + left + 2 * center,
983        )
984    }
985}
986
987#[cfg(test)]
988mod tests {
989    use super::*;
990
991    use crate::borrow_element_as;
992    use crate::document::Document;
993
994    #[test]
995    fn extracts_light_source() {
996        let document = Document::load_from_bytes(
997            br#"<?xml version="1.0" encoding="UTF-8"?>
998<svg xmlns="http://www.w3.org/2000/svg">
999  <filter id="filter">
1000    <feDiffuseLighting id="diffuse_distant">
1001      <feDistantLight azimuth="0.0" elevation="45.0"/>
1002    </feDiffuseLighting>
1003
1004    <feSpecularLighting id="specular_point">
1005      <fePointLight x="1.0" y="2.0" z="3.0"/>
1006    </feSpecularLighting>
1007
1008    <feDiffuseLighting id="diffuse_spot">
1009      <feSpotLight x="1.0" y="2.0" z="3.0"
1010                   pointsAtX="4.0" pointsAtY="5.0" pointsAtZ="6.0"
1011                   specularExponent="7.0" limitingConeAngle="8.0"/>
1012    </feDiffuseLighting>
1013  </filter>
1014</svg>
1015"#,
1016        );
1017        let mut acquired_nodes = AcquiredNodes::new(&document, None::<gio::Cancellable>);
1018
1019        let node = document.lookup_internal_node("diffuse_distant").unwrap();
1020        let lighting = borrow_element_as!(node, FeDiffuseLighting);
1021        let resolved = lighting.resolve(&mut acquired_nodes, &node).unwrap();
1022        let ResolvedPrimitive { params, .. } = resolved.first().unwrap();
1023        let diffuse_lighting = match params {
1024            PrimitiveParams::DiffuseLighting(l) => l,
1025            _ => unreachable!(),
1026        };
1027        assert_eq!(
1028            diffuse_lighting.light.source,
1029            UntransformedLightSource::Distant(FeDistantLight {
1030                azimuth: 0.0,
1031                elevation: 45.0,
1032            })
1033        );
1034
1035        let node = document.lookup_internal_node("specular_point").unwrap();
1036        let lighting = borrow_element_as!(node, FeSpecularLighting);
1037        let resolved = lighting.resolve(&mut acquired_nodes, &node).unwrap();
1038        let ResolvedPrimitive { params, .. } = resolved.first().unwrap();
1039        let specular_lighting = match params {
1040            PrimitiveParams::SpecularLighting(l) => l,
1041            _ => unreachable!(),
1042        };
1043        assert_eq!(
1044            specular_lighting.light.source,
1045            UntransformedLightSource::Point(FePointLight {
1046                x: 1.0,
1047                y: 2.0,
1048                z: 3.0,
1049            })
1050        );
1051
1052        let node = document.lookup_internal_node("diffuse_spot").unwrap();
1053        let lighting = borrow_element_as!(node, FeDiffuseLighting);
1054        let resolved = lighting.resolve(&mut acquired_nodes, &node).unwrap();
1055        let ResolvedPrimitive { params, .. } = resolved.first().unwrap();
1056        let diffuse_lighting = match params {
1057            PrimitiveParams::DiffuseLighting(l) => l,
1058            _ => unreachable!(),
1059        };
1060        assert_eq!(
1061            diffuse_lighting.light.source,
1062            UntransformedLightSource::Spot(FeSpotLight {
1063                x: 1.0,
1064                y: 2.0,
1065                z: 3.0,
1066                points_at_x: 4.0,
1067                points_at_y: 5.0,
1068                points_at_z: 6.0,
1069                specular_exponent: 7.0,
1070                limiting_cone_angle: Some(8.0),
1071            })
1072        );
1073    }
1074}