resvg/filter/
lighting.rs

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