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