resvg/filter/
turbulence.rs

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 = 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}