1#![warn(clippy::pedantic, clippy::nursery)]
2#![cfg_attr(not(test), no_std)]
3#[cfg(test)]
4mod tests {
5 use super::*;
6 #[test]
7 fn rawwood_12() {
8 for i in 0..5 {
9 let raw_wood = wood(584, 668, 40., 12., &WOOD_1);
10 raw_wood
11 .unwrap()
12 .save(format!("rawwood_12_{}.png", i))
13 .unwrap();
14 }
15 }
16 #[test]
17 fn brightwood12() {
18 for i in 0..5 {
19 let bright = wood(584, 668, 40., 12., &BRIGHT_WOOD);
20 bright
21 .unwrap()
22 .save(format!("brightwood_12_{}.png", i))
23 .unwrap();
24 }
25 }
26
27 #[test]
28 fn rawwood_24() {
29 for i in 0..5 {
30 let raw_wood = wood(584, 668, 40., 24., &WOOD_1);
31 raw_wood
32 .unwrap()
33 .save(format!("rawwood_24_{}.png", i))
34 .unwrap();
35 }
36 }
37}
38
39extern crate alloc;
40use alloc::vec::Vec;
41
42struct Noise {
43 width: usize,
44 height: usize,
45 data: Vec<Vec<f64>>,
46}
47
48use rand::distributions::{Distribution, Uniform};
49
50impl Noise {
51 fn gen_noise(width: usize, height: usize) -> Self {
52 let between = Uniform::from(0.0..1.0);
54 let mut rng = rand::thread_rng();
55 let mut noise: Vec<Vec<f64>> = Vec::new();
56 for _ in 0..height {
57 let mut vec = Vec::new();
58 for _ in 0..width {
59 vec.push(between.sample(&mut rng));
60 }
61 noise.push(vec);
62 }
63
64 Self {
65 width,
66 height,
67 data: noise,
68 }
69 }
70
71 fn sample_smooth_noise(&self, x: f64, y: f64) -> f64 {
72 let fract_x = x.fract();
74 let fract_y = y.fract();
75 let width = self.width;
76 let height = self.height;
77
78 let x1: usize = ((x as usize) + width) % width;
80 let y1: usize = ((y as usize) + height) % height;
81
82 let x2: usize = (x1 + width - 1) % width;
84 let y2: usize = (y1 + height - 1) % height;
85
86 let mut value = 0.0;
88 value += fract_x * fract_y * self.data[y1][x1];
89 value += (1. - fract_x) * fract_y * self.data[y1][x2];
90 value += fract_x * (1. - fract_y) * self.data[y2][x1];
91 value += (1. - fract_x) * (1. - fract_y) * self.data[y2][x2];
92
93 value
94 }
95
96 fn turbulence(&self, x: f64, y: f64, initial_size: f64) -> f64 {
97 let mut value = 0.0_f64;
99 let mut size = initial_size;
100
101 while size >= 1. {
102 value += self.sample_smooth_noise(x / size, y / size) * size;
103 size /= 2.0;
104 }
105
106 128.0 * value / initial_size
107 }
108}
109
110pub struct WoodProfile {
111 brightness_adjustment: i32,
112 dark_color: [u8; 3],
113 light_color: [u8; 3],
114}
115
116pub const BRIGHT_WOOD: WoodProfile = WoodProfile {
117 brightness_adjustment: 20,
118 dark_color: [120, 70, 70],
119 light_color: [208, 158, 70],
120};
121
122pub const WOOD_1: WoodProfile = WoodProfile {
123 brightness_adjustment: 0,
124 dark_color: [120, 70, 70],
125 light_color: [208, 158, 70],
126};
127
128#[must_use]
136pub fn wood(
137 width: u32,
138 height: u32,
139 offsetstdev: f64,
140 length_scale: f64,
141 wood_profile: &WoodProfile,
142) -> Result<image::RgbImage, rand_distr::NormalError> {
143 use rand::Rng;
144 let mut imgbuf = image::RgbImage::new(width, height);
145
146 let noise = Noise::gen_noise(width as usize, height as usize);
147
148 let turb = 14.6; let turb_size = 32.0; let mut rng = rand::thread_rng();
153 let distr = rand_distr::Normal::new(0., offsetstdev)?;
154 let offset_x = rng.sample(distr);
155 let offset_y = rng.sample(distr);
156
157 let phase = rng.sample(Uniform::from(0.0..core::f64::consts::PI));
159
160 for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
161 let x_value_times_scale = f64::from(x) - f64::from(width) / 2. + offset_x; let y_value_times_scale = f64::from(y) - f64::from(height) / 2. + offset_y; let dist_value_times_scale = x_value_times_scale.hypot(y_value_times_scale)
164 + turb * noise.turbulence(f64::from(x), f64::from(y), turb_size) / 256.0;
165
166 #[allow(clippy::cast_possible_truncation)]
167 let sine_value = (dist_value_times_scale / length_scale)
168 .mul_add(core::f64::consts::PI, phase)
169 .sin()
170 .abs()
171 .powf(0.4) as f32;
172 *pixel = lerp_pixel(
173 image::Rgb(wood_profile.dark_color),
174 image::Rgb(wood_profile.light_color),
175 sine_value,
176 );
177 }
178
179 Ok(image::imageops::colorops::brighten(
180 &imgbuf,
181 wood_profile.brightness_adjustment,
182 ))
183}
184
185use interpolation::Lerp;
186
187fn lerp_pixel(a: image::Rgb<u8>, b: image::Rgb<u8>, t: f32) -> image::Rgb<u8> {
188 image::Rgb([
189 (a.0[0]).lerp(&b.0[0], &t),
190 (a.0[1]).lerp(&b.0[1], &t),
191 (a.0[2]).lerp(&b.0[2], &t),
192 ])
193}