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