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: i16 = img.alpha_at(x:0, y:0);
346 let right: i16 = img.alpha_at(x:1, y:0);
347 let bottom: i16 = img.alpha_at(x:0, y:1);
348 let bottom_right: i16 = img.alpha_at(x:1, y:1);
349
350 Normal::new(
351 FACTOR_2_3,
352 FACTOR_2_3,
353 nx:-2 * center + 2 * right - bottom + bottom_right,
354 ny:-2 * center - right + 2 * bottom + bottom_right,
355 )
356}
357
358fn top_right_normal(img: ImageRef) -> Normal {
359 let left: i16 = img.alpha_at(x:img.width - 2, y:0);
360 let center: i16 = img.alpha_at(x:img.width - 1, y:0);
361 let bottom_left: i16 = img.alpha_at(x:img.width - 2, y:1);
362 let bottom: i16 = img.alpha_at(x:img.width - 1, y:1);
363
364 Normal::new(
365 FACTOR_2_3,
366 FACTOR_2_3,
367 nx:-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: i16 = img.alpha_at(x:0, y:img.height - 2);
374 let top_right: i16 = img.alpha_at(x:1, y:img.height - 2);
375 let center: i16 = img.alpha_at(x:0, y:img.height - 1);
376 let right: i16 = img.alpha_at(x:1, y:img.height - 1);
377
378 Normal::new(
379 FACTOR_2_3,
380 FACTOR_2_3,
381 -top + top_right - 2 * center + 2 * right,
382 ny:-2 * top - top_right + 2 * center + right,
383 )
384}
385
386fn bottom_right_normal(img: ImageRef) -> Normal {
387 let top_left: i16 = img.alpha_at(x:img.width - 2, y:img.height - 2);
388 let top: i16 = img.alpha_at(x:img.width - 1, y:img.height - 2);
389 let left: i16 = img.alpha_at(x:img.width - 2, y:img.height - 1);
390 let center: i16 = img.alpha_at(x:img.width - 1, y: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: i16 = img.alpha_at(x:x - 1, y:0);
402 let center: i16 = img.alpha_at(x, y:0);
403 let right: i16 = img.alpha_at(x:x + 1, y:0);
404 let bottom_left: i16 = img.alpha_at(x:x - 1, y:1);
405 let bottom: i16 = img.alpha_at(x, y:1);
406 let bottom_right: i16 = img.alpha_at(x:x + 1, y:1);
407
408 Normal::new(
409 FACTOR_1_3,
410 FACTOR_1_2,
411 nx:-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: i16 = img.alpha_at(x:x - 1, y:img.height - 2);
418 let top: i16 = img.alpha_at(x, y:img.height - 2);
419 let top_right: i16 = img.alpha_at(x:x + 1, y:img.height - 2);
420 let left: i16 = img.alpha_at(x:x - 1, y:img.height - 1);
421 let center: i16 = img.alpha_at(x, y:img.height - 1);
422 let right: i16 = img.alpha_at(x:x + 1, y: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: i16 = img.alpha_at(x:0, y:y - 1);
434 let top_right: i16 = img.alpha_at(x:1, y:y - 1);
435 let center: i16 = img.alpha_at(x:0, y);
436 let right: i16 = img.alpha_at(x:1, y);
437 let bottom: i16 = img.alpha_at(x:0, y:y + 1);
438 let bottom_right: i16 = img.alpha_at(x:1, y: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 ny:-2 * top - top_right + 2 * bottom + bottom_right,
445 )
446}
447
448fn right_column_normal(img: ImageRef, y: u32) -> Normal {
449 let top_left: i16 = img.alpha_at(x:img.width - 2, y:y - 1);
450 let top: i16 = img.alpha_at(x:img.width - 1, y:y - 1);
451 let left: i16 = img.alpha_at(x:img.width - 2, y);
452 let center: i16 = img.alpha_at(x:img.width - 1, y);
453 let bottom_left: i16 = img.alpha_at(x:img.width - 2, y:y + 1);
454 let bottom: i16 = img.alpha_at(x:img.width - 1, y: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: i16 = img.alpha_at(x:x - 1, y:y - 1);
466 let top: i16 = img.alpha_at(x, y:y - 1);
467 let top_right: i16 = img.alpha_at(x:x + 1, y:y - 1);
468 let left: i16 = img.alpha_at(x:x - 1, y);
469 let right: i16 = img.alpha_at(x:x + 1, y);
470 let bottom_left: i16 = img.alpha_at(x:x - 1, y:y + 1);
471 let bottom: i16 = img.alpha_at(x, y:y + 1);
472 let bottom_right: i16 = img.alpha_at(x:x + 1, y: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(v1:max(r, g), v2:b)
489}
490