resvg/filter/
lighting.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use super::{f32_bound, ImageRef, ImageRefMut};
6use rgb::RGBA8;
7use usvg::filter::{DiffuseLighting, LightSource, SpecularLighting};
8use usvg::{ApproxEqUlps, ApproxZeroUlps, Color};
9
10const FACTOR_1_2: f32 = 1.0 / 2.0;
11const FACTOR_1_3: f32 = 1.0 / 3.0;
12const FACTOR_1_4: f32 = 1.0 / 4.0;
13const FACTOR_2_3: f32 = 2.0 / 3.0;
14
15#[derive(Clone, Copy, Debug)]
16struct Vector2 {
17    x: f32,
18    y: f32,
19}
20
21impl Vector2 {
22    #[inline]
23    fn new(x: f32, y: f32) -> Self {
24        Vector2 { x, y }
25    }
26
27    #[inline]
28    fn approx_zero(&self) -> bool {
29        self.x.approx_zero_ulps(4) && self.y.approx_zero_ulps(4)
30    }
31}
32
33impl core::ops::Mul<f32> for Vector2 {
34    type Output = Self;
35
36    #[inline]
37    fn mul(self, c: f32) -> Self::Output {
38        Vector2 {
39            x: self.x * c,
40            y: self.y * c,
41        }
42    }
43}
44
45#[derive(Clone, Copy, Debug)]
46struct Vector3 {
47    x: f32,
48    y: f32,
49    z: f32,
50}
51
52impl Vector3 {
53    #[inline]
54    fn new(x: f32, y: f32, z: f32) -> Self {
55        Vector3 { x, y, z }
56    }
57
58    #[inline]
59    fn dot(&self, other: &Self) -> f32 {
60        self.x * other.x + self.y * other.y + self.z * other.z
61    }
62
63    #[inline]
64    fn length(&self) -> f32 {
65        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
66    }
67
68    #[inline]
69    fn normalized(&self) -> Option<Self> {
70        let length = self.length();
71        if !length.approx_zero_ulps(4) {
72            Some(Vector3 {
73                x: self.x / length,
74                y: self.y / length,
75                z: self.z / length,
76            })
77        } else {
78            None
79        }
80    }
81}
82
83impl core::ops::Add<Vector3> for Vector3 {
84    type Output = Self;
85
86    #[inline]
87    fn add(self, rhs: Vector3) -> Self::Output {
88        Vector3 {
89            x: self.x + rhs.x,
90            y: self.y + rhs.y,
91            z: self.z + rhs.z,
92        }
93    }
94}
95
96impl core::ops::Sub<Vector3> for Vector3 {
97    type Output = Self;
98
99    #[inline]
100    fn sub(self, rhs: Vector3) -> Self::Output {
101        Vector3 {
102            x: self.x - rhs.x,
103            y: self.y - rhs.y,
104            z: self.z - rhs.z,
105        }
106    }
107}
108
109#[derive(Clone, Copy, Debug)]
110struct Normal {
111    factor: Vector2,
112    normal: Vector2,
113}
114
115impl Normal {
116    #[inline]
117    fn new(factor_x: f32, factor_y: f32, nx: i16, ny: i16) -> Self {
118        Normal {
119            factor: Vector2::new(factor_x, factor_y),
120            normal: Vector2::new(-nx as f32, -ny as f32),
121        }
122    }
123}
124
125/// Renders a diffuse lighting.
126///
127/// - `src` pixels can have any alpha method, since only the alpha channel is used.
128/// - `dest` will have an **unpremultiplied alpha**.
129///
130/// Does nothing when `src` is less than 3x3.
131///
132/// # Panics
133///
134/// - When `src` and `dest` have different sizes.
135pub fn diffuse_lighting(
136    fe: &DiffuseLighting,
137    light_source: LightSource,
138    src: ImageRef,
139    dest: ImageRefMut,
140) {
141    assert!(src.width == dest.width && src.height == dest.height);
142
143    let light_factor = |normal: Normal, light_vector: Vector3| {
144        let k = if normal.normal.approx_zero() {
145            light_vector.z
146        } else {
147            let mut n = normal.normal * (fe.surface_scale() / 255.0);
148            n.x *= normal.factor.x;
149            n.y *= normal.factor.y;
150
151            let normal = Vector3::new(n.x, n.y, 1.0);
152
153            normal.dot(&light_vector) / normal.length()
154        };
155
156        fe.diffuse_constant() * k
157    };
158
159    apply(
160        light_source,
161        fe.surface_scale(),
162        fe.lighting_color(),
163        &light_factor,
164        calc_diffuse_alpha,
165        src,
166        dest,
167    );
168}
169
170/// Renders a specular lighting.
171///
172/// - `src` pixels can have any alpha method, since only the alpha channel is used.
173/// - `dest` will have a **premultiplied alpha**.
174///
175/// Does nothing when `src` is less than 3x3.
176///
177/// # Panics
178///
179/// - When `src` and `dest` have different sizes.
180pub fn specular_lighting(
181    fe: &SpecularLighting,
182    light_source: LightSource,
183    src: ImageRef,
184    dest: ImageRefMut,
185) {
186    assert!(src.width == dest.width && src.height == dest.height);
187
188    let light_factor = |normal: Normal, light_vector: Vector3| {
189        let h = light_vector + Vector3::new(0.0, 0.0, 1.0);
190        let h_length = h.length();
191
192        if h_length.approx_zero_ulps(4) {
193            return 0.0;
194        }
195
196        let k = if normal.normal.approx_zero() {
197            let n_dot_h = h.z / h_length;
198            if fe.specular_exponent().approx_eq_ulps(&1.0, 4) {
199                n_dot_h
200            } else {
201                n_dot_h.powf(fe.specular_exponent())
202            }
203        } else {
204            let mut n = normal.normal * (fe.surface_scale() / 255.0);
205            n.x *= normal.factor.x;
206            n.y *= normal.factor.y;
207
208            let normal = Vector3::new(n.x, n.y, 1.0);
209
210            let n_dot_h = normal.dot(&h) / normal.length() / h_length;
211            if fe.specular_exponent().approx_eq_ulps(&1.0, 4) {
212                n_dot_h
213            } else {
214                n_dot_h.powf(fe.specular_exponent())
215            }
216        };
217
218        fe.specular_constant() * k
219    };
220
221    apply(
222        light_source,
223        fe.surface_scale(),
224        fe.lighting_color(),
225        &light_factor,
226        calc_specular_alpha,
227        src,
228        dest,
229    );
230}
231
232fn apply(
233    light_source: LightSource,
234    surface_scale: f32,
235    lighting_color: Color,
236    light_factor: &dyn Fn(Normal, Vector3) -> f32,
237    calc_alpha: fn(u8, u8, u8) -> u8,
238    src: ImageRef,
239    mut dest: ImageRefMut,
240) {
241    if src.width < 3 || src.height < 3 {
242        return;
243    }
244
245    let width = src.width;
246    let height = src.height;
247
248    // `feDistantLight` has a fixed vector, so calculate it beforehand.
249    let mut light_vector = match light_source {
250        LightSource::DistantLight(light) => {
251            let azimuth = light.azimuth.to_radians();
252            let elevation = light.elevation.to_radians();
253            Vector3::new(
254                azimuth.cos() * elevation.cos(),
255                azimuth.sin() * elevation.cos(),
256                elevation.sin(),
257            )
258        }
259        _ => Vector3::new(1.0, 1.0, 1.0),
260    };
261
262    let mut calc = |nx, ny, normal: Normal| {
263        match light_source {
264            LightSource::DistantLight(_) => {}
265            LightSource::PointLight(ref light) => {
266                let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale;
267                let origin = Vector3::new(light.x, light.y, light.z);
268                let v = origin - Vector3::new(nx as f32, ny as f32, nz);
269                light_vector = v.normalized().unwrap_or(v);
270            }
271            LightSource::SpotLight(ref light) => {
272                let nz = src.alpha_at(nx, ny) as f32 / 255.0 * surface_scale;
273                let origin = Vector3::new(light.x, light.y, light.z);
274                let v = origin - Vector3::new(nx as f32, ny as f32, nz);
275                light_vector = v.normalized().unwrap_or(v);
276            }
277        }
278
279        let light_color = light_color(&light_source, lighting_color, light_vector);
280        let factor = light_factor(normal, light_vector);
281
282        let compute = |x| (f32_bound(0.0, x as f32 * factor, 255.0) + 0.5) as u8;
283
284        let r = compute(light_color.red);
285        let g = compute(light_color.green);
286        let b = compute(light_color.blue);
287        let a = calc_alpha(r, g, b);
288
289        *dest.pixel_at_mut(nx, ny) = RGBA8 { b, g, r, a };
290    };
291
292    calc(0, 0, top_left_normal(src));
293    calc(width - 1, 0, top_right_normal(src));
294    calc(0, height - 1, bottom_left_normal(src));
295    calc(width - 1, height - 1, bottom_right_normal(src));
296
297    for x in 1..width - 1 {
298        calc(x, 0, top_row_normal(src, x));
299        calc(x, height - 1, bottom_row_normal(src, x));
300    }
301
302    for y in 1..height - 1 {
303        calc(0, y, left_column_normal(src, y));
304        calc(width - 1, y, right_column_normal(src, y));
305    }
306
307    for y in 1..height - 1 {
308        for x in 1..width - 1 {
309            calc(x, y, interior_normal(src, x, y));
310        }
311    }
312}
313
314fn light_color(light: &LightSource, lighting_color: Color, light_vector: Vector3) -> Color {
315    match *light {
316        LightSource::DistantLight(_) | LightSource::PointLight(_) => lighting_color,
317        LightSource::SpotLight(ref light) => {
318            let origin = Vector3::new(light.x, light.y, light.z);
319            let direction = Vector3::new(light.points_at_x, light.points_at_y, light.points_at_z);
320            let direction = direction - origin;
321            let direction = direction.normalized().unwrap_or(direction);
322            let minus_l_dot_s = -light_vector.dot(&direction);
323            if minus_l_dot_s <= 0.0 {
324                return Color::black();
325            }
326
327            if let Some(limiting_cone_angle) = light.limiting_cone_angle {
328                if minus_l_dot_s < limiting_cone_angle.to_radians().cos() {
329                    return Color::black();
330                }
331            }
332
333            let factor = minus_l_dot_s.powf(light.specular_exponent.get());
334            let compute = |x| (f32_bound(0.0, x as f32 * factor, 255.0) + 0.5) as u8;
335
336            Color::new_rgb(
337                compute(lighting_color.red),
338                compute(lighting_color.green),
339                compute(lighting_color.blue),
340            )
341        }
342    }
343}
344
345fn top_left_normal(img: ImageRef) -> Normal {
346    let center = img.alpha_at(0, 0);
347    let right = img.alpha_at(1, 0);
348    let bottom = img.alpha_at(0, 1);
349    let bottom_right = img.alpha_at(1, 1);
350
351    Normal::new(
352        FACTOR_2_3,
353        FACTOR_2_3,
354        -2 * center + 2 * right - bottom + bottom_right,
355        -2 * center - right + 2 * bottom + bottom_right,
356    )
357}
358
359fn top_right_normal(img: ImageRef) -> Normal {
360    let left = img.alpha_at(img.width - 2, 0);
361    let center = img.alpha_at(img.width - 1, 0);
362    let bottom_left = img.alpha_at(img.width - 2, 1);
363    let bottom = img.alpha_at(img.width - 1, 1);
364
365    Normal::new(
366        FACTOR_2_3,
367        FACTOR_2_3,
368        -2 * left + 2 * center - bottom_left + bottom,
369        -left - 2 * center + bottom_left + 2 * bottom,
370    )
371}
372
373fn bottom_left_normal(img: ImageRef) -> Normal {
374    let top = img.alpha_at(0, img.height - 2);
375    let top_right = img.alpha_at(1, img.height - 2);
376    let center = img.alpha_at(0, img.height - 1);
377    let right = img.alpha_at(1, img.height - 1);
378
379    Normal::new(
380        FACTOR_2_3,
381        FACTOR_2_3,
382        -top + top_right - 2 * center + 2 * right,
383        -2 * top - top_right + 2 * center + right,
384    )
385}
386
387fn bottom_right_normal(img: ImageRef) -> Normal {
388    let top_left = img.alpha_at(img.width - 2, img.height - 2);
389    let top = img.alpha_at(img.width - 1, img.height - 2);
390    let left = img.alpha_at(img.width - 2, img.height - 1);
391    let center = img.alpha_at(img.width - 1, img.height - 1);
392
393    Normal::new(
394        FACTOR_2_3,
395        FACTOR_2_3,
396        -top_left + top - 2 * left + 2 * center,
397        -top_left - 2 * top + left + 2 * center,
398    )
399}
400
401fn top_row_normal(img: ImageRef, x: u32) -> Normal {
402    let left = img.alpha_at(x - 1, 0);
403    let center = img.alpha_at(x, 0);
404    let right = img.alpha_at(x + 1, 0);
405    let bottom_left = img.alpha_at(x - 1, 1);
406    let bottom = img.alpha_at(x, 1);
407    let bottom_right = img.alpha_at(x + 1, 1);
408
409    Normal::new(
410        FACTOR_1_3,
411        FACTOR_1_2,
412        -2 * left + 2 * right - bottom_left + bottom_right,
413        -left - 2 * center - right + bottom_left + 2 * bottom + bottom_right,
414    )
415}
416
417fn bottom_row_normal(img: ImageRef, x: u32) -> Normal {
418    let top_left = img.alpha_at(x - 1, img.height - 2);
419    let top = img.alpha_at(x, img.height - 2);
420    let top_right = img.alpha_at(x + 1, img.height - 2);
421    let left = img.alpha_at(x - 1, img.height - 1);
422    let center = img.alpha_at(x, img.height - 1);
423    let right = img.alpha_at(x + 1, img.height - 1);
424
425    Normal::new(
426        FACTOR_1_3,
427        FACTOR_1_2,
428        -top_left + top_right - 2 * left + 2 * right,
429        -top_left - 2 * top - top_right + left + 2 * center + right,
430    )
431}
432
433fn left_column_normal(img: ImageRef, y: u32) -> Normal {
434    let top = img.alpha_at(0, y - 1);
435    let top_right = img.alpha_at(1, y - 1);
436    let center = img.alpha_at(0, y);
437    let right = img.alpha_at(1, y);
438    let bottom = img.alpha_at(0, y + 1);
439    let bottom_right = img.alpha_at(1, y + 1);
440
441    Normal::new(
442        FACTOR_1_2,
443        FACTOR_1_3,
444        -top + top_right - 2 * center + 2 * right - bottom + bottom_right,
445        -2 * top - top_right + 2 * bottom + bottom_right,
446    )
447}
448
449fn right_column_normal(img: ImageRef, y: u32) -> Normal {
450    let top_left = img.alpha_at(img.width - 2, y - 1);
451    let top = img.alpha_at(img.width - 1, y - 1);
452    let left = img.alpha_at(img.width - 2, y);
453    let center = img.alpha_at(img.width - 1, y);
454    let bottom_left = img.alpha_at(img.width - 2, y + 1);
455    let bottom = img.alpha_at(img.width - 1, y + 1);
456
457    Normal::new(
458        FACTOR_1_2,
459        FACTOR_1_3,
460        -top_left + top - 2 * left + 2 * center - bottom_left + bottom,
461        -top_left - 2 * top + bottom_left + 2 * bottom,
462    )
463}
464
465fn interior_normal(img: ImageRef, x: u32, y: u32) -> Normal {
466    let top_left = img.alpha_at(x - 1, y - 1);
467    let top = img.alpha_at(x, y - 1);
468    let top_right = img.alpha_at(x + 1, y - 1);
469    let left = img.alpha_at(x - 1, y);
470    let right = img.alpha_at(x + 1, y);
471    let bottom_left = img.alpha_at(x - 1, y + 1);
472    let bottom = img.alpha_at(x, y + 1);
473    let bottom_right = img.alpha_at(x + 1, y + 1);
474
475    Normal::new(
476        FACTOR_1_4,
477        FACTOR_1_4,
478        -top_left + top_right - 2 * left + 2 * right - bottom_left + bottom_right,
479        -top_left - 2 * top - top_right + bottom_left + 2 * bottom + bottom_right,
480    )
481}
482
483fn calc_diffuse_alpha(_: u8, _: u8, _: u8) -> u8 {
484    255
485}
486
487fn calc_specular_alpha(r: u8, g: u8, b: u8) -> u8 {
488    use core::cmp::max;
489    max(max(r, g), b)
490}