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
7use super::{f32_bound, ImageRefMut};
8use usvg::ApproxZeroUlps;
9
10const RAND_M: i32 = 2147483647; // 2**31 - 1
11const RAND_A: i32 = 16807; // 7**5; primitive root of m
12const RAND_Q: i32 = 127773; // m / a
13const RAND_R: i32 = 2836; // m % a
14const B_SIZE: usize = 0x100;
15const B_SIZE_32: i32 = 0x100;
16const B_LEN: usize = B_SIZE + B_SIZE + 2;
17const BM: i32 = 0xff;
18const PERLIN_N: i32 = 0x1000;
19
20#[derive(Clone, Copy)]
21struct 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.
34pub 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
94fn 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
143fn 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
225fn 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
288fn 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]
298fn s_curve(t: f64) -> f64 {
299 t * t * (3.0 - 2.0 * t)
300}
301
302#[inline]
303fn lerp(t: f64, a: f64, b: f64) -> f64 {
304 a + t * (b - a)
305}
306