1 | // Copyright 2020 the Resvg Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | use super::{f32_bound, ImageRef, ImageRefMut}; |
5 | use rgb::RGBA8; |
6 | use usvg::filter::{DiffuseLighting, LightSource, SpecularLighting}; |
7 | use usvg::{ApproxEqUlps, ApproxZeroUlps, Color}; |
8 | |
9 | const FACTOR_1_2: f32 = 1.0 / 2.0; |
10 | const FACTOR_1_3: f32 = 1.0 / 3.0; |
11 | const FACTOR_1_4: f32 = 1.0 / 4.0; |
12 | const FACTOR_2_3: f32 = 2.0 / 3.0; |
13 | |
14 | #[derive (Clone, Copy, Debug)] |
15 | struct Vector2 { |
16 | x: f32, |
17 | y: f32, |
18 | } |
19 | |
20 | impl 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 | |
32 | impl 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)] |
45 | struct Vector3 { |
46 | x: f32, |
47 | y: f32, |
48 | z: f32, |
49 | } |
50 | |
51 | impl 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 | |
82 | impl 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 | |
95 | impl 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)] |
109 | struct Normal { |
110 | factor: Vector2, |
111 | normal: Vector2, |
112 | } |
113 | |
114 | impl 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. |
134 | pub 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. |
179 | pub 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 | |
231 | fn 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 | |
313 | fn 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 | |
344 | fn 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 | |
358 | fn 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 | |
372 | fn 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 | |
386 | fn 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 | |
400 | fn 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 | |
416 | fn 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 | |
432 | fn 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 | |
448 | fn 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 | |
464 | fn 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 | |
482 | fn calc_diffuse_alpha(_: u8, _: u8, _: u8) -> u8 { |
483 | 255 |
484 | } |
485 | |
486 | fn 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 | |