Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
blurs.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2021, 2023, 2025-2026 Aurélien PIERRE.
4 Copyright (C) 2021 Hubert Kowalski.
5 Copyright (C) 2021-2022 Pascal Obry.
6 Copyright (C) 2021 Ralf Brown.
7 Copyright (C) 2022 Diederik Ter Rahe.
8 Copyright (C) 2022 Hanno Schwalm.
9 Copyright (C) 2022 Martin Bařinka.
10 Copyright (C) 2022 Philipp Lutz.
11 Copyright (C) 2023 Luca Zulberti.
12 Copyright (C) 2024 Alynx Zhou.
13 Copyright (C) 2026 Guillaume Stutin.
14
15 darktable is free software: you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation, either version 3 of the License, or
18 (at your option) any later version.
19
20 darktable is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with darktable. If not, see <http://www.gnu.org/licenses/>.
27*/
28#ifdef HAVE_CONFIG_H
29#include "system/macros.h"
30#include "system/mem_alloc.h"
32#include "common/logging.h"
33#include "system/openmp.h"
34#include "system/simd.h"
36#include "config.h"
37#endif
38// our includes go first:
39#include "widgets/bauhaus.h"
40#include "develop/imageop.h"
41#include "develop/imageop_gui.h"
42#include "widgets/drawingarea.h"
43#include "iop/iop_api.h"
44
45// #include <fftw3.h> // one day, include FFT convolution
46#include <gtk/gtk.h>
47#include <stdlib.h>
48
50
52{
53 DT_BLUR_LENS = 0, // $DESCRIPTION: "lens"
54 DT_BLUR_MOTION = 1, // $DESCRIPTION: "motion"
55 DT_BLUR_GAUSSIAN = 2, // $DESCRIPTION: "gaussian"
57
58
60{
61 dt_iop_blur_type_t type; // $DEFAULT: DT_BLUR_LENS $DESCRIPTION: "blur type"
62 int radius; // $MIN: 4 $MAX: 128 $DEFAULT: 8 $DESCRIPTION: "blur radius"
63
64 // lens blur params
65 int blades; // $MIN: 3 $MAX: 11 $DEFAULT: 5 $DESCRIPTION: "diaphragm blades"
66 float concavity; // $MIN: 1. $MAX: 9. $DEFAULT: 1. $DESCRIPTION: "concavity"
67 float linearity; // $MIN: 0. $MAX: 1. $DEFAULT: 1. $DESCRIPTION: "linearity"
68 float rotation; // $MIN: -1.57 $MAX: 1.57 $DEFAULT: 0. $DESCRIPTION: "rotation"
69
70 // motion blur params
71 float angle; // $MIN: -3.14 $MAX: 3.14 $DEFAULT: 0. $DESCRIPTION: "direction"
72 float curvature; // $MIN: -2. $MAX: 2. $DEFAULT: 0. $DESCRIPTION: "curvature"
73 float offset; // $MIN: -1. $MAX: 1. $DEFAULT: 0 $DESCRIPTION: "offset"
74
76
77
86
87
92
93
94const char *name()
95{
96 return _("blurs");
97}
98
99const char *aliases()
100{
101 return _("blur|lens|motion");
102}
103
104const char **description(struct dt_iop_module_t *self)
105{
106 return dt_iop_set_description(self,
107 _("simulate physically-accurate lens and motion blurs"),
108 _("creative"), _("linear, RGB, scene-referred"), _("linear, RGB"),
109 _("linear, RGB, scene-referred"));
110}
111
116
117
119{
120 return IOP_GROUP_SHARPNESS;
121}
122
123
128
129
134
135// B spline filter
136#define FSIZE 5
137
138inline static void blur_2D_Bspline(const float *const restrict in, float *const restrict out,
139 const size_t width, const size_t height)
140{
141 __OMP_PARALLEL_FOR__( collapse(2))
142 for(size_t i = 0; i < height; i++)
143 {
144 for(size_t j = 0; j < width; j++)
145 {
146 const size_t index = (i * width + j);
147 float acc = 0.f;
148
149 for(size_t ii = 0; ii < FSIZE; ++ii)
150 for(size_t jj = 0; jj < FSIZE; ++jj)
151 {
152 const size_t row = CLAMP((int)i + (int)(ii - (FSIZE - 1) / 2), (int)0, (int)height - 1);
153 const size_t col = CLAMP((int)j + (int)(jj - (FSIZE - 1) / 2), (int)0, (int)width - 1);
154 const size_t k_index = (row * width + col);
155
156 static const float DT_ALIGNED_ARRAY filter[FSIZE]
157 = { 1.0f / 16.0f, 4.0f / 16.0f, 6.0f / 16.0f, 4.0f / 16.0f, 1.0f / 16.0f };
158
159 acc += filter[ii] * filter[jj] * in[k_index];
160 }
161
162 out[index] = acc;
163 }
164 }
165}
166
167
169static inline void init_kernel(float *const restrict buffer, const size_t width, const size_t height)
170{
171 // init an empty kernel with zeros
172 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer:64))
173 for(size_t k = 0; k < height * width; k++) buffer[k] = 0.f;
174}
175
176
178static inline void create_lens_kernel(float *const restrict buffer, const size_t width,
179 const size_t height, const float n, const float m,
180 const float k, const float rotation)
181{
182 // n is number of diaphragm blades
183 // m is the concavity, aka the number of vertices on straight lines (?)
184 // k is the roundness vs. linearity factor
185 // see https://math.stackexchange.com/a/4160104/498090
186 // buffer sizes need to be odd
187
188 // Spatial coordinates rounding error
189 const float eps = 1.f / (float)width;
190 const float radius = (float)(width - 1) / 2.f - 1;
191 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer:64) collapse(2))
192 for(size_t i = 0; i < height; i++)
193 for(size_t j = 0; j < width; j++)
194 {
195 // get normalized kernel coordinates in [-1 ; 1]
196 const float x = (float)(i - 1) / radius - 1;
197 const float y = (float)(j - 1) / radius - 1;
198
199 // get current radial distance from kernel center
200 const float r = dt_fast_hypotf(x, y);
201
202 // get the radial distance at current angle of the shape envelope
203 const float M = cosf((2.f * asinf(k) + M_PI_F * m) / (2.f * n))
204 / cosf((2.f * asinf(k * cosf(n * (atan2f(y, x) + rotation))) + M_PI_F * m) / (2.f * n));
205
206 // write 1 if we are inside the envelope of the shape, else 0
207 buffer[i * width + j] = (M >= r + eps);
208 }
209}
210
211
213static inline void create_motion_kernel(float *const restrict buffer, const size_t width,
214 const size_t height, const float angle,
215 const float curvature, const float offset)
216{
217 // Compute the polynomial params from user params
218 const float A = curvature / 2.f;
219 const float B = 1.f;
220 const float C = -A * offset * offset + B * offset;
221 // Note : C ensures the polynomial arc always goes through the central pixel
222 // so we don't shift pixels. This is meant to allow seamless connection
223 // with unmasked areas when using masked blur.
224
225 // Spatial coordinates rounding error
226 const float eps = 1.f / (float)width;
227
228 const float radius = (float)(width - 1) / 2.f - 1;
229 const float corr_angle = -M_PI_F / 4.f - angle;
230
231 // Matrix of rotation
232 const float M[2][2] = { { cosf(corr_angle), -sinf(corr_angle) },
233 { sinf(corr_angle), cosf(corr_angle) } };
234 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer:64))
235 for(size_t i = 0; i < 8 * width; i++)
236 {
237 // Note : for better smoothness of the polynomial discretization,
238 // we oversample 8 times, meaning we evaluate the polynomial
239 // every eighth of pixel
240
241 // get normalized kernel coordinates in [-1 ; 1]
242 const float x = (float)(i / 8.f - 1) / radius - 1;
243 //const float y = (j - 1) / radius - 1; // not used here
244
245 // build the motion path : 2nd order polynomial
246 const float X = x - offset;
247 const float y = X * X * A + X * B + C;
248
249 // rotate the motion path around the kernel center
250 const float rot_x = x * M[0][0] + y * M[0][1];
251 const float rot_y = x * M[1][0] + y * M[1][1];
252
253 // convert back to kernel absolute coordinates ± eps
254 const int y_f[2] = { roundf((rot_y + 1) * radius - eps),
255 roundf((rot_y + 1) * radius + eps) };
256 const int x_f[2] = { roundf((rot_x + 1) * radius - eps),
257 roundf((rot_x + 1) * radius + eps) };
258
259 // write 1 if we are inside the envelope of the shape, else 0
260 // leave 1px padding on each border of the kernel for the anti-aliasing
261 for(int l = 0; l < 2; l++)
262 for(int m = 0; m < 2; m++)
263 {
264 if(x_f[l] > 0 && x_f[l] < width - 1 && y_f[m] > 0 && y_f[m] < width - 1)
265 buffer[y_f[m] * width + x_f[l]] = 1.f;
266 }
267 }
268}
269
270
272static inline void create_gauss_kernel(float *const restrict buffer, const size_t width,
273 const size_t height)
274{
275 // This is not optimized. Gauss kernel is separable and can be turned into
276 // 2 x 1D convolutions.
277 const float radius = (width - 1) / 2.f - 1;
278 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer:64) collapse(2))
279 for(size_t i = 0; i < height; i++)
280 for(size_t j = 0; j < width; j++)
281 {
282 // get normalized kernel coordinates in [-1 ; 1]
283 const float x = (float)(i - 1) / radius - 1;
284 const float y = (float)(j - 1) / radius - 1;
285
286 // get current square radial distance from kernel center
287 const float r_2 = x * x + y * y;
288 buffer[i * width + j] = expf(-4.f * r_2);
289 }
290}
291
292
293
295static inline int build_gui_kernel(unsigned char *const buffer, const size_t width,
296 const size_t height, dt_iop_blurs_params_t *p)
297{
298 float *const restrict kernel_1 = dt_alloc_align_float(width * height);
299 float *const restrict kernel_2 = dt_alloc_align_float(width * height);
300 if(IS_NULL_PTR(kernel_1) || IS_NULL_PTR(kernel_2)) goto error;
301
302 if(p->type == DT_BLUR_LENS)
303 {
304 create_lens_kernel(kernel_1, width, height, p->blades, p->concavity, p->linearity, p->rotation);
305
306 // anti-aliasing step
307 blur_2D_Bspline(kernel_1, kernel_2, width, height);
308 }
309 else if(p->type == DT_BLUR_MOTION)
310 {
311 init_kernel(kernel_1, width, height);
312 create_motion_kernel(kernel_1, width, height, p->angle, p->curvature, p->offset);
313
314 // anti-aliasing step
315 blur_2D_Bspline(kernel_1, kernel_2, width, height);
316 }
317 else if(p->type == DT_BLUR_GAUSSIAN)
318 {
319 create_gauss_kernel(kernel_2, width, height);
320 }
321
322 // Convert to Gtk/Cairo RGBA 8x4 bits
323 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer, kernel_2:64))
324 for(size_t k = 0; k < height * width; k++)
325 {
326 buffer[k * 4] = buffer[k * 4 + 1] = buffer[k * 4 + 2] = buffer[k * 4 + 3] = roundf(255.f * kernel_2[k]);
327 }
328
329error:;
330 int err = (IS_NULL_PTR(kernel_1) || IS_NULL_PTR(kernel_2));
331 dt_free_align(kernel_1);
332 dt_free_align(kernel_2);
333 return err;
334}
335
336
338static inline float compute_norm(float *const buffer, const size_t width, const size_t height)
339{
340 float norm = 0.f;
341 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer:64) reduction(+:norm))
342 for(size_t i = 0; i < width * height; i++)
343 {
344 norm += buffer[i];
345 }
346
347 return norm;
348}
349
350
352static inline void normalize(float *const buffer, const size_t width, const size_t height,
353 const float norm)
354{
355 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer:64))
356 for(size_t i = 0; i < width * height; i++)
357 {
358 buffer[i] /= norm;
359 }
360}
361
362
363static inline int build_pixel_kernel(float *const buffer, const size_t width, const size_t height,
365{
366 float *const restrict kernel_1 = dt_alloc_align_float(width * height);
367 if(IS_NULL_PTR(kernel_1)) return 1;
368
369 if(p->type == DT_BLUR_LENS)
370 {
371 create_lens_kernel(kernel_1, width, height, p->blades, p->concavity, p->linearity, p->rotation + M_PI_F);
372
373 // anti-aliasing step
374 blur_2D_Bspline(kernel_1, buffer, width, height);
375 }
376 else if(p->type == DT_BLUR_MOTION)
377 {
378 init_kernel(kernel_1, width, height);
379 create_motion_kernel(kernel_1, width, height, p->angle + M_PI_F, p->curvature, p->offset);
380
381 // anti-aliasing step
382 blur_2D_Bspline(kernel_1, buffer, width, height);
383 }
384 else if(p->type == DT_BLUR_GAUSSIAN)
385 {
387 }
388
389 // normalize to respect the conservation of energy law
390 const float norm = compute_norm(buffer, width, height);
391 normalize(buffer, width, height, norm);
392
393 dt_free_align(kernel_1);
394 return 0;
395}
396
397#if 0
398
399// This crashes on the FFT step - not sure why and no time to investigate opaque libs now
400// FFT convolution should be faster for large blurs because it is o(N log2(N))
401// where N is the width of the kernel
402// TODO
403
404static void process_fft(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece,
405 const void *const restrict ivoid, void *const restrict ovoid,
406 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
407{
409 const float scale = dt_dev_get_module_scale(pipe, roi_in);
410
411 const float *const restrict in = __builtin_assume_aligned(ivoid, 64);
412 //float *const restrict out = __builtin_assume_aligned(ovoid, 64);
413
414 // FFT needs odd buffer sizes, so fix that here
415 const int is_width_even = (roi_in->width % 2 == 0);
416 const int is_height_even = (roi_in->height % 2 == 0);
417 const size_t padded_width = roi_in->width + 1 * is_width_even;
418 const size_t padded_height = roi_in->height + 1 * is_height_even;
419
420 float *const restrict padded_in = dt_alloc_align_float(padded_width * padded_height * 4);
421 float *const restrict padded_out = dt_alloc_align_float(padded_width * padded_height * 4);
422
423 // Write the image in the padded buffer
424 __OMP_PARALLEL_FOR_SIMD__(aligned(in, padded_in:64))
425 for(size_t i = 0; i < roi_in->height; i++)
426 for(size_t j = 0; j < roi_in->width; j++)
427 {
428 const size_t index_in = (i * roi_in->width + j) * 4;
429 const size_t index_out = (i * padded_width + j) * 4;
430 for_four_channels(c, aligned(in, padded_in : 64)) padded_in[index_out + c] = in[index_in + c];
431 }
432
433 // Write the padding if needed
434 if(padded_width > roi_in->width)
435 {
436 __OMP_PARALLEL_FOR_SIMD__(aligned(in, padded_in:64))
437 for(size_t i = 0; i < roi_in->height; i++)
438 {
439 const size_t index_in = (i * (roi_in->width - 1)) * 4;
440 const size_t index_out = (i * (padded_width - 1)) * 4;
441 for_four_channels(c, aligned(in, padded_in : 64)) padded_in[index_out + c] = in[index_in + c];
442 }
443 }
444
445 if(padded_height > roi_in->height)
446 {
447 __OMP_PARALLEL_FOR_SIMD__(aligned(in, padded_in:64))
448 for(size_t j = 0; j < roi_in->width; j++)
449 {
450 const size_t index_in = ((roi_in->height - 1) * roi_in->width + j) * 4;
451 const size_t index_out = ((padded_height - 1) * padded_width + j) * 4;
452 for_four_channels(c, aligned(in, padded_in : 64)) padded_in[index_out + c] = in[index_in + c];
453 }
454 }
455
456 // Init the blur kernel
457 const size_t radius = MAX(roundf(p->radius / scale), 1);
458 const size_t kernel_width = 2 * radius + 1;
459
460 float *const restrict kernel = dt_alloc_align_float(kernel_width * kernel_width);
461 build_pixel_kernel(kernel, kernel_width, kernel_width, p);
462
463 // Convert to padded kernel - copy kernel in the center
464 float *const restrict padded_kernel = dt_alloc_align_float(padded_width * padded_height);
465 const size_t offset_i = (padded_height - 1) / 2 - (kernel_width - 1) / 2;
466 const size_t offset_j = (padded_width - 1) / 2 - (kernel_width - 1) / 2;
467 const size_t i_reach = offset_i + kernel_width;
468 const size_t j_reach = offset_j + kernel_width;
469 __OMP_PARALLEL_FOR_SIMD__(aligned(kernel, padded_kernel:64))
470 for(size_t i = 0; i < padded_width; i++)
471 for(size_t j = 0; j < padded_width; j++)
472 {
473 const size_t padded_idx = (i * padded_width) + j;
474
475 if(i >= offset_i && i < i_reach && j >= offset_j && j < j_reach)
476 {
477 const size_t i_kern = i - offset_i;
478 const size_t j_kern = j - offset_j;
479 const size_t kern_idx = i_kern * kernel_width + j_kern;
480 padded_kernel[padded_idx] = kernel[kern_idx];
481 }
482 else
483 padded_kernel[padded_idx] = 0.f;
484 }
485
486 // Init the FFT transforms
487 int threads = fftw_init_threads();
488 fftw_plan_with_nthreads(threads);
489
490 // TODO: things go well until this point
491
492 // Plan the dimensions of the FFT
493 // notice we use fftwf prefix to use the float 32 variant of fftw
494 int rank = 2; /* 2D FFT */
495 int n[2] = { padded_width, padded_height };
496 int howmany = 4; /* 4 channels : RGBa */
497 int idist = 1; /* channels are distanced by one float in memory */
498 int odist = 1; /* channels are distanced by one float in memory */
499 int istride = 4; /* array is not contiguous in memory */
500 int ostride = 4; /* array is not contiguous in memory */
501 int *inembed = n;
502 int *onembed = n;
503
504 fftwf_complex *const restrict kernel_fft = fftwf_alloc_complex(padded_height * padded_height * 4);
505 fftwf_complex *const restrict image_fft = fftwf_alloc_complex(padded_height * padded_height * 4);
506
507 // FFT convert the kernel
508 fftwf_plan kernel_plan = fftwf_plan_many_dft_r2c(rank, n, howmany,
509 padded_kernel, inembed,
510 istride, idist,
511 kernel_fft, onembed,
512 ostride, odist,
513 FFTW_ESTIMATE);
514 fftwf_execute(kernel_plan);
515
516 // Clean FFT
517 fftwf_destroy_plan(kernel_plan);
518 fftwf_free(kernel_fft);
519 fftwf_free(image_fft);
520 fftw_cleanup_threads();
521
523 dt_free_align(padded_kernel);
524 dt_free_align(padded_in);
525 dt_free_align(padded_out);
526}
527#endif
528
529// Spatial convolution should be slower for large blurs because it is o(N²) where N is the width of the kernel
530// but code is much simpler and easier to debug
531
534 const void *const restrict ivoid, void *const restrict ovoid)
535{
536 const dt_iop_roi_t *const roi_in = &piece->roi_in;
537 const dt_iop_roi_t *const roi_out = &piece->roi_out;
539 const float scale = dt_dev_get_module_scale(pipe, roi_in);
540
541 const float *const restrict in = __builtin_assume_aligned(ivoid, 64);
542 float *const restrict out = __builtin_assume_aligned(ovoid, 64);
543
544 // Init the blur kernel
545 const int radius = MAX(roundf(p->radius / scale), 2);
546 const size_t kernel_width = 2 * radius + 1;
547
548 float *restrict kernel = dt_alloc_align_float(kernel_width * kernel_width);
549 if(IS_NULL_PTR(kernel)) return 1;
550 if(build_pixel_kernel(kernel, kernel_width, kernel_width, p))
551 {
553 return 1;
554 }
555 // The pipe abandoned this run: the rows not started yet are skipped and the module fails. One
556 // convolution of up to (2 * 128 + 1)² taps per pixel at full resolution has no other step to stop
557 // at than its rows. A new request may lower the killswitch again before the loop ends, hence the
558 // latch, shared since the macro makes every variable firstprivate.
559 dt_atomic_int abandoned = FALSE;
560 __OMP_PARALLEL_FOR__(shared(abandoned))
561 for(int i = 0; i < roi_out->height; i++)
562 {
564 {
565 dt_atomic_set_int(&abandoned, TRUE);
566 continue;
567 }
568 for(int j = 0; j < roi_out->width; j++)
569 {
570 const size_t index = ((i * roi_out->width) + j) * 4;
571 float DT_ALIGNED_PIXEL acc[4] = { 0.f };
572
573 if(i >= radius && j >= radius && i < roi_out->height - radius && j < roi_out->width - radius)
574 {
575 // We are in the safe area, no need to check for out-of-bounds
576 for(int l = -radius; l <= radius; l++)
577 for(int m = -radius; m <= radius; m++)
578 {
579 const int ii = i + l;
580 const int jj = j + m;
581 const size_t idx_shift = ((ii * roi_out->width) + jj) * 4;
582
583 const int ik = l + radius;
584 const int jk = m + radius;
585 const size_t idx_kernel = (ik * kernel_width) + jk;
586 const float k = kernel[idx_kernel];
587
588 for_four_channels(c, aligned(in : 64)) acc[c] += k * in[idx_shift + c];
589 }
590 }
591 else
592 {
593 // We are close to borders, we need to clamp indices to bounds
594 // assume constant boundary conditions
595 for(int l = -radius; l <= radius; l++)
596 for(int m = -radius; m <= radius; m++)
597 {
598 const int ii = CLAMP((int)i + l, (int)0, (int)roi_out->height - 1);
599 const int jj = CLAMP((int)j + m, (int)0, (int)roi_out->width - 1);
600 const size_t idx_shift = ((ii * roi_out->width) + jj) * 4;
601
602 const int ik = l + radius;
603 const int jk = m + radius;
604 const size_t idx_kernel = (ik * kernel_width) + jk;
605 const float k = kernel[idx_kernel];
606
607 for_four_channels(c, aligned(in : 64)) acc[c] += k * in[idx_shift + c];
608 }
609 }
610
611 for_each_channel(c, aligned(out : 64) aligned(acc : 16)) out[index + c] = acc[c];
612
613 // copy alpha
614 out[index + 3] = in[index + 3];
615 }
616 }
618 return dt_atomic_get_int(&abandoned);
619}
620
621
622#if HAVE_OPENCL
623int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
624{
625 const dt_iop_roi_t *const roi_in = &piece->roi_in;
626 const dt_iop_roi_t *const roi_out = &piece->roi_out;
629
630 cl_int err = -999;
631
632 const int devid = pipe->devid;
633 const int width = roi_in->width;
634 const int height = roi_in->height;
635
636 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
637
638 // Init the blur kernel
639 const float scale = dt_dev_get_module_scale(pipe, roi_in);
640 const int radius = MAX(roundf(p->radius / scale), 2);
641 const size_t kernel_width = 2 * radius + 1;
642
643 float *const restrict kernel = dt_alloc_align_float(kernel_width * kernel_width);
644 if(IS_NULL_PTR(kernel)) return FALSE;
645 if(build_pixel_kernel(kernel, kernel_width, kernel_width, p))
646 {
648 return FALSE;
649 }
650
651 cl_mem kernel_cl = dt_opencl_copy_host_to_device(devid, kernel, kernel_width, kernel_width, sizeof(float));
652
653 dt_opencl_set_kernel_arg(devid, gd->kernel_blurs_convolve, 0, sizeof(cl_mem), (void *)&dev_in);
654 dt_opencl_set_kernel_arg(devid, gd->kernel_blurs_convolve, 1, sizeof(cl_mem), (void *)&kernel_cl);
655 dt_opencl_set_kernel_arg(devid, gd->kernel_blurs_convolve, 2, sizeof(cl_mem), (void *)&dev_out);
656 dt_opencl_set_kernel_arg(devid, gd->kernel_blurs_convolve, 3, sizeof(int), (void *)&roi_out->width);
657 dt_opencl_set_kernel_arg(devid, gd->kernel_blurs_convolve, 4, sizeof(int), (void *)&roi_out->height);
658 dt_opencl_set_kernel_arg(devid, gd->kernel_blurs_convolve, 5, sizeof(int), (void *)&radius);
659
660 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_blurs_convolve, sizes);
661 if(err != CL_SUCCESS) goto error;
662
663 // cleanup and exit on success
666 return TRUE;
667
668error:
671 dt_print(DT_DEBUG_OPENCL, "[opencl_blurs] couldn't enqueue kernel! %d\n", err);
672 return FALSE;
673}
674
676{
677 const int program = 34;
679 module->data = gd;
680 gd->kernel_blurs_convolve = dt_opencl_create_kernel(program, "convolve");
681}
682
683
690#endif
691
692
693void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
694{
697
698 if(IS_NULL_PTR(w) || w == g->type)
699 {
700 if(p->type == DT_BLUR_LENS)
701 {
702 gtk_widget_hide(g->angle);
703 gtk_widget_hide(g->curvature);
704 gtk_widget_hide(g->offset);
705
706 gtk_widget_show(g->blades);
707 gtk_widget_show(g->concavity);
708 gtk_widget_show(g->rotation);
709 gtk_widget_show(g->linearity);
710 }
711 else if(p->type == DT_BLUR_MOTION)
712 {
713 gtk_widget_show(g->angle);
714 gtk_widget_show(g->curvature);
715 gtk_widget_show(g->offset);
716
717 gtk_widget_hide(g->blades);
718 gtk_widget_hide(g->concavity);
719 gtk_widget_hide(g->rotation);
720 gtk_widget_hide(g->linearity);
721 }
722 else if(p->type == DT_BLUR_GAUSSIAN)
723 {
724 gtk_widget_hide(g->angle);
725 gtk_widget_hide(g->curvature);
726 gtk_widget_hide(g->offset);
727
728 gtk_widget_hide(g->blades);
729 gtk_widget_hide(g->concavity);
730 gtk_widget_hide(g->rotation);
731 gtk_widget_hide(g->linearity);
732 }
733 }
734
735 // update kernel view
736 if(g->img_cached)
737 {
738 if(build_gui_kernel(g->img, g->img_width, g->img_width, p)) return;
739 gtk_widget_queue_draw(GTK_WIDGET(g->area));
740 }
741}
742
743static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
744{
745 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
748
749 GtkAllocation allocation;
750 GtkStyleContext *context = gtk_widget_get_style_context(widget);
751 gtk_widget_get_allocation(widget, &allocation);
752 gtk_render_background(context, crf, 0, 0, allocation.width, allocation.height);
753
754 if(allocation.width != g->img_width)
755 {
756 // Widget size changed, flush the cache buffer and restart
757 g->img_cached = FALSE;
758 dt_free_align(g->img);
759 g->img = NULL;
760 }
761
762 if(!g->img_cached)
763 {
764 g->img = dt_alloc_align(sizeof(unsigned char) * 4 * allocation.width * allocation.width);
765 if(IS_NULL_PTR(g->img)) return FALSE;
766 g->img_width = allocation.width;
767 g->img_cached = TRUE;
768 if(build_gui_kernel(g->img, g->img_width, g->img_width, p))
769 return FALSE;
770
771 // Note: if params change, we silently recompute the img in the buffer
772 // no need to flush the cache. Flush only if buffer size changes,
773 // aka GUI widget gets resized.
774 }
775
776 // Paint the kernel
777 const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, g->img_width);
778 cairo_surface_t *cst = cairo_image_surface_create_for_data(g->img, CAIRO_FORMAT_ARGB32,
779 g->img_width, g->img_width, stride);
780
781 cairo_set_source_surface(crf, cst, 0, 0);
782 cairo_paint(crf);
783 cairo_surface_destroy(cst);
784 return TRUE;
785}
786
788{
789// FIXME check why needed
790 gui_changed(self, NULL, NULL);
791}
792
793#define DEG_TO_RAD 180.f / M_PI_F
794
796{
798
799 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
800
801 // Image buffer to store the kernel look
802 // Don't recompute it in the drawing function, only when a param is changed
803 // then serve it from cache to the drawing function.
804 g->img_cached = FALSE;
805 g->img = NULL;
806 g->img_width = 0.f;
807
808 g->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(1.f));
809 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(dt_iop_tonecurve_draw), self);
810 gtk_box_pack_start(GTK_BOX(self->gui->widget), GTK_WIDGET(g->area), TRUE, TRUE, 0);
811
812 g->radius = dt_bauhaus_slider_from_params(self, "radius");
813 dt_bauhaus_slider_set_format(g->radius, " px");
814
815 g->type = dt_bauhaus_combobox_from_params(self, "type");
816
817 g->blades = dt_bauhaus_slider_from_params(self, "blades");
818 g->concavity = dt_bauhaus_slider_from_params(self, "concavity");
819 g->linearity = dt_bauhaus_slider_from_params(self, "linearity");
820 g->rotation = dt_bauhaus_slider_from_params(self, "rotation");
822 dt_bauhaus_slider_set_format(g->rotation, "\302\260");
823
824 g->angle = dt_bauhaus_slider_from_params(self, "angle");
826 dt_bauhaus_slider_set_format(g->angle, "\302\260");
827
828
829 g->curvature = dt_bauhaus_slider_from_params(self, "curvature");
830 g->offset = dt_bauhaus_slider_from_params(self, "offset");
831
832}
833
835{
837 dt_free_align(g->img);
838 g->img = NULL;
840}
841
842
843// clang-format off
844// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
845// vim: shiftwidth=2 expandtab tabstop=2 cindent
846// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
847// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_atomic_set_int(dt_atomic_int *var, int value)
int dt_atomic_get_int(dt_atomic_int *var)
atomic_int dt_atomic_int
Definition atomic.h:68
#define m
Definition basecurve.c:283
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3410
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3426
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid, void *const restrict ovoid)
Definition blurs.c:533
const char ** description(struct dt_iop_module_t *self)
Definition blurs.c:104
int default_group()
Definition blurs.c:118
static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
Definition blurs.c:743
#define DEG_TO_RAD
Definition blurs.c:793
void gui_update(dt_iop_module_t *self)
Definition blurs.c:787
static __DT_CLONE_TARGETS__ void create_gauss_kernel(float *const restrict buffer, const size_t width, const size_t height)
Definition blurs.c:272
const char * aliases()
Definition blurs.c:99
dt_iop_blur_type_t
Definition blurs.c:52
@ DT_BLUR_LENS
Definition blurs.c:53
@ DT_BLUR_GAUSSIAN
Definition blurs.c:55
@ DT_BLUR_MOTION
Definition blurs.c:54
static __DT_CLONE_TARGETS__ void normalize(float *const buffer, const size_t width, const size_t height, const float norm)
Definition blurs.c:352
static __DT_CLONE_TARGETS__ int build_gui_kernel(unsigned char *const buffer, const size_t width, const size_t height, dt_iop_blurs_params_t *p)
Definition blurs.c:295
const char * name()
Definition blurs.c:94
static __DT_CLONE_TARGETS__ void init_kernel(float *const restrict buffer, const size_t width, const size_t height)
Definition blurs.c:169
void gui_init(dt_iop_module_t *self)
Definition blurs.c:795
static void blur_2D_Bspline(const float *const restrict in, float *const restrict out, const size_t width, const size_t height)
Definition blurs.c:138
#define FSIZE
Definition blurs.c:136
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition blurs.c:693
void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition blurs.c:130
void gui_cleanup(dt_iop_module_t *self)
Definition blurs.c:834
void cleanup_global(dt_iop_module_so_t *module)
Definition blurs.c:684
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition blurs.c:124
static __DT_CLONE_TARGETS__ float compute_norm(float *const buffer, const size_t width, const size_t height)
Definition blurs.c:338
int flags()
Definition blurs.c:112
static __DT_CLONE_TARGETS__ void create_motion_kernel(float *const restrict buffer, const size_t width, const size_t height, const float angle, const float curvature, const float offset)
Definition blurs.c:213
static int build_pixel_kernel(float *const buffer, const size_t width, const size_t height, dt_iop_blurs_params_t *p)
Definition blurs.c:363
void init_global(dt_iop_module_so_t *module)
Definition blurs.c:675
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
Definition blurs.c:623
static __DT_CLONE_TARGETS__ void create_lens_kernel(float *const restrict buffer, const size_t width, const size_t height, const float n, const float m, const float k, const float rotation)
Definition blurs.c:178
@ IOP_CS_RGB
static const float x
#define B(y, x)
#define A(y, x)
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
static const float const float C
static const int row
for(size_t c=0;c< 3;c++) sRGB[c]
static const dt_colormatrix_t M
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:511
#define M_PI_F
void dt_iop_params_t
Definition dev_history.h:43
GtkWidget * dtgtk_drawing_area_new_with_aspect_ratio(double aspect)
Definition drawingarea.c:54
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1909
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:134
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_GROUP_SHARPNESS
Definition imageop.h:160
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
static float kernel(const float *x, const float *y)
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define DT_ALIGNED_PIXEL
Align a 4-float pixel on 16 bytes, enough for SSE. Same struct-member caveat as DT_ALIGNED_ARRAY,...
Definition mem_alloc.h:85
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
#define DT_ALIGNED_ARRAY
Align an object on a cacheline boundary, so AVX2 can load it whole.
Definition mem_alloc.h:80
static float * dt_alloc_align_float(size_t pixels)
Allocate pixels floats, cacheline-aligned and marked as such.
Definition mem_alloc.h:235
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define X
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2612
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2506
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2549
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2603
void * dt_opencl_copy_host_to_device(const int devid, void *host, const int width, const int height, const int bpp)
Definition opencl.c:2823
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2863
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
static gboolean dt_dev_pixelpipe_has_shutdown(const dt_dev_pixelpipe_t *pipe)
#define eps
Definition rcd.c:87
#define for_each_channel(_var,...)
Definition simd.h:87
#define for_four_channels(_var,...)
Definition simd.h:89
const float r
GtkWidget * rotation
Definition blurs.c:80
GtkWidget * blades
Definition blurs.c:80
GtkWidget * radius
Definition blurs.c:80
GtkWidget * offset
Definition blurs.c:80
GtkWidget * linearity
Definition blurs.c:80
GtkDrawingArea * area
Definition blurs.c:81
unsigned char * img
Definition blurs.c:82
GtkWidget * concavity
Definition blurs.c:80
GtkWidget * angle
Definition blurs.c:80
GtkWidget * curvature
Definition blurs.c:80
GtkWidget * type
Definition blurs.c:80
dt_iop_blur_type_t type
Definition blurs.c:61
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
dt_iop_global_data_t * global_data
Definition imageop.h:337
int32_t params_size
Definition imageop.h:335
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MAX(a, b)
Definition thinplate.c:29
#define DT_GUI_BOX_SPACING