Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
nr-filter-turbulence.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * feTurbulence filter primitive renderer
4 *
5 * Authors:
6 * World Wide Web Consortium <http://www.w3.org/>
7 * Felipe Corrêa da Silva Sanches <juca@members.fsf.org>
8 *
9 * This file has a considerable amount of code adapted from
10 * the W3C SVG filter specs, available at:
11 * http://www.w3.org/TR/SVG11/filters.html#feTurbulence
12 *
13 * W3C original code is licensed under the terms of
14 * the (GPL compatible) W3C® SOFTWARE NOTICE AND LICENSE:
15 * http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
16 *
17 * Copyright (C) 2007 authors
18 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
19 */
20
22#include "display/cairo-utils.h"
23#include "display/nr-filter.h"
27#include <cmath>
28
29namespace Inkscape {
30namespace Filters{
31
32class TurbulenceGenerator
33{
34public:
35 TurbulenceGenerator()
36 : _tile()
37 , _baseFreq()
38 , _latticeSelector()
39 , _gradient()
40 , _seed(0)
41 , _octaves(0)
42 , _stitchTiles(false)
43 , _wrapx(0)
44 , _wrapy(0)
45 , _wrapw(0)
46 , _wraph(0)
47 , _inited(false)
48 , _fractalnoise(false) {}
49
50 void init(long seed, Geom::Rect const &tile, Geom::Point const &freq, bool stitch, bool fractalnoise, int octaves)
51 {
52 // setup random number generator
53 _setupSeed(seed);
54
55 // set values
56 _tile = tile;
57 _baseFreq = freq;
58 _stitchTiles = stitch;
59 _fractalnoise = fractalnoise;
60 _octaves = octaves;
61
62 int i;
63 for (int k = 0; k < 4; ++k) {
64 for (i = 0; i < BSize; ++i) {
65 _latticeSelector[i] = i;
66
67 do {
68 _gradient[i][k][0] = static_cast<double>(_random() % (BSize * 2) - BSize) / BSize;
69 _gradient[i][k][1] = static_cast<double>(_random() % (BSize * 2) - BSize) / BSize;
70 } while (_gradient[i][k][0] == 0 && _gradient[i][k][1] == 0);
71
72 // normalize gradient
73 double s = hypot(_gradient[i][k][0], _gradient[i][k][1]);
74 _gradient[i][k][0] /= s;
75 _gradient[i][k][1] /= s;
76 }
77 }
78 while (--i) {
79 // shuffle lattice selectors
80 int j = _random() % BSize;
81 std::swap(_latticeSelector[i], _latticeSelector[j]);
82 }
83
84 // fill out the remaining part of the gradient
85 for (i = 0; i < BSize + 2; ++i)
86 {
87 _latticeSelector[BSize + i] = _latticeSelector[i];
88
89 for (int k = 0; k < 4; ++k) {
90 _gradient[BSize + i][k][0] = _gradient[i][k][0];
91 _gradient[BSize + i][k][1] = _gradient[i][k][1];
92 }
93 }
94
95 // When stitching tiled turbulence, the frequencies must be adjusted
96 // so that the tile borders will be continuous.
97 if (_stitchTiles) {
98 if (_baseFreq[Geom::X] != 0.0) {
99 double freq = _baseFreq[Geom::X];
100 double lo = std::floor(_tile.width() * freq) / _tile.width();
101 double hi = std::ceil(_tile.width() * freq) / _tile.width();
102 _baseFreq[Geom::X] = freq / lo < hi / freq ? lo : hi;
103 }
104 if (_baseFreq[Geom::Y] != 0.0) {
105 double freq = _baseFreq[Geom::Y];
106 double lo = std::floor(_tile.height() * freq) / _tile.height();
107 double hi = std::ceil(_tile.height() * freq) / _tile.height();
108 _baseFreq[Geom::Y] = freq / lo < hi / freq ? lo : hi;
109 }
110
111 _wrapw = _tile.width() * _baseFreq[Geom::X] + 0.5;
112 _wraph = _tile.height() * _baseFreq[Geom::Y] + 0.5;
113 _wrapx = _tile.left() * _baseFreq[Geom::X] + PerlinOffset + _wrapw;
114 _wrapy = _tile.top() * _baseFreq[Geom::Y] + PerlinOffset + _wraph;
115 }
116 _inited = true;
117 }
118
119 G_GNUC_PURE
120 guint32 turbulencePixel(Geom::Point const &p) const
121 {
122 int wrapx = _wrapx, wrapy = _wrapy, wrapw = _wrapw, wraph = _wraph;
123
124 double pixel[4];
125 double x = p[Geom::X] * _baseFreq[Geom::X];
126 double y = p[Geom::Y] * _baseFreq[Geom::Y];
127 double ratio = 1.0;
128
129 for (double & k : pixel)
130 k = 0.0;
131
132 for (int octave = 0; octave < _octaves; ++octave)
133 {
134 double tx = x + PerlinOffset;
135 double bx = floor(tx);
136 double rx0 = tx - bx, rx1 = rx0 - 1.0;
137 int bx0 = bx, bx1 = bx0 + 1;
138
139 double ty = y + PerlinOffset;
140 double by = floor(ty);
141 double ry0 = ty - by, ry1 = ry0 - 1.0;
142 int by0 = by, by1 = by0 + 1;
143
144 if (_stitchTiles) {
145 if (bx0 >= wrapx) bx0 -= wrapw;
146 if (bx1 >= wrapx) bx1 -= wrapw;
147 if (by0 >= wrapy) by0 -= wraph;
148 if (by1 >= wrapy) by1 -= wraph;
149 }
150 bx0 &= BMask;
151 bx1 &= BMask;
152 by0 &= BMask;
153 by1 &= BMask;
154
155 int i = _latticeSelector[bx0];
156 int j = _latticeSelector[bx1];
157 int b00 = _latticeSelector[i + by0];
158 int b01 = _latticeSelector[i + by1];
159 int b10 = _latticeSelector[j + by0];
160 int b11 = _latticeSelector[j + by1];
161
162 double sx = _scurve(rx0);
163 double sy = _scurve(ry0);
164
165 double result[4];
166 // channel numbering: R=0, G=1, B=2, A=3
167 for (int k = 0; k < 4; ++k) {
168 double const *qxa = _gradient[b00][k];
169 double const *qxb = _gradient[b10][k];
170 double a = _lerp(sx, rx0 * qxa[0] + ry0 * qxa[1],
171 rx1 * qxb[0] + ry0 * qxb[1]);
172 double const *qya = _gradient[b01][k];
173 double const *qyb = _gradient[b11][k];
174 double b = _lerp(sx, rx0 * qya[0] + ry1 * qya[1],
175 rx1 * qyb[0] + ry1 * qyb[1]);
176 result[k] = _lerp(sy, a, b);
177 }
178
179 if (_fractalnoise) {
180 for (int k = 0; k < 4; ++k)
181 pixel[k] += result[k] / ratio;
182 } else {
183 for (int k = 0; k < 4; ++k)
184 pixel[k] += fabs(result[k]) / ratio;
185 }
186
187 x *= 2;
188 y *= 2;
189 ratio *= 2;
190
191 if(_stitchTiles)
192 {
193 // Update stitch values. Subtracting PerlinOffset before the multiplication and
194 // adding it afterward simplifies to subtracting it once.
195 wrapw *= 2;
196 wraph *= 2;
197 wrapx = wrapx*2 - PerlinOffset;
198 wrapy = wrapy*2 - PerlinOffset;
199 }
200 }
201
202 if (_fractalnoise) {
203 guint32 r = CLAMP_D_TO_U8((pixel[0]*255.0 + 255.0) / 2);
204 guint32 g = CLAMP_D_TO_U8((pixel[1]*255.0 + 255.0) / 2);
205 guint32 b = CLAMP_D_TO_U8((pixel[2]*255.0 + 255.0) / 2);
206 guint32 a = CLAMP_D_TO_U8((pixel[3]*255.0 + 255.0) / 2);
207 r = premul_alpha(r, a);
208 g = premul_alpha(g, a);
209 b = premul_alpha(b, a);
210 ASSEMBLE_ARGB32(pxout, a,r,g,b);
211 return pxout;
212 } else {
213 guint32 r = CLAMP_D_TO_U8(pixel[0]*255.0);
214 guint32 g = CLAMP_D_TO_U8(pixel[1]*255.0);
215 guint32 b = CLAMP_D_TO_U8(pixel[2]*255.0);
216 guint32 a = CLAMP_D_TO_U8(pixel[3]*255.0);
217 r = premul_alpha(r, a);
218 g = premul_alpha(g, a);
219 b = premul_alpha(b, a);
220 ASSEMBLE_ARGB32(pxout, a,r,g,b);
221 return pxout;
222 }
223 }
224
225 //G_GNUC_PURE
226 /*guint32 turbulencePixel(Geom::Point const &p) const {
227 if (!_fractalnoise) {
228 guint32 r = CLAMP_D_TO_U8(turbulence(0, p)*255.0);
229 guint32 g = CLAMP_D_TO_U8(turbulence(1, p)*255.0);
230 guint32 b = CLAMP_D_TO_U8(turbulence(2, p)*255.0);
231 guint32 a = CLAMP_D_TO_U8(turbulence(3, p)*255.0);
232 r = premul_alpha(r, a);
233 g = premul_alpha(g, a);
234 b = premul_alpha(b, a);
235 ASSEMBLE_ARGB32(pxout, a,r,g,b);
236 return pxout;
237 } else {
238 guint32 r = CLAMP_D_TO_U8((turbulence(0, p)*255.0 + 255.0) / 2);
239 guint32 g = CLAMP_D_TO_U8((turbulence(1, p)*255.0 + 255.0) / 2);
240 guint32 b = CLAMP_D_TO_U8((turbulence(2, p)*255.0 + 255.0) / 2);
241 guint32 a = CLAMP_D_TO_U8((turbulence(3, p)*255.0 + 255.0) / 2);
242 r = premul_alpha(r, a);
243 g = premul_alpha(g, a);
244 b = premul_alpha(b, a);
245 ASSEMBLE_ARGB32(pxout, a,r,g,b);
246 return pxout;
247 }
248 }*/
249
250 bool ready() const { return _inited; }
251 void dirty() { _inited = false; }
252
253private:
254 void _setupSeed(long seed)
255 {
256 _seed = seed;
257 if (_seed <= 0) _seed = -(_seed % (RAND_m - 1)) + 1;
258 if (_seed > RAND_m - 1) _seed = RAND_m - 1;
259 }
260
261 long _random()
262 {
263 /* Produces results in the range [1, 2**31 - 2].
264 * Algorithm is: r = (a * r) mod m
265 * where a = 16807 and m = 2**31 - 1 = 2147483647
266 * See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988
267 * To test: the algorithm should produce the result 1043618065
268 * as the 10,000th generated number if the original seed is 1. */
269 _seed = RAND_a * (_seed % RAND_q) - RAND_r * (_seed / RAND_q);
270 if (_seed <= 0) _seed += RAND_m;
271 return _seed;
272 }
273
274 static inline double _scurve(double t)
275 {
276 return t * t * (3.0 - 2.0 * t);
277 }
278
279 static inline double _lerp(double t, double a, double b)
280 {
281 return a + t * (b - a);
282 }
283
284 // random number generator constants
285 static long constexpr
286 RAND_m = 2147483647, // 2**31 - 1
287 RAND_a = 16807, // 7**5; primitive root of m
288 RAND_q = 127773, // m / a
289 RAND_r = 2836; // m % a
290
291 // other constants
292 static int constexpr BSize = 0x100;
293 static int constexpr BMask = 0xff;
294
295 static double constexpr PerlinOffset = 4096.0;
296
297 Geom::Rect _tile;
298 Geom::Point _baseFreq;
299 int _latticeSelector[2 * BSize + 2];
300 double _gradient[2 * BSize + 2][4][2];
301 long _seed;
302 int _octaves;
303 bool _stitchTiles;
304 int _wrapx;
305 int _wrapy;
306 int _wrapw;
307 int _wraph;
308 bool _inited;
309 bool _fractalnoise;
310};
311
313 : gen(std::make_unique<TurbulenceGenerator>())
314 , XbaseFrequency(0)
315 , YbaseFrequency(0)
316 , numOctaves(1)
317 , seed(0)
318 , updated(false)
319 , fTileWidth(10) //guessed
320 , fTileHeight(10) //guessed
321 , fTileX(1) //guessed
322 , fTileY(1) //guessed
323{
324}
325
327
328void FilterTurbulence::set_baseFrequency(int axis, double freq)
329{
330 if (axis == 0) XbaseFrequency = freq;
331 if (axis == 1) YbaseFrequency = freq;
332 gen->dirty();
333}
334
336{
337 numOctaves = num;
338 gen->dirty();
339}
340
342{
343 seed = s;
344 gen->dirty();
345}
346
348{
349 stitchTiles = st;
350 gen->dirty();
351}
352
354{
355 type = t;
356 gen->dirty();
357}
358
360{
361}
362
363struct Turbulence
364{
365 Turbulence(TurbulenceGenerator const &gen, Geom::Affine const &trans, int x0, int y0)
366 : _gen(gen)
367 , _trans(trans)
368 , _x0(x0), _y0(y0) {}
369
370 guint32 operator()(int x, int y)
371 {
372 Geom::Point point(x + _x0, y + _y0);
373 point *= _trans;
374 return _gen.turbulencePixel(point);
375 }
376
377private:
378 TurbulenceGenerator const &_gen;
379 Geom::Affine _trans;
380 int _x0, _y0;
381};
382
384{
385 cairo_surface_t *input = slot.getcairo(_input);
386 cairo_surface_t *out = ink_cairo_surface_create_same_size(input, CAIRO_CONTENT_COLOR_ALPHA);
387
388 // It is probably possible to render at a device scale greater than one
389 // but for the moment rendering at a device scale of one is the easiest.
390 // cairo_image_surface_get_width() returns width in pixels but
391 // cairo_surface_create_similar() requires width in device units so divide by device scale.
392 // We are rendering at a device scale of 1... so divide by device scale again!
393 double x_scale = 0;
394 double y_scale = 0;
395 cairo_surface_get_device_scale(input, &x_scale, &y_scale);
396 int width = ceil(cairo_image_surface_get_width( input)/x_scale/x_scale);
397 int height = ceil(cairo_image_surface_get_height(input)/y_scale/y_scale);
398 cairo_surface_t *temp = cairo_surface_create_similar (input, CAIRO_CONTENT_COLOR_ALPHA, width, height);
399 cairo_surface_set_device_scale( temp, 1, 1 );
400
401 // color_interpolation_filter is determined by CSS value (see spec. Turbulence).
403
404 if (!gen->ready()) {
407 gen->init(seed, Geom::Rect(ta, tb),
410 }
411
413 Geom::Rect slot_area = slot.get_slot_area();
414 double x0 = slot_area.min()[Geom::X];
415 double y0 = slot_area.min()[Geom::Y];
416 ink_cairo_surface_synthesize(temp, Turbulence(*gen, unit_trans, x0, y0));
417
418 // cairo_surface_write_to_png( temp, "turbulence0.png" );
419
420 cairo_t *ct = cairo_create(out);
421 cairo_set_source_surface(ct, temp, 0, 0);
422 cairo_paint(ct);
423 cairo_destroy(ct);
424
425 cairo_surface_destroy(temp);
426
427 cairo_surface_mark_dirty(out);
428
429 slot.set(_output, out);
430 cairo_surface_destroy(out);
431}
432
434{
435 return 5.0;
436}
437
438} // namespace Filters
439} // namespace Inkscape
440
441/*
442 Local Variables:
443 mode:c++
444 c-file-style:"stroustrup"
445 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
446 indent-tabs-mode:nil
447 fill-column:99
448 End:
449*/
450// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Cairo software blending templates.
void ink_cairo_surface_synthesize(cairo_surface_t *out, cairo_rectangle_t const &out_area, Synth &&synth)
Synthesize surface pixels based on their position.
cairo_surface_t * ink_cairo_surface_create_same_size(cairo_surface_t *s, cairo_content_t c)
void set_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation ci)
Set the color_interpolation_value for a Cairo surface.
Cairo integration helpers.
G_GNUC_CONST guint32 premul_alpha(const guint32 color, const guint32 alpha)
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
cairo_surface_t * getcairo(int slot)
Returns the pixblock in specified slot.
void set(int slot, cairo_surface_t *s)
Sets or re-sets the pixblock associated with given slot.
FilterUnits const & get_units() const
void set_baseFrequency(int axis, double freq)
void render_cairo(FilterSlot &slot) const override
double complexity(Geom::Affine const &ctm) const override
std::unique_ptr< TurbulenceGenerator > gen
Geom::Affine get_matrix_primitiveunits2pb() const
Gets the primitiveUnits to pixblock coordinates transformation matrix.
Css & result
unsigned int guint32
struct _cairo_surface cairo_surface_t
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
auto floor(Geom::Rect const &rect)
Definition geom.h:130
Helper class to stream background task notifications as a series of messages.
STL namespace.
Definition of functions needed by several filters.
struct _cairo cairo_t
Definition path-cairo.h:16
bool ready
int num
Definition scribble.cpp:47
double height
double width
void init(int argc, char **argv, Toy *t, int width=600, int height=600)