1use image::{DynamicImage, GrayImage, RgbImage};
2
3pub const DEFAULT_STRENGTH: f32 = 6.0;
9
10struct AdjPixels {
11 nw: f32, n : f32, ne: f32,
12 w: f32, e: f32,
13 sw: f32, s : f32, se: f32,
14}
15
16impl AdjPixels {
17 #[allow(clippy::many_single_char_names, clippy::absurd_extreme_comparisons)]
19 fn new(x: u32, y: u32, img: &GrayImage) -> Self {
20 let n = if y <= 0 { 0 } else { y-1 };
21 let s = if y >= (img.height()-1) { img.height()-1 } else { y+1 };
22 let w = if x <= 0 { 0 } else { x-1 };
23 let e = if x >= (img.width()-1) { img.width()-1 } else { x+1 };
24
25 AdjPixels {
26 nw: fetch_pixel(n,w,img),
27 n : fetch_pixel(n,x,img),
28 ne: fetch_pixel(n,e,img),
29 w: fetch_pixel(y,w,img),
30
31 e: fetch_pixel(y,e,img),
32 sw: fetch_pixel(s,w,img),
33 s : fetch_pixel(s,x,img),
34 se: fetch_pixel(s,e,img),
35 }
36 }
37
38 fn x_normals(&self) -> f32 {
41 -( self.se-self.sw
42 + 2.0*(self.e -self.w )
43 + self.ne-self.nw
44 )
45 }
46
47 fn y_normals(&self) -> f32 {
50 -( self.nw-self.sw
51 + 2.0*(self.n -self.s )
52 + self.ne-self.se
53 )
54 }
55}
56
57fn fetch_pixel(y: u32, x: u32, img: &GrayImage) -> f32 {
61 (img.get_pixel(x,y)[0] as f32)/255.0
62}
63
64pub fn map_normals(img: &DynamicImage) -> RgbImage {
67 map_normals_with_strength(img, DEFAULT_STRENGTH)
68}
69
70pub fn map_normals_with_strength(img: &DynamicImage, strength: f32) -> RgbImage {
72 let img = img.clone().into_luma8();
73 let mut normal_map = RgbImage::new(img.width(), img.height());
74
75 for (x, y, p) in normal_map.enumerate_pixels_mut() {
76 let mut new_p = [0.0, 0.0, 0.0];
77 let s = AdjPixels::new(x,y,&img);
78
79 new_p[0] = s.x_normals();
80 new_p[1] = s.y_normals();
81 new_p[2] = 1.0/strength;
82
83 let new_p = scale_normalized_to_0_to_1(&normalize(new_p));
84
85 p[0] = (new_p[0]*255.0) as u8;
86 p[1] = (new_p[1]*255.0) as u8;
87 p[2] = (new_p[2]*255.0) as u8;
88 }
89 normal_map
90}
91
92fn normalize(v: [f32;3]) -> [f32;3] {
93 let v_mag = (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]).sqrt();
94 [v[0]/v_mag, v[1]/v_mag, v[2]/v_mag]
95}
96
97fn scale_normalized_to_0_to_1(v: &[f32;3]) -> [f32;3] {
98 [
99 v[0] * 0.5 + 0.5,
100 v[1] * 0.5 + 0.5,
101 v[2] * 0.5 + 0.5,
102 ]
103}
104
105#[cfg(test)]
106mod tests {
107 use super::map_normals_with_strength;
108
109 #[test]
110 fn shapes_bmp_regression_test() {
111 let height_map = image::open("./samples/shapes.bmp").unwrap();
112 let test_normal = map_normals_with_strength(&height_map, 3.14);
113 let reference_normal = image::open("./samples/shapes_normal_strength_3.14.png").unwrap().into_rgb8();
114 assert_eq!(reference_normal.width(), test_normal.width());
115 assert_eq!(reference_normal.height(), test_normal.height());
116 for (ref_pixel, test_pixel) in reference_normal.pixels().zip(test_normal.pixels()) {
117 assert_eq!(ref_pixel, test_pixel);
118 }
119 }
120
121 #[test]
122 fn world_regression_test() {
123 let height_map = image::open("./samples/gebco_08_rev_elev_1080x540.png").unwrap();
124 let test_normal = map_normals_with_strength(&height_map, 6.0);
125 let reference_normal = image::open("./samples/gebco_08_rev_elev_1080x540_normal.png").unwrap().into_rgb8();
126 assert_eq!(reference_normal.width(), test_normal.width());
127 assert_eq!(reference_normal.height(), test_normal.height());
128 for (ref_pixel, test_pixel) in reference_normal.pixels().zip(test_normal.pixels()) {
129 assert_eq!(ref_pixel, test_pixel);
130 }
131 }
132}