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 | |
5 | use super::{f32_bound, ImageRef, ImageRefMut}; |
6 | use rgb::RGBA8; |
7 | use usvg::filter::{DiffuseLighting, LightSource, SpecularLighting}; |
8 | use usvg::{ApproxEqUlps, ApproxZeroUlps, Color}; |
9 | |
10 | const FACTOR_1_2: f32 = 1.0 / 2.0; |
11 | const FACTOR_1_3: f32 = 1.0 / 3.0; |
12 | const FACTOR_1_4: f32 = 1.0 / 4.0; |
13 | const FACTOR_2_3: f32 = 2.0 / 3.0; |
14 | |
15 | #[derive (Clone, Copy, Debug)] |
16 | struct Vector2 { |
17 | x: f32, |
18 | y: f32, |
19 | } |
20 | |
21 | impl 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 | |
33 | impl 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)] |
46 | struct Vector3 { |
47 | x: f32, |
48 | y: f32, |
49 | z: f32, |
50 | } |
51 | |
52 | impl 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 | |
83 | impl 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 | |
96 | impl 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)] |
110 | struct Normal { |
111 | factor: Vector2, |
112 | normal: Vector2, |
113 | } |
114 | |
115 | impl 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. |
135 | pub 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. |
180 | pub 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 | |
232 | fn 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 | |
314 | fn 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 | |
345 | fn 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 | |
359 | fn 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 | |
373 | fn 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 | |
387 | fn 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 | |
401 | fn 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 | |
417 | fn 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 | |
433 | fn 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 | |
449 | fn 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 | |
465 | fn 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 | |
483 | fn calc_diffuse_alpha(_: u8, _: u8, _: u8) -> u8 { |
484 | 255 |
485 | } |
486 | |
487 | fn 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 | |