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 | #![allow (clippy::needless_range_loop)] |
6 | |
7 | use super::{f32_bound, ImageRefMut}; |
8 | use usvg::ApproxZeroUlps; |
9 | |
10 | const RAND_M: i32 = 2147483647; // 2**31 - 1 |
11 | const RAND_A: i32 = 16807; // 7**5; primitive root of m |
12 | const RAND_Q: i32 = 127773; // m / a |
13 | const RAND_R: i32 = 2836; // m % a |
14 | const B_SIZE: usize = 0x100; |
15 | const B_SIZE_32: i32 = 0x100; |
16 | const B_LEN: usize = B_SIZE + B_SIZE + 2; |
17 | const BM: i32 = 0xff; |
18 | const PERLIN_N: i32 = 0x1000; |
19 | |
20 | #[derive (Clone, Copy)] |
21 | struct StitchInfo { |
22 | width: i32, // How much to subtract to wrap for stitching. |
23 | height: i32, |
24 | wrap_x: i32, // Minimum value to wrap. |
25 | wrap_y: i32, |
26 | } |
27 | |
28 | /// Applies a turbulence filter. |
29 | /// |
30 | /// `dest` image pixels will have an **unpremultiplied alpha**. |
31 | /// |
32 | /// - `offset_x` and `offset_y` indicate filter region offset. |
33 | /// - `sx` and `sy` indicate canvas scale. |
34 | pub fn apply( |
35 | offset_x: f64, |
36 | offset_y: f64, |
37 | sx: f64, |
38 | sy: f64, |
39 | base_frequency_x: f64, |
40 | base_frequency_y: f64, |
41 | num_octaves: u32, |
42 | seed: i32, |
43 | stitch_tiles: bool, |
44 | fractal_noise: bool, |
45 | dest: ImageRefMut, |
46 | ) { |
47 | let (lattice_selector, gradient) = init(seed); |
48 | let width = dest.width; |
49 | let height = dest.height; |
50 | let mut x = 0; |
51 | let mut y = 0; |
52 | for pixel in dest.data.iter_mut() { |
53 | let turb = |channel| { |
54 | let (tx, ty) = ((x as f64 + offset_x) / sx, (y as f64 + offset_y) / sy); |
55 | let n = turbulence( |
56 | channel, |
57 | tx, |
58 | ty, |
59 | x as f64, |
60 | y as f64, |
61 | width as f64, |
62 | height as f64, |
63 | base_frequency_x, |
64 | base_frequency_y, |
65 | num_octaves, |
66 | fractal_noise, |
67 | stitch_tiles, |
68 | &lattice_selector, |
69 | &gradient, |
70 | ); |
71 | |
72 | let n = if fractal_noise { |
73 | (n * 255.0 + 255.0) / 2.0 |
74 | } else { |
75 | n * 255.0 |
76 | }; |
77 | |
78 | (f32_bound(0.0, n as f32, 255.0) + 0.5) as u8 |
79 | }; |
80 | |
81 | pixel.r = turb(0); |
82 | pixel.g = turb(1); |
83 | pixel.b = turb(2); |
84 | pixel.a = turb(3); |
85 | |
86 | x += 1; |
87 | if x == dest.width { |
88 | x = 0; |
89 | y += 1; |
90 | } |
91 | } |
92 | } |
93 | |
94 | fn init(mut seed: i32) -> (Vec<usize>, Vec<Vec<Vec<f64>>>) { |
95 | let mut lattice_selector = vec![0; B_LEN]; |
96 | let mut gradient = vec![vec![vec![0.0; 2]; B_LEN]; 4]; |
97 | |
98 | if seed <= 0 { |
99 | seed = -seed % (RAND_M - 1) + 1; |
100 | } |
101 | |
102 | if seed > RAND_M - 1 { |
103 | seed = RAND_M - 1; |
104 | } |
105 | |
106 | for k in 0..4 { |
107 | for i in 0..B_SIZE { |
108 | lattice_selector[i] = i; |
109 | for j in 0..2 { |
110 | seed = random(seed); |
111 | gradient[k][i][j] = |
112 | ((seed % (B_SIZE_32 + B_SIZE_32)) - B_SIZE_32) as f64 / B_SIZE_32 as f64; |
113 | } |
114 | |
115 | let s = (gradient[k][i][0] * gradient[k][i][0] + gradient[k][i][1] * gradient[k][i][1]) |
116 | .sqrt(); |
117 | |
118 | gradient[k][i][0] /= s; |
119 | gradient[k][i][1] /= s; |
120 | } |
121 | } |
122 | |
123 | for i in (1..B_SIZE).rev() { |
124 | let k = lattice_selector[i]; |
125 | seed = random(seed); |
126 | let j = (seed % B_SIZE_32) as usize; |
127 | lattice_selector[i] = lattice_selector[j]; |
128 | lattice_selector[j] = k; |
129 | } |
130 | |
131 | for i in 0..B_SIZE + 2 { |
132 | lattice_selector[B_SIZE + i] = lattice_selector[i]; |
133 | for g in gradient.iter_mut().take(4) { |
134 | for j in 0..2 { |
135 | g[B_SIZE + i][j] = g[i][j]; |
136 | } |
137 | } |
138 | } |
139 | |
140 | (lattice_selector, gradient) |
141 | } |
142 | |
143 | fn turbulence( |
144 | color_channel: usize, |
145 | mut x: f64, |
146 | mut y: f64, |
147 | tile_x: f64, |
148 | tile_y: f64, |
149 | tile_width: f64, |
150 | tile_height: f64, |
151 | mut base_freq_x: f64, |
152 | mut base_freq_y: f64, |
153 | num_octaves: u32, |
154 | fractal_sum: bool, |
155 | do_stitching: bool, |
156 | lattice_selector: &[usize], |
157 | gradient: &[Vec<Vec<f64>>], |
158 | ) -> f64 { |
159 | // Adjust the base frequencies if necessary for stitching. |
160 | let mut stitch = if do_stitching { |
161 | // When stitching tiled turbulence, the frequencies must be adjusted |
162 | // so that the tile borders will be continuous. |
163 | if !base_freq_x.approx_zero_ulps(4) { |
164 | let lo_freq = (tile_width * base_freq_x).floor() / tile_width; |
165 | let hi_freq = (tile_width * base_freq_x).ceil() / tile_width; |
166 | if base_freq_x / lo_freq < hi_freq / base_freq_x { |
167 | base_freq_x = lo_freq; |
168 | } else { |
169 | base_freq_x = hi_freq; |
170 | } |
171 | } |
172 | |
173 | if !base_freq_y.approx_zero_ulps(4) { |
174 | let lo_freq = (tile_height * base_freq_y).floor() / tile_height; |
175 | let hi_freq = (tile_height * base_freq_y).ceil() / tile_height; |
176 | if base_freq_y / lo_freq < hi_freq / base_freq_y { |
177 | base_freq_y = lo_freq; |
178 | } else { |
179 | base_freq_y = hi_freq; |
180 | } |
181 | } |
182 | |
183 | // Set up initial stitch values. |
184 | let width = (tile_width * base_freq_x + 0.5) as i32; |
185 | let height = (tile_height * base_freq_y + 0.5) as i32; |
186 | let wrap_x = (tile_x * base_freq_x + PERLIN_N as f64 + width as f64) as i32; |
187 | let wrap_y = (tile_y * base_freq_y + PERLIN_N as f64 + height as f64) as i32; |
188 | Some(StitchInfo { |
189 | width, |
190 | height, |
191 | wrap_x, |
192 | wrap_y, |
193 | }) |
194 | } else { |
195 | None |
196 | }; |
197 | |
198 | let mut sum = 0.0; |
199 | x *= base_freq_x; |
200 | y *= base_freq_y; |
201 | let mut ratio = 1.0; |
202 | for _ in 0..num_octaves { |
203 | if fractal_sum { |
204 | sum += noise2(color_channel, x, y, lattice_selector, gradient, stitch) / ratio; |
205 | } else { |
206 | sum += noise2(color_channel, x, y, lattice_selector, gradient, stitch).abs() / ratio; |
207 | } |
208 | x *= 2.0; |
209 | y *= 2.0; |
210 | ratio *= 2.0; |
211 | |
212 | if let Some(ref mut stitch) = stitch { |
213 | // Update stitch values. Subtracting PerlinN before the multiplication and |
214 | // adding it afterward simplifies to subtracting it once. |
215 | stitch.width *= 2; |
216 | stitch.wrap_x = 2 * stitch.wrap_x - PERLIN_N; |
217 | stitch.height *= 2; |
218 | stitch.wrap_y = 2 * stitch.wrap_y - PERLIN_N; |
219 | } |
220 | } |
221 | |
222 | sum |
223 | } |
224 | |
225 | fn noise2( |
226 | color_channel: usize, |
227 | x: f64, |
228 | y: f64, |
229 | lattice_selector: &[usize], |
230 | gradient: &[Vec<Vec<f64>>], |
231 | stitch_info: Option<StitchInfo>, |
232 | ) -> f64 { |
233 | let t = x + PERLIN_N as f64; |
234 | let mut bx0 = t as i32; |
235 | let mut bx1 = bx0 + 1; |
236 | let rx0 = t - t as i64 as f64; |
237 | let rx1 = rx0 - 1.0; |
238 | let t = y + PERLIN_N as f64; |
239 | let mut by0 = t as i32; |
240 | let mut by1 = by0 + 1; |
241 | let ry0 = t - t as i64 as f64; |
242 | let ry1 = ry0 - 1.0; |
243 | |
244 | // If stitching, adjust lattice points accordingly. |
245 | if let Some(info) = stitch_info { |
246 | if bx0 >= info.wrap_x { |
247 | bx0 -= info.width; |
248 | } |
249 | |
250 | if bx1 >= info.wrap_x { |
251 | bx1 -= info.width; |
252 | } |
253 | |
254 | if by0 >= info.wrap_y { |
255 | by0 -= info.height; |
256 | } |
257 | |
258 | if by1 >= info.wrap_y { |
259 | by1 -= info.height; |
260 | } |
261 | } |
262 | |
263 | bx0 &= BM; |
264 | bx1 &= BM; |
265 | by0 &= BM; |
266 | by1 &= BM; |
267 | let i = lattice_selector[bx0 as usize]; |
268 | let j = lattice_selector[bx1 as usize]; |
269 | let b00 = lattice_selector[i + by0 as usize]; |
270 | let b10 = lattice_selector[j + by0 as usize]; |
271 | let b01 = lattice_selector[i + by1 as usize]; |
272 | let b11 = lattice_selector[j + by1 as usize]; |
273 | let sx = s_curve(rx0); |
274 | let sy = s_curve(ry0); |
275 | let q = &gradient[color_channel][b00]; |
276 | let u = rx0 * q[0] + ry0 * q[1]; |
277 | let q = &gradient[color_channel][b10]; |
278 | let v = rx1 * q[0] + ry0 * q[1]; |
279 | let a = lerp(sx, u, v); |
280 | let q = &gradient[color_channel][b01]; |
281 | let u = rx0 * q[0] + ry1 * q[1]; |
282 | let q = &gradient[color_channel][b11]; |
283 | let v = rx1 * q[0] + ry1 * q[1]; |
284 | let b = lerp(sx, u, v); |
285 | lerp(sy, a, b) |
286 | } |
287 | |
288 | fn random(seed: i32) -> i32 { |
289 | let mut result: i32 = RAND_A * (seed % RAND_Q) - RAND_R * (seed / RAND_Q); |
290 | if result <= 0 { |
291 | result += RAND_M; |
292 | } |
293 | |
294 | result |
295 | } |
296 | |
297 | #[inline ] |
298 | fn s_curve(t: f64) -> f64 { |
299 | t * t * (3.0 - 2.0 * t) |
300 | } |
301 | |
302 | #[inline ] |
303 | fn lerp(t: f64, a: f64, b: f64) -> f64 { |
304 | a + t * (b - a) |
305 | } |
306 | |