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