Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
nr-filter-gaussian.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Gaussian blur renderer
4 *
5 * Authors:
6 * Niko Kiirala <niko@kiirala.com>
7 * bulia byak
8 * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
9 *
10 * Copyright (C) 2006-2008 authors
11 *
12 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13 */
14
15#include <2geom/affine.h>
16#include <algorithm>
17#include <cmath>
18#include <complex>
19#include <cstdlib>
20#include <glib.h>
21#include <limits>
22
23#include "display/cairo-utils.h"
29#include "display/threading.h"
30#include "util/fixed_point.h"
31
32// IIR filtering method based on:
33// L.J. van Vliet, I.T. Young, and P.W. Verbeek, Recursive Gaussian Derivative Filters,
34// in: A.K. Jain, S. Venkatesh, B.C. Lovell (eds.),
35// ICPR'98, Proc. 14th Int. Conference on Pattern Recognition (Brisbane, Aug. 16-20),
36// IEEE Computer Society Press, Los Alamitos, 1998, 509-514.
37//
38// Using the backwards-pass initialization procedure from:
39// Boundary Conditions for Young - van Vliet Recursive Filtering
40// Bill Triggs, Michael Sdika
41// IEEE Transactions on Signal Processing, Volume 54, Number 5 - may 2006
42
43// Number of IIR filter coefficients used. Currently only 3 is supported.
44// "Recursive Gaussian Derivative Filters" says this is enough though (and
45// some testing indeed shows that the quality doesn't improve much if larger
46// filters are used).
47static size_t const N = 3;
48
49template<typename InIt, typename OutIt, typename Size>
50inline void copy_n(InIt beg_in, Size N, OutIt beg_out) {
51 std::copy(beg_in, beg_in+N, beg_out);
52}
53
54// Type used for IIR filter coefficients (can be 10.21 signed fixed point, see Anisotropic Gaussian Filtering Using Fixed Point Arithmetic, Christoph H. Lampert & Oliver Wirjadi, 2006)
55typedef double IIRValue;
56
57// Type used for FIR filter coefficients (can be 16.16 unsigned fixed point, should have 8 or more bits in the fractional part, the integer part should be capable of storing approximately 20*255)
59
60template<typename T> static inline T sqr(T const& v) { return v*v; }
61
62template<typename T> static inline T clip(T const& v, T const& a, T const& b) {
63 if ( v < a ) return a;
64 if ( v > b ) return b;
65 return v;
66}
67
68template<typename Tt, typename Ts>
69static inline Tt round_cast(Ts v) {
70 static Ts const rndoffset(.5);
71 return static_cast<Tt>(v+rndoffset);
72}
73/*
74template<>
75inline unsigned char round_cast(double v) {
76 // This (fast) rounding method is based on:
77 // http://stereopsis.com/sree/fpu2006.html
78#if G_BYTE_ORDER==G_LITTLE_ENDIAN
79 double const dmr = 6755399441055744.0;
80 v = v + dmr;
81 return ((unsigned char*)&v)[0];
82#elif G_BYTE_ORDER==G_BIG_ENDIAN
83 double const dmr = 6755399441055744.0;
84 v = v + dmr;
85 return ((unsigned char*)&v)[7];
86#else
87 static double const rndoffset(.5);
88 return static_cast<unsigned char>(v+rndoffset);
89#endif
90}*/
91
92template<typename Tt, typename Ts>
93static inline Tt clip_round_cast(Ts const v) {
94 Ts const minval = std::numeric_limits<Tt>::min();
95 Ts const maxval = std::numeric_limits<Tt>::max();
96 Tt const minval_rounded = std::numeric_limits<Tt>::min();
97 Tt const maxval_rounded = std::numeric_limits<Tt>::max();
98 if ( v < minval ) return minval_rounded;
99 if ( v > maxval ) return maxval_rounded;
100 return round_cast<Tt>(v);
101}
102
103template<typename Tt, typename Ts>
104static inline Tt clip_round_cast_varmax(Ts const v, Tt const maxval_rounded) {
105 Ts const minval = std::numeric_limits<Tt>::min();
106 Tt const maxval = maxval_rounded;
107 Tt const minval_rounded = std::numeric_limits<Tt>::min();
108 if ( v < minval ) return minval_rounded;
109 if ( v > maxval ) return maxval_rounded;
110 return round_cast<Tt>(v);
111}
112
113namespace Inkscape {
114namespace Filters {
115
120
124
125static int
126_effect_area_scr(double const deviation)
127{
128 return (int)std::ceil(std::fabs(deviation) * 3.0);
129}
130
131static void
132_make_kernel(FIRValue *const kernel, double const deviation)
133{
134 int const scr_len = _effect_area_scr(deviation);
135 g_assert(scr_len >= 0);
136 double const d_sq = sqr(deviation) * 2;
137 double k[scr_len+1]; // This is only called for small kernel sizes (above approximately 10 coefficients the IIR filter is used)
138
139 // Compute kernel and sum of coefficients
140 // Note that actually only half the kernel is computed, as it is symmetric
141 double sum = 0;
142 for ( int i = scr_len; i >= 0 ; i-- ) {
143 k[i] = std::exp(-sqr(i) / d_sq);
144 if ( i > 0 ) sum += k[i];
145 }
146 // the sum of the complete kernel is twice as large (plus the center element which we skipped above to prevent counting it twice)
147 sum = 2*sum + k[0];
148
149 // Normalize kernel (making sure the sum is exactly 1)
150 double ksum = 0;
151 FIRValue kernelsum = 0;
152 for ( int i = scr_len; i >= 1 ; i-- ) {
153 ksum += k[i]/sum;
154 kernel[i] = ksum-static_cast<double>(kernelsum);
155 kernelsum += kernel[i];
156 }
157 kernel[0] = FIRValue(1)-2*kernelsum;
158}
159
160// Return value (v) should satisfy:
161// 2^(2*v)*255<2^32
162// 255<2^(32-2*v)
163// 2^8<=2^(32-2*v)
164// 8<=32-2*v
165// 2*v<=24
166// v<=12
167static int
168_effect_subsample_step_log2(double const deviation, int const quality)
169{
170 // To make sure FIR will always be used (unless the kernel is VERY big):
171 // deviation/step <= 3
172 // deviation/3 <= step
173 // log(deviation/3) <= log(step)
174 // So when x below is >= 1/3 FIR will almost always be used.
175 // This means IIR is almost only used with the modes BETTER or BEST.
176 int stepsize_l2;
177 switch (quality) {
179 // 2 == log(x*8/3))
180 // 2^2 == x*2^3/3
181 // x == 3/2
182 stepsize_l2 = clip(static_cast<int>(log(deviation*(3./2.))/log(2.)), 0, 12);
183 break;
185 // 2 == log(x*16/3))
186 // 2^2 == x*2^4/3
187 // x == 3/2^2
188 stepsize_l2 = clip(static_cast<int>(log(deviation*(3./4.))/log(2.)), 0, 12);
189 break;
191 // 2 == log(x*32/3))
192 // 2 == x*2^5/3
193 // x == 3/2^4
194 stepsize_l2 = clip(static_cast<int>(log(deviation*(3./16.))/log(2.)), 0, 12);
195 break;
197 stepsize_l2 = 0; // no subsampling at all
198 break;
200 default:
201 // 2 == log(x*16/3))
202 // 2 == x*2^4/3
203 // x == 3/2^3
204 stepsize_l2 = clip(static_cast<int>(log(deviation*(3./8.))/log(2.)), 0, 12);
205 break;
206 }
207 return stepsize_l2;
208}
209
210static void calcFilter(double const sigma, double b[N]) {
211 assert(N==3);
212 std::complex<double> const d1_org(1.40098, 1.00236);
213 double const d3_org = 1.85132;
214 double qbeg = 1; // Don't go lower than sigma==2 (we'd probably want a normal convolution in that case anyway)
215 double qend = 2*sigma;
216 double const sigmasqr = sqr(sigma);
217 do { // Binary search for right q (a linear interpolation scheme is suggested, but this should work fine as well)
218 double const q = (qbeg+qend)/2;
219 // Compute scaled filter coefficients
220 std::complex<double> const d1 = pow(d1_org, 1.0/q);
221 double const d3 = pow(d3_org, 1.0/q);
222 // Compute actual sigma^2
223 double const ssqr = 2*(2*(d1/sqr(d1-1.)).real()+d3/sqr(d3-1.));
224 if ( ssqr < sigmasqr ) {
225 qbeg = q;
226 } else {
227 qend = q;
228 }
229 } while(qend-qbeg>(sigma/(1<<30)));
230 // Compute filter coefficients
231 double const q = (qbeg+qend)/2;
232 std::complex<double> const d1 = pow(d1_org, 1.0/q);
233 double const d3 = pow(d3_org, 1.0/q);
234 double const absd1sqr = std::norm(d1); // d1*d2 = d1*conj(d1) = |d1|^2 = std::norm(d1)
235 double const re2d1 = 2*d1.real(); // d1+d2 = d1+conj(d1) = 2*real(d1)
236 double const bscale = 1.0/(absd1sqr*d3);
237 b[2] = -bscale;
238 b[1] = bscale*(d3+re2d1);
239 b[0] = -bscale*(absd1sqr+d3*re2d1);
240}
241
242static void calcTriggsSdikaM(double const b[N], double M[N*N]) {
243 assert(N==3);
244 double a1=b[0], a2=b[1], a3=b[2];
245 double const Mscale = 1.0/((1+a1-a2+a3)*(1-a1-a2-a3)*(1+a2+(a1-a3)*a3));
246 M[0] = 1-a2-a1*a3-sqr(a3);
247 M[1] = (a1+a3)*(a2+a1*a3);
248 M[2] = a3*(a1+a2*a3);
249 M[3] = a1+a2*a3;
250 M[4] = (1-a2)*(a2+a1*a3);
251 M[5] = a3*(1-a2-a1*a3-sqr(a3));
252 M[6] = a1*(a1+a3)+a2*(1-a2);
253 M[7] = a1*(a2-sqr(a3))+a3*(1+a2*(a2-1)-sqr(a3));
254 M[8] = a3*(a1+a2*a3);
255 for(unsigned int i=0; i<9; i++) M[i] *= Mscale;
256}
257
258template<unsigned int SIZE>
259static void calcTriggsSdikaInitialization(double const M[N*N], IIRValue const uold[N][SIZE], IIRValue const uplus[SIZE], IIRValue const vplus[SIZE], IIRValue const alpha, IIRValue vold[N][SIZE]) {
260 for(unsigned int c=0; c<SIZE; c++) {
261 double uminp[N];
262 for(unsigned int i=0; i<N; i++) uminp[i] = uold[i][c] - uplus[c];
263 for(unsigned int i=0; i<N; i++) {
264 double voldf = 0;
265 for(unsigned int j=0; j<N; j++) {
266 voldf += uminp[j]*M[i*N+j];
267 }
268 // Properly takes care of the scaling coefficient alpha and vplus (which is already appropriately scaled)
269 // This was arrived at by starting from a version of the blur filter that ignored the scaling coefficient
270 // (and scaled the final output by alpha^2) and then gradually reintroducing the scaling coefficient.
271 vold[i][c] = voldf*alpha;
272 vold[i][c] += vplus[c];
273 }
274 }
275}
276
277// Filters over 1st dimension
278template<typename PT, unsigned int PC, bool PREMULTIPLIED_ALPHA>
279static void
280filter2D_IIR(PT *const dest, int const dstr1, int const dstr2,
281 PT const *const src, int const sstr1, int const sstr2,
282 int const n1, int const n2, IIRValue const b[N+1], double const M[N*N],
283 IIRValue *const tmpdata[], dispatch_pool &pool)
284{
285 assert(src && dest);
286
287#if G_BYTE_ORDER == G_LITTLE_ENDIAN
288 static unsigned int const alpha_PC = PC-1;
289 #define PREMUL_ALPHA_LOOP for(unsigned int c=0; c<PC-1; ++c)
290#else
291 static unsigned int const alpha_PC = 0;
292 #define PREMUL_ALPHA_LOOP for(unsigned int c=1; c<PC; ++c)
293#endif
294
295 pool.dispatch(n2, [&](int c2, int tid) {
296 // corresponding line in the source and output buffer
297 PT const * srcimg = src + c2*sstr2;
298 PT * dstimg = dest + c2*dstr2 + n1*dstr1;
299 // Border constants
300 IIRValue imin[PC]; copy_n(srcimg + (0)*sstr1, PC, imin);
301 IIRValue iplus[PC]; copy_n(srcimg + (n1-1)*sstr1, PC, iplus);
302 // Forward pass
303 IIRValue u[N+1][PC];
304 for(unsigned int i=0; i<N; i++) copy_n(imin, PC, u[i]);
305 for ( int c1 = 0 ; c1 < n1 ; c1++ ) {
306 for(unsigned int i=N; i>0; i--) copy_n(u[i-1], PC, u[i]);
307 copy_n(srcimg, PC, u[0]);
308 srcimg += sstr1;
309 for(unsigned int c=0; c<PC; c++) u[0][c] *= b[0];
310 for(unsigned int i=1; i<N+1; i++) {
311 for(unsigned int c=0; c<PC; c++) u[0][c] += u[i][c]*b[i];
312 }
313 copy_n(u[0], PC, tmpdata[tid]+c1*PC);
314 }
315 // Backward pass
316 IIRValue v[N+1][PC];
317 calcTriggsSdikaInitialization<PC>(M, u, iplus, iplus, b[0], v);
318 dstimg -= dstr1;
319 if ( PREMULTIPLIED_ALPHA ) {
320 dstimg[alpha_PC] = clip_round_cast<PT>(v[0][alpha_PC]);
321 PREMUL_ALPHA_LOOP dstimg[c] = clip_round_cast_varmax<PT>(v[0][c], dstimg[alpha_PC]);
322 } else {
323 for(unsigned int c=0; c<PC; c++) dstimg[c] = clip_round_cast<PT>(v[0][c]);
324 }
325 int c1=n1-1;
326 while(c1-->0) {
327 for(unsigned int i=N; i>0; i--) copy_n(v[i-1], PC, v[i]);
328 copy_n(tmpdata[tid]+c1*PC, PC, v[0]);
329 for(unsigned int c=0; c<PC; c++) v[0][c] *= b[0];
330 for(unsigned int i=1; i<N+1; i++) {
331 for(unsigned int c=0; c<PC; c++) v[0][c] += v[i][c]*b[i];
332 }
333 dstimg -= dstr1;
334 if ( PREMULTIPLIED_ALPHA ) {
335 dstimg[alpha_PC] = clip_round_cast<PT>(v[0][alpha_PC]);
336 PREMUL_ALPHA_LOOP dstimg[c] = clip_round_cast_varmax<PT>(v[0][c], dstimg[alpha_PC]);
337 } else {
338 for(unsigned int c=0; c<PC; c++) dstimg[c] = clip_round_cast<PT>(v[0][c]);
339 }
340 }
341 });
342}
343
344// Filters over 1st dimension
345// Assumes kernel is symmetric
346// Kernel should have scr_len+1 elements
347template<typename PT, unsigned int PC>
348static void
349filter2D_FIR(PT *const dst, int const dstr1, int const dstr2,
350 PT const *const src, int const sstr1, int const sstr2,
351 int const n1, int const n2, FIRValue const *const kernel, int const scr_len, dispatch_pool &pool)
352{
353 assert(src && dst);
354
355 pool.dispatch(n2, [&](int c2, int) {
356 // Past pixels seen (to enable in-place operation)
357 PT history[scr_len + 1][PC];
358
359 // corresponding line in the source buffer
360 int const src_line = c2 * sstr2;
361
362 // current line in the output buffer
363 int const dst_line = c2 * dstr2;
364
365 int skipbuf[4] = {INT_MIN, INT_MIN, INT_MIN, INT_MIN};
366
367 // history initialization
368 PT imin[PC]; copy_n(src + src_line, PC, imin);
369 for(int i=0; i<scr_len; i++) copy_n(imin, PC, history[i]);
370
371 for ( int c1 = 0 ; c1 < n1 ; c1++ ) {
372
373 int const src_disp = src_line + c1 * sstr1;
374 int const dst_disp = dst_line + c1 * dstr1;
375
376 // update history
377 for(int i=scr_len; i>0; i--) copy_n(history[i-1], PC, history[i]);
378 copy_n(src + src_disp, PC, history[0]);
379
380 // for all bytes of the pixel
381 for ( unsigned int byte = 0 ; byte < PC ; byte++) {
382
383 if(skipbuf[byte] > c1) continue;
384
385 FIRValue sum = 0;
386 int last_in = -1;
387 int different_count = 0;
388
389 // go over our point's neighbours in the history
390 for ( int i = 0 ; i <= scr_len ; i++ ) {
391 // value at the pixel
392 PT in_byte = history[i][byte];
393
394 // is it the same as last one we saw?
395 if(in_byte != last_in) different_count++;
396 last_in = in_byte;
397
398 // sum pixels weighted by the kernel
399 sum += in_byte * kernel[i];
400 }
401
402 // go over our point's neighborhood on x axis in the in buffer
403 int nb_src_disp = src_disp + byte;
404 for ( int i = 1 ; i <= scr_len ; i++ ) {
405 // the pixel we're looking at
406 int c1_in = c1 + i;
407 if (c1_in >= n1) {
408 c1_in = n1 - 1;
409 } else {
410 nb_src_disp += sstr1;
411 }
412
413 // value at the pixel
414 PT in_byte = src[nb_src_disp];
415
416 // is it the same as last one we saw?
417 if(in_byte != last_in) different_count++;
418 last_in = in_byte;
419
420 // sum pixels weighted by the kernel
421 sum += in_byte * kernel[i];
422 }
423
424 // store the result in bufx
425 dst[dst_disp + byte] = round_cast<PT>(sum);
426
427 // optimization: if there was no variation within this point's neighborhood,
428 // skip ahead while we keep seeing the same last_in byte:
429 // blurring flat color would not change it anyway
430 if (different_count <= 1) { // note that different_count is at least 1, because last_in is initialized to -1
431 int pos = c1 + 1;
432 int nb_src_disp = src_disp + (1+scr_len)*sstr1 + byte; // src_line + (pos+scr_len) * sstr1 + byte
433 int nb_dst_disp = dst_disp + (1) *dstr1 + byte; // dst_line + (pos) * sstr1 + byte
434 while(pos + scr_len < n1 && src[nb_src_disp] == last_in) {
435 dst[nb_dst_disp] = last_in;
436 pos++;
437 nb_src_disp += sstr1;
438 nb_dst_disp += dstr1;
439 }
440 skipbuf[byte] = pos;
441 }
442 }
443 }
444 });
445}
446
447static void
449 IIRValue **tmpdata, dispatch_pool &pool)
450{
451 // Filter variables
452 IIRValue b[N+1]; // scaling coefficient + filter coefficients (can be 10.21 fixed point)
453 double bf[N]; // computed filter coefficients
454 double M[N*N]; // matrix used for initialization procedure (has to be double)
455
456 // Compute filter
457 calcFilter(deviation, bf);
458 for(double & i : bf) i = -i;
459 b[0] = 1; // b[0] == alpha (scaling coefficient)
460 for(size_t i=0; i<N; i++) {
461 b[i+1] = bf[i];
462 b[0] -= b[i+1];
463 }
464
465 // Compute initialization matrix
466 calcTriggsSdikaM(bf, M);
467
468 int stride = cairo_image_surface_get_stride(src);
469 int w = cairo_image_surface_get_width(src);
470 int h = cairo_image_surface_get_height(src);
471 if (d != Geom::X) std::swap(w, h);
472
473 // Filter
474 switch (cairo_image_surface_get_format(src)) {
475 case CAIRO_FORMAT_A8:
476 filter2D_IIR<unsigned char,1,false>(
477 cairo_image_surface_get_data(dest), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1,
478 cairo_image_surface_get_data(src), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1,
479 w, h, b, M, tmpdata, pool);
480 break;
481 case CAIRO_FORMAT_ARGB32:
482 filter2D_IIR<unsigned char,4,true>(
483 cairo_image_surface_get_data(dest), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4,
484 cairo_image_surface_get_data(src), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4,
485 w, h, b, M, tmpdata, pool);
486 break;
487 default:
488 g_warning("gaussian_pass_IIR: unsupported image format");
489 };
490}
491
492static void
494 dispatch_pool &pool)
495{
496 int scr_len = _effect_area_scr(deviation);
497 // Filter kernel for x direction
498 std::vector<FIRValue> kernel(scr_len + 1);
499 _make_kernel(&kernel[0], deviation);
500
501 int stride = cairo_image_surface_get_stride(src);
502 int w = cairo_image_surface_get_width(src);
503 int h = cairo_image_surface_get_height(src);
504 if (d != Geom::X) std::swap(w, h);
505
506 // Filter (x)
507 switch (cairo_image_surface_get_format(src)) {
508 case CAIRO_FORMAT_A8:
509 filter2D_FIR<unsigned char,1>(
510 cairo_image_surface_get_data(dest), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1,
511 cairo_image_surface_get_data(src), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1,
512 w, h, &kernel[0], scr_len, pool);
513 break;
514 case CAIRO_FORMAT_ARGB32:
515 filter2D_FIR<unsigned char,4>(
516 cairo_image_surface_get_data(dest), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4,
517 cairo_image_surface_get_data(src), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4,
518 w, h, &kernel[0], scr_len, pool);
519 break;
520 default:
521 g_warning("gaussian_pass_FIR: unsupported image format");
522 };
523}
524
526{
527 cairo_surface_t *in = slot.getcairo(_input);
529 return;
530 }
531
532 // We may need to transform input surface to correct color interpolation space. The input surface
533 // might be used as input to another primitive but it is likely that all the primitives in a given
534 // filter use the same color interpolation space so we don't copy the input before converting.
536
537 // zero deviation = no change in output
538 if (_deviation_x <= 0 && _deviation_y <= 0) {
540 slot.set(_output, cp);
541 cairo_surface_destroy(cp);
542 return;
543 }
544
545 // Handle bounding box case.
546 double dx = _deviation_x;
547 double dy = _deviation_y;
549 Geom::OptRect const bbox = slot.get_units().get_item_bbox();
550 if( bbox ) {
551 dx *= (*bbox).width();
552 dy *= (*bbox).height();
553 }
554 }
555
557
558 double deviation_x_orig = dx * trans.expansionX();
559 double deviation_y_orig = dy * trans.expansionY();
560
561 int device_scale = slot.get_device_scale();
562
563 deviation_x_orig *= device_scale;
564 deviation_y_orig *= device_scale;
565
566 cairo_format_t fmt = cairo_image_surface_get_format(in);
567 int bytes_per_pixel = 0;
568 switch (fmt) {
569 case CAIRO_FORMAT_A8:
570 bytes_per_pixel = 1; break;
571 case CAIRO_FORMAT_ARGB32:
572 default:
573 bytes_per_pixel = 4; break;
574 }
575
576 auto const pool = get_global_dispatch_pool();
577 int quality = slot.get_blurquality();
578 int threads = pool->size();
579 int x_step = 1 << _effect_subsample_step_log2(deviation_x_orig, quality);
580 int y_step = 1 << _effect_subsample_step_log2(deviation_y_orig, quality);
581 bool resampling = x_step > 1 || y_step > 1;
582 int w_orig = ink_cairo_surface_get_width(in); // Pixels
583 int h_orig = ink_cairo_surface_get_height(in);
584 int w_downsampled = resampling ? static_cast<int>(ceil(static_cast<double>(w_orig)/x_step))+1 : w_orig;
585 int h_downsampled = resampling ? static_cast<int>(ceil(static_cast<double>(h_orig)/y_step))+1 : h_orig;
586 double deviation_x = deviation_x_orig / x_step;
587 double deviation_y = deviation_y_orig / y_step;
588 int scr_len_x = _effect_area_scr(deviation_x);
589 int scr_len_y = _effect_area_scr(deviation_y);
590
591 // Decide which filter to use for X and Y
592 // This threshold was determined by trial-and-error for one specific machine,
593 // so there's a good chance that it's not optimal.
594 // Whatever you do, don't go below 1 (and preferably not even below 2), as
595 // the IIR filter gets unstable there.
596 bool use_IIR_x = deviation_x > 3;
597 bool use_IIR_y = deviation_y > 3;
598
599 // Temporary storage for IIR filter
600 // NOTE: This can be eliminated, but it reduces the precision a bit
601 IIRValue * tmpdata[threads];
602 std::fill_n(tmpdata, threads, (IIRValue*)0);
603 if ( use_IIR_x || use_IIR_y ) {
604 for(int i = 0; i < threads; ++i) {
605 tmpdata[i] = new IIRValue[std::max(w_downsampled,h_downsampled)*bytes_per_pixel];
606 }
607 }
608
609 cairo_surface_t *downsampled = nullptr;
610 if (resampling) {
611 // Divide by device scale as w_downsampled is in pixels while
612 // cairo_surface_create_similar() uses device units.
613 downsampled = cairo_surface_create_similar(in, cairo_surface_get_content(in),
614 w_downsampled/device_scale, h_downsampled/device_scale);
615 cairo_t *ct = cairo_create(downsampled);
616 cairo_scale(ct, static_cast<double>(w_downsampled)/w_orig, static_cast<double>(h_downsampled)/h_orig);
617 cairo_set_source_surface(ct, in, 0, 0);
618 cairo_paint(ct);
619 cairo_destroy(ct);
620 } else {
621 downsampled = ink_cairo_surface_copy(in);
622 }
623 cairo_surface_flush(downsampled);
624
625 if (scr_len_x > 0) {
626 if (use_IIR_x) {
627 gaussian_pass_IIR(Geom::X, deviation_x, downsampled, downsampled, tmpdata, *pool);
628 } else {
629 gaussian_pass_FIR(Geom::X, deviation_x, downsampled, downsampled, *pool);
630 }
631 }
632
633 if (scr_len_y > 0) {
634 if (use_IIR_y) {
635 gaussian_pass_IIR(Geom::Y, deviation_y, downsampled, downsampled, tmpdata, *pool);
636 } else {
637 gaussian_pass_FIR(Geom::Y, deviation_y, downsampled, downsampled, *pool);
638 }
639 }
640
641 // free the temporary data
642 if ( use_IIR_x || use_IIR_y ) {
643 for(int i = 0; i < threads; ++i) {
644 delete[] tmpdata[i];
645 }
646 }
647
648 cairo_surface_mark_dirty(downsampled);
649 if (resampling) {
650 cairo_surface_t *upsampled = cairo_surface_create_similar(downsampled, cairo_surface_get_content(downsampled),
651 w_orig / device_scale, h_orig / device_scale);
652 cairo_t *ct = cairo_create(upsampled);
653 cairo_scale(ct, static_cast<double>(w_orig) / w_downsampled, static_cast<double>(h_orig) / h_downsampled);
654 cairo_set_source_surface(ct, downsampled, 0, 0);
655 cairo_paint(ct);
656 cairo_destroy(ct);
657
659
660 slot.set(_output, upsampled);
661 cairo_surface_destroy(upsampled);
662 cairo_surface_destroy(downsampled);
663 } else {
665
666 slot.set(_output, downsampled);
667 cairo_surface_destroy(downsampled);
668 }
669}
670
672{
673 int area_x = _effect_area_scr(_deviation_x * trans.expansionX());
674 int area_y = _effect_area_scr(_deviation_y * trans.expansionY());
675 // maximum is used because rotations can mix up these directions
676 // TODO: calculate a more tight-fitting rendering area
677 int area_max = std::max(area_x, area_y);
678 area.expandBy(area_max);
679}
680
682{
683 // Previously we tried to be smart and return true for rotations.
684 // However, the transform passed here is NOT the total transform
685 // from filter user space to screen.
686 // TODO: fix this, or replace can_handle_affine() with isotropic().
687 return false;
688}
689
690double FilterGaussian::complexity(Geom::Affine const &trans) const
691{
692 int area_x = _effect_area_scr(_deviation_x * trans.expansionX());
693 int area_y = _effect_area_scr(_deviation_y * trans.expansionY());
694 return 2.0 * area_x * area_y;
695}
696
697void FilterGaussian::set_deviation(double deviation)
698{
699 if (std::isfinite(deviation) && deviation >= 0) {
700 _deviation_x = _deviation_y = deviation;
701 }
702}
703
704void FilterGaussian::set_deviation(double x, double y)
705{
706 if (std::isfinite(x) && x >= 0 && std::isfinite(y) && y >= 0) {
707 _deviation_x = x;
708 _deviation_y = y;
709 }
710}
711
712} // namespace Filters
713} // namespace Inkscape
714
715/*
716 Local Variables:
717 mode:c++
718 c-file-style:"stroustrup"
719 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
720 indent-tabs-mode:nil
721 fill-column:99
722 End:
723*/
724// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
3x3 affine transformation matrix.
cairo_surface_t * ink_cairo_surface_copy(cairo_surface_t *s)
Create an exact copy of a surface.
int ink_cairo_surface_get_width(cairo_surface_t *surface)
Return width in pixels.
int ink_cairo_surface_get_height(cairo_surface_t *surface)
Return height in pixels.
void set_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation ci)
Set the color_interpolation_value for a Cairo surface.
Cairo integration helpers.
static int constexpr SIZE
Definition cielab.cpp:45
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord expansionX() const
Calculates the amount of x-scaling imparted by the Affine.
Definition affine.cpp:64
Coord expansionY() const
Calculates the amount of y-scaling imparted by the Affine.
Definition affine.cpp:71
Axis aligned, non-empty, generic rectangle.
void expandBy(C amount)
Expand the rectangle in both directions by the specified amount.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
void set_deviation(double deviation)
Set the standard deviation value for gaussian blur.
void render_cairo(FilterSlot &slot) const override
bool can_handle_affine(Geom::Affine const &m) const override
Indicate whether the filter primitive can handle the given affine.
void area_enlarge(Geom::IntRect &area, Geom::Affine const &m) const override
double complexity(Geom::Affine const &ctm) const override
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.
int get_device_scale() const
Gets the device scale; for high DPI monitors.
FilterUnits const & get_units() const
int get_blurquality() const
Gets the gaussian filtering quality.
SPFilterUnits get_primitive_units() const
Gets Primitive Units (userSpaceOnUse or objectBoundingBox)
Geom::OptRect get_item_bbox() const
Gets the item bounding box in user coordinates.
Geom::Affine get_matrix_user2pb() const
Gets the user coordinates to pixblock coordinates transformation matrix.
General-purpose, parallel thread dispatch mechanism.
void dispatch(int count, dispatch_func function)
const double w
Definition conic-4.cpp:19
double c[8][4]
struct _cairo_surface cairo_surface_t
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
static void gaussian_pass_IIR(Geom::Dim2 d, double deviation, cairo_surface_t *src, cairo_surface_t *dest, IIRValue **tmpdata, dispatch_pool &pool)
static void filter2D_FIR(PT *const dst, int const dstr1, int const dstr2, PT const *const src, int const sstr1, int const sstr2, int const n1, int const n2, FIRValue const *const kernel, int const scr_len, dispatch_pool &pool)
static void calcFilter(double const sigma, double b[N])
static void gaussian_pass_FIR(Geom::Dim2 d, double deviation, cairo_surface_t *src, cairo_surface_t *dest, dispatch_pool &pool)
static int _effect_subsample_step_log2(double const deviation, int const quality)
static void calcTriggsSdikaM(double const b[N], double M[N *N])
static void filter2D_IIR(PT *const dest, int const dstr1, int const dstr2, PT const *const src, int const sstr1, int const sstr2, int const n1, int const n2, IIRValue const b[N+1], double const M[N *N], IIRValue *const tmpdata[], dispatch_pool &pool)
static int _effect_area_scr(double const deviation)
static void calcTriggsSdikaInitialization(double const M[N *N], IIRValue const uold[N][SIZE], IIRValue const uplus[SIZE], IIRValue const vplus[SIZE], IIRValue const alpha, IIRValue vold[N][SIZE])
static void _make_kernel(FIRValue *const kernel, double const deviation)
Helper class to stream background task notifications as a series of messages.
std::shared_ptr< dispatch_pool > get_global_dispatch_pool()
Definition threading.cpp:31
static size_t const N
static Tt clip_round_cast_varmax(Ts const v, Tt const maxval_rounded)
double IIRValue
void copy_n(InIt beg_in, Size N, OutIt beg_out)
Inkscape::Util::FixedPoint< unsigned int, 16 > FIRValue
static T sqr(T const &v)
static T clip(T const &v, T const &a, T const &b)
static Tt clip_round_cast(Ts const v)
static Tt round_cast(Ts v)
@ BLUR_QUALITY_NORMAL
@ BLUR_QUALITY_BEST
@ BLUR_QUALITY_WORSE
@ BLUR_QUALITY_BETTER
@ BLUR_QUALITY_WORST
struct _cairo cairo_t
Definition path-cairo.h:16
int stride
Piecewise< SBasis > log(Interval in)
Definition pw-funcs.cpp:37
unsigned n1
unsigned n2
int const char * fmt
Definition safe-printf.h:18
size_t N
@ SP_FILTER_UNITS_OBJECTBOUNDINGBOX
double sum(const double alpha[16], const double &x, const double &y)
G_BEGIN_DECLS typedef double real
Definition splinefont.h:10