Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
diffuse.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2021-2023, 2025-2026 Aurélien PIERRE.
4 Copyright (C) 2021 Chris Elston.
5 Copyright (C) 2021 Hubert Kowalski.
6 Copyright (C) 2021 luzpaz.
7 Copyright (C) 2021-2022 Pascal Obry.
8 Copyright (C) 2021-2022 quovadit.
9 Copyright (C) 2021 Ralf Brown.
10 Copyright (C) 2021-2022 Sakari Kapanen.
11 Copyright (C) 2021 Victor Forsiuk.
12 Copyright (C) 2022 Diederik Ter Rahe.
13 Copyright (C) 2022 Hanno Schwalm.
14 Copyright (C) 2022 Martin Bařinka.
15 Copyright (C) 2022 Philipp Lutz.
16 Copyright (C) 2023, 2025-2026 Guillaume Stutin.
17 Copyright (C) 2023 Luca Zulberti.
18 Copyright (C) 2024 Alynx Zhou.
19
20 Ansel is free software: you can redistribute it and/or modify
21 it under the terms of the GNU General Public License as published by
22 the Free Software Foundation, either version 3 of the License, or
23 (at your option) any later version.
24
25 Ansel is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 GNU General Public License for more details.
29
30 You should have received a copy of the GNU General Public License
31 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
32*/
33
34#ifdef HAVE_CONFIG_H
35#include "config.h"
36#endif
37#include "widgets/bauhaus.h"
38#include "pixel/bspline.h"
39#include "system/macros.h"
40#include "system/mem_alloc.h"
42#include "common/logging.h"
43#include "system/openmp.h"
44#include "system/simd.h"
47#include "pixel/dwt.h"
48#include "develop/iop_profile.h"
49#include "common/opencl.h"
50#include "develop/develop.h"
51#include "develop/imageop_gui.h"
52#include "iop/noise_generator.h"
53#include "math/openmp_maths.h"
54#include "develop/tiling.h"
55
56#include "gui/presets.h"
57#include "iop/iop_api.h"
58#include "widgets/label.h"
59
60// Set to one to output intermediate image steps as PFM in /tmp
61#define DEBUG_DUMP_PFM 0
62
63// Diffuse v3 adds a new parameter that allows more aggressive "sharpening"
64// (and mathematically-accurate multiscale handling...)
65// on coarse scales, ensuring each HF details band is normalized to the same
66// energy. This makes the `radius span` parameter much more impactful.
67#define DIFFUSE_V3 0
68
69#if DIFFUSE_V3
71#else
73#endif
74
75#define MAX_NUM_SCALES 10
77{
78 // global parameters
79 int iterations; // $MIN: 0 $MAX: 500 $DEFAULT: 1 $DESCRIPTION: "iterations"
80 float sharpness; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "sharpness"
81 int radius; // $MIN: 0 $MAX: 2048 $DEFAULT: 8 $DESCRIPTION: "radius span"
82 float regularization; // $MIN: 0. $MAX: 6. $DEFAULT: 0. $DESCRIPTION: "edge sensitivity"
83 float variance_threshold; // $MIN: -2. $MAX: 2. $DEFAULT: 0. $DESCRIPTION: "edge threshold"
84
85 float anisotropy_first; // $MIN: -10. $MAX: 10. $DEFAULT: 0. $DESCRIPTION: "1st order anisotropy"
86 float anisotropy_second; // $MIN: -10. $MAX: 10. $DEFAULT: 0. $DESCRIPTION: "2nd order anisotropy"
87 float anisotropy_third; // $MIN: -10. $MAX: 10. $DEFAULT: 0. $DESCRIPTION: "3rd order anisotropy"
88 float anisotropy_fourth; // $MIN: -10. $MAX: 10. $DEFAULT: 0. $DESCRIPTION: "4th order anisotropy"
89
90 float threshold; // $MIN: 0. $MAX: 8. $DEFAULT: 0. $DESCRIPTION: "luminance masking threshold"
91
92 float first; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "1st order speed"
93 float second; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "2nd order speed"
94 float third; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "3rd order speed"
95 float fourth; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "4th order speed"
96
97 // v2
98 int radius_center; // $MIN: 0 $MAX: 1024 $DEFAULT: 0 $DESCRIPTION: "central radius"
99
100 // new versions add params mandatorily at the end, so we can memcpy old parameters at the beginning
101
102 // v3 : Ansel 1.0
103 // bool normalize_band_energy; // $DEFAULT: FALSE $DESCRIPTION: "normalize coarse scales"
104 // When disabled, this will boost coarse scale sharpening by a lot.
105 // There is no reason to enable it for new edits,
106 // it's there to keep compatiblity with old edits.
107
109
110
116
129
130
131// only copy params struct to avoid a commit_params()
133
136{
137 memcpy(piece->data, params, self->params_size);
138 piece->cache_output_on_ram = TRUE;
139}
140
141
142typedef enum dt_isotropy_t
143{
144 DT_ISOTROPY_ISOTROPE = 0, // diffuse in all directions with same intensity
145 DT_ISOTROPY_ISOPHOTE = 1, // diffuse more in the isophote direction (orthogonal to gradient)
146 DT_ISOTROPY_GRADIENT = 2 // diffuse more in the gradient direction
148
149
151static inline dt_isotropy_t check_isotropy_mode(const float anisotropy)
152{
153 // user param is negative, positive or zero. The sign encodes the direction of diffusion, the magnitude encodes the ratio of anisotropy
154 // ultimately, the anisotropy factor needs to be positive before going into the exponential
155 if(anisotropy == 0.f)
157 else if(anisotropy > 0.f)
159 else
160 return DT_ISOTROPY_GRADIENT; // if(anisotropy > 0.f)
161}
162
163
164const char *name()
165{
166 return _("diffuse or _sharpen");
167}
168
169const char *aliases()
170{
171 return _("diffusion|deconvolution|blur|sharpening");
172}
173
174const char **description(struct dt_iop_module_t *self)
175{
176 return dt_iop_set_description(self,
177 _("simulate directional diffusion of light with heat transfer model\n"
178 "to apply an iterative edge-oriented blur,\n"
179 "inpaint damaged parts of the image,"
180 "or to remove blur with blind deconvolution."),
181 _("corrective and creative"),
182 _("linear, RGB, scene-referred"),
183 _("linear, RGB"),
184 _("linear, RGB, scene-referred"));
185}
186
188{
189 return IOP_GROUP_SHARPNESS;
190}
191
196
201
202int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
203 const int new_version)
204{
205 if(old_version == 1 && new_version == 2)
206 {
207 typedef struct dt_iop_diffuse_params_v1_t
208 {
209 // global parameters
210 int iterations;
211 float sharpness;
212 int radius;
213 float regularization;
214 float variance_threshold;
215
216 float anisotropy_first;
217 float anisotropy_second;
218 float anisotropy_third;
219 float anisotropy_fourth;
220
221 float threshold;
222
223 float first;
224 float second;
225 float third;
226 float fourth;
227 } dt_iop_diffuse_params_v1_t;
228
229 dt_iop_diffuse_params_v1_t *o = (dt_iop_diffuse_params_v1_t *)old_params;
232
233 *n = *d; // start with a fresh copy of default parameters
234
235 // copy common parameters
236 memcpy(n, o, sizeof(dt_iop_diffuse_params_v1_t));
237
238 // init only new parameters
239 n->radius_center = 0;
240
241#if !DIFFUSE_V3
242 // When version 3 will be out, we need to handle v1 -> v2 -> v3 conversion,
243 // so don't return just yet.
244 return 0;
245#endif
246 }
247
248#if DIFFUSE_V3
249 if(old_version == 2 && new_version == 3)
250 {
251 typedef struct dt_iop_diffuse_params_v2_t
252 {
253 // global parameters
254 int iterations; // $MIN: 0 $MAX: 500 $DEFAULT: 1 $DESCRIPTION: "iterations"
255 float sharpness; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "sharpness"
256 int radius; // $MIN: 0 $MAX: 2048 $DEFAULT: 8 $DESCRIPTION: "radius span"
257 float regularization; // $MIN: 0. $MAX: 8. $DEFAULT: 0. $DESCRIPTION: "edge sensitivity"
258 float variance_threshold; // $MIN: -3. $MAX: 3. $DEFAULT: 0. $DESCRIPTION: "edge threshold"
259
260 float anisotropy_first; // $MIN: -100. $MAX: 100. $DEFAULT: 0. $DESCRIPTION: "1st order anisotropy"
261 float anisotropy_second; // $MIN: -100. $MAX: 100. $DEFAULT: 0. $DESCRIPTION: "2nd order anisotropy"
262 float anisotropy_third; // $MIN: -100. $MAX: 100. $DEFAULT: 0. $DESCRIPTION: "3rd order anisotropy"
263 float anisotropy_fourth; // $MIN: -100. $MAX: 100. $DEFAULT: 0. $DESCRIPTION: "4th order anisotropy"
264
265 float threshold; // $MIN: 0. $MAX: 8. $DEFAULT: 0. $DESCRIPTION: "luminance masking threshold"
266
267 float first; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "1st order speed"
268 float second; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "2nd order speed"
269 float third; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "3rd order speed"
270 float fourth; // $MIN: -1. $MAX: 1. $DEFAULT: 0. $DESCRIPTION: "4th order speed"
271
272 // v2
273 int radius_center; // $MIN: 0 $MAX: 1024 $DEFAULT: 0 $DESCRIPTION: "central radius"
274
275 } dt_iop_diffuse_params_v2_t;
276
277 dt_iop_diffuse_params_v2_t *o = (dt_iop_diffuse_params_v2_t *)old_params;
280
281 *n = *d; // start with a fresh copy of default parameters
282
283 // copy common parameters
284 memcpy(n, o, sizeof(dt_iop_diffuse_params_v2_t));
285
286 // init only new parameters
287 n->normalize_band_energy = 1; // legacy compatiblity
288
289 return 0;
290 }
291#endif
292
293 return 1;
294}
295
297{
299 memset(&p, 0, sizeof(p));
300 p.radius_center = 0;
301
302 // deblurring presets
303 p.sharpness = 0.0f;
304 p.threshold = 0.0f;
305 p.variance_threshold = +0.f;
306 p.regularization = 1.f;
307
308 p.anisotropy_first = +2.f;
309 p.anisotropy_second = 0.f;
310 p.anisotropy_third = +2.f;
311 p.anisotropy_fourth = 0.f;
312
313 p.first = -0.25f;
314 p.second = +0.125f;
315 p.third = -0.125f;
316 p.fourth = +0.0625f;
317
318 p.radius = 8;
319 p.iterations = 8;
320 dt_gui_presets_add_generic(_("lens deblur: soft"), self->op, self->version(), &p, sizeof(p), 1);
321
322 p.radius = 10;
323 p.iterations = 16;
324 dt_gui_presets_add_generic(_("lens deblur: medium"), self->op, self->version(), &p, sizeof(p), 1);
325
326 p.radius = 12;
327 p.iterations = 24;
328 dt_gui_presets_add_generic(_("lens deblur: hard"), self->op, self->version(), &p, sizeof(p), 1);
329
330 p.iterations = 10;
331 p.radius = 512;
332 p.sharpness = 0.f;
333 p.variance_threshold = 0.f;
334 p.regularization = 2.5f;
335
336 p.first = -0.20f;
337 p.second = +0.10f;
338 p.third = -0.20f;
339 p.fourth = +0.10f;
340
341 p.anisotropy_first = 2.f;
342 p.anisotropy_second = 0.f;
343 p.anisotropy_third = 2.f;
344 p.anisotropy_fourth = 0.f;
345
346 dt_gui_presets_add_generic(_("dehaze"), self->op, self->version(), &p, sizeof(p), 1);
347
348 /* Denoise presets, retuned for issue #879 (they were too faint to see).
349 * Values are the RMSE optimum of a two-stage parametric sweep over
350 * synthetic Poisson-Gaussian noise, calibrated per ISO bucket from the
351 * median of 216 modern-camera profiles in noiseprofiles.json and validated
352 * across three pictures: fine ISO 400-800, medium 800-1600, coarse
353 * 1600-3200. The faintness had two causes: speeds far below useful, and
354 * edge sensitivity 4.0 throttling diffusion image-wide (2.5 wins at every
355 * level). The optima all sit at 0.20-0.25 of the 1.0 band-speed stability
356 * budget, and RMSE stays flat up to 1.0 — no degeneration nearby. Fine and
357 * medium share speeds on purpose: their differentiation is the radius,
358 * which is the physically right axis for grain size; coarse grain wants
359 * pure 3rd-order diffusion. */
360 p.iterations = 32;
361 p.sharpness = 0.f;
362 p.threshold = 0.f;
363 p.variance_threshold = -0.f;
364 p.regularization = 2.5f;
365
366 p.anisotropy_first = +2.f;
367 p.anisotropy_second = 0.f;
368 p.anisotropy_third = +2.f;
369 p.anisotropy_fourth = 0.f;
370
371 p.radius = 1;
372 p.radius_center = 2;
373
374 p.first = +0.10f;
375 p.second = 0.f;
376 p.third = +0.10f;
377 p.fourth = 0.f;
378 dt_gui_presets_add_generic(_("denoise: fine"), self->op, self->version(), &p, sizeof(p), 1);
379
380 p.radius = 3;
381 p.radius_center = 4;
382
383 p.first = +0.10f;
384 p.second = 0.f;
385 p.third = +0.10f;
386 p.fourth = 0.f;
387 dt_gui_presets_add_generic(_("denoise: medium"), self->op, self->version(), &p, sizeof(p), 1);
388
389 p.radius = 6;
390 p.radius_center = 8;
391
392 p.first = 0.f;
393 p.second = 0.f;
394 p.third = +0.25f;
395 p.fourth = 0.f;
396 dt_gui_presets_add_generic(_("denoise: coarse"), self->op, self->version(), &p, sizeof(p), 1);
397
398 p.radius_center = 0;
399
400 p.iterations = 2;
401 p.radius = 32;
402 p.sharpness = 0.0f;
403 p.threshold = 0.0f;
404 p.variance_threshold = 0.f;
405 p.regularization = 4.f;
406
407 p.anisotropy_first = +4.f;
408 p.anisotropy_second = +4.f;
409 p.anisotropy_third = +4.f;
410 p.anisotropy_fourth = +4.f;
411
412 p.first = +1.f;
413 p.second = +1.f;
414 p.third = +1.f;
415 p.fourth = +1.f;
416 dt_gui_presets_add_generic(_("surface blur"), self->op, self->version(), &p, sizeof(p), 1);
417
418 p.iterations = 1;
419 p.radius = 32;
420 p.sharpness = 0.0f;
421 p.threshold = 0.0f;
422 p.variance_threshold = 0.f;
423 p.regularization = 0.f;
424
425 p.anisotropy_first = 0.f;
426 p.anisotropy_second = 0.f;
427 p.anisotropy_third = 0.f;
428 p.anisotropy_fourth = 0.f;
429
430 p.first = +0.5f;
431 p.second = +0.5f;
432 p.third = +0.5f;
433 p.fourth = +0.5f;
434 dt_gui_presets_add_generic(_("bloom"), self->op, self->version(), &p, sizeof(p), 1);
435
436 p.iterations = 1;
437 p.radius = 4;
438 p.sharpness = 0.0f;
439 p.threshold = 0.0f;
440 p.variance_threshold = 0.f;
441 p.regularization = 1.f;
442
443 p.anisotropy_first = +1.f;
444 p.anisotropy_second = +1.f;
445 p.anisotropy_third = +1.f;
446 p.anisotropy_fourth = +1.f;
447
448 p.first = -0.25f;
449 p.second = -0.25f;
450 p.third = -0.25f;
451 p.fourth = -0.25f;
452 dt_gui_presets_add_generic(_("sharpen demosaicing (no AA filter)"), self->op, self->version(), &p, sizeof(p), 1);
453
454 p.radius = 8;
455 dt_gui_presets_add_generic(_("sharpen demosaicing (AA filter)"), self->op, self->version(), &p, sizeof(p), 1);
456
457 p.iterations = 4;
458 p.radius = 64;
459 p.sharpness = 0.0f;
460 p.threshold = 0.0f;
461 p.variance_threshold = 0.f;
462 p.regularization = 2.f;
463
464 p.anisotropy_first = 0.f;
465 p.anisotropy_second = 0.f;
466 p.anisotropy_third = +4.f;
467 p.anisotropy_fourth = +4.f;
468
469 p.first = 0.f;
470 p.second = 0.f;
471 p.third = +0.5f;
472 p.fourth = +0.5f;
473 dt_gui_presets_add_generic(_("simulate watercolor"), self->op, self->version(), &p, sizeof(p), 1);
474
475 p.iterations = 50;
476 p.radius = 64;
477 p.sharpness = 0.0f;
478 p.threshold = 0.0f;
479 p.variance_threshold = 0.f;
480 p.regularization = 4.f;
481
482 p.anisotropy_first = -5.f;
483 p.anisotropy_second = -5.f;
484 p.anisotropy_third = -5.f;
485 p.anisotropy_fourth = -5.f;
486
487 p.first = -1.f;
488 p.second = -1.f;
489 p.third = -1.f;
490 p.fourth = -1.f;
491 dt_gui_presets_add_generic(_("simulate line drawing"), self->op, self->version(), &p, sizeof(p), 1);
492
493 // local contrast
494 p.sharpness = 0.0f;
495 p.threshold = 0.0f;
496 p.variance_threshold = 0.f;
497
498 p.anisotropy_first = -2.5f;
499 p.anisotropy_second = 0.f;
500 p.anisotropy_third = 0.f;
501 p.anisotropy_fourth = -2.5f;
502
503 p.first = -0.50f;
504 p.second = 0.f;
505 p.third = 0.f;
506 p.fourth = -0.50f;
507
508 p.iterations = 10;
509 p.radius = 333;
510 p.radius_center = 512;
511 p.regularization = 0.1f;
512 dt_gui_presets_add_generic(_("add local contrast"), self->op, self->version(), &p, sizeof(p), 1);
513
514 p.iterations = 32;
515 p.radius = 4;
516 p.radius_center = 0;
517 p.sharpness = 0.0f;
518 p.threshold = 1.41f;
519 p.variance_threshold = 0.f;
520 p.regularization = 0.f;
521
522 p.anisotropy_first = +0.f;
523 p.anisotropy_second = +0.f;
524 p.anisotropy_third = +0.f;
525 p.anisotropy_fourth = +2.f;
526
527 p.first = +0.0f;
528 p.second = +0.0f;
529 p.third = +0.0f;
530 p.fourth = +0.5f;
531 dt_gui_presets_add_generic(_("inpaint highlights"), self->op, self->version(), &p, sizeof(p), 1);
532
533 // fast presets for slow hardware
534 p.radius_center = 0;
535 p.radius = 128;
536 p.sharpness = 0.0f;
537 p.threshold = 0.0f;
538 p.variance_threshold = 0.f;
539 p.regularization = 0.f;
540
541 p.anisotropy_first = 0.f;
542 p.anisotropy_second = 0.f;
543 p.anisotropy_third = 5.f;
544 p.anisotropy_fourth = 0.f;
545
546 p.first = 0.f;
547 p.second = 0.f;
548 p.third = -0.50f;
549 p.fourth = 0.f;
550
551 p.iterations = 1;
552 dt_gui_presets_add_generic(_("fast sharpness"), self->op, self->version(), &p, sizeof(p), 1);
553
554 p.radius_center = 512;
555 p.radius = 512;
556 p.sharpness = 0.0f;
557 p.threshold = 0.0f;
558 p.variance_threshold = 0.f;
559 p.regularization = 0.f;
560
561
562 p.anisotropy_first = 0.f;
563 p.anisotropy_second = 0.f;
564 p.anisotropy_third = 5.f;
565 p.anisotropy_fourth = 0.f;
566
567 p.first = 0.f;
568 p.second = 0.f;
569 p.third = -0.50f;
570 p.fourth = 0.f;
571
572 p.iterations = 1;
573 dt_gui_presets_add_generic(_("fast local contrast"), self->op, self->version(), &p, sizeof(p), 1);
574}
575
577{
578 const dt_iop_roi_t *const roi_in = &piece->roi_in;
580
581 const float zoom = dt_dev_get_module_scale(pipe, roi_in);
582 const float final_radius = (data->radius + data->radius_center) * 2.f / zoom;
583 const int diffusion_scales = num_steps_to_reach_equivalent_sigma(B_SPLINE_SIGMA, final_radius);
584 const int scales = CLAMP(diffusion_scales, 1, MAX_NUM_SCALES);
585 const int max_filter_radius = (1 << scales);
586
587 // Account for the exact full-frame buffers kept alive by the CPU/OpenCL paths:
588 // one borrowed input, one output, two temp ping-pong buffers, two low-pass ping-pong
589 // buffers, one stored detail buffer per wavelet scale, and one 8-bit mask.
590 tiling->factor = 6.0625f + scales;
591 tiling->factor_cl = 6.0625f + scales;
592
593 tiling->maxbuf = 1.0f;
594 tiling->maxbuf_cl = 1.0f;
595 tiling->overhead = 0;
596 tiling->overlap = max_filter_radius;
597 tiling->xalign = 1;
598 tiling->yalign = 1;
599 return;
600}
601
603static inline void init_reconstruct(float *const restrict reconstructed, const size_t width,
604 const size_t height)
605{
606// init the reconstructed buffer with non-clipped and partially clipped pixels
607 __OMP_PARALLEL_FOR_SIMD__(aligned(reconstructed:64))
608 for(size_t k = 0; k < height * width * 4; k++) reconstructed[k] = 0.f;
609
610}
611
612
613// Discretization parameters for the Partial Derivative Equation solver
614#define H 1 // spatial step
615#define KAPPA 0.25f // 0.25 if h = 1, 1 if h = 2
616
617
618static inline __attribute__((always_inline)) void find_gradients(const dt_aligned_pixel_simd_t pixels[9],
619 dt_aligned_pixel_simd_t xy[2])
620{
621 // Compute the gradient with centered finite differences in a 3x3 stencil
622 // warning : x is vertical, y is horizontal
623 const dt_aligned_pixel_simd_t half = dt_simd_set1(0.5f);
624 xy[0] = (pixels[7] - pixels[1]) * half;
625 xy[1] = (pixels[5] - pixels[3]) * half;
626}
627
628static inline __attribute__((always_inline)) void find_laplacians(const dt_aligned_pixel_simd_t pixels[9],
629 dt_aligned_pixel_simd_t xy[2])
630{
631 // Compute the laplacian with centered finite differences in a 3x3 stencil
632 // warning : x is vertical, y is horizontal
633 const dt_aligned_pixel_simd_t two = dt_simd_set1(2.f);
634 xy[0] = (pixels[7] + pixels[1]) - two * pixels[4];
635 xy[1] = (pixels[5] + pixels[3]) - two * pixels[4];
636}
637
638
639static inline __attribute__((always_inline)) void rotation_matrix_isophote(
640 const dt_aligned_pixel_simd_t c2, const dt_aligned_pixel_simd_t cos_theta_sin_theta,
641 const dt_aligned_pixel_simd_t cos_theta2, const dt_aligned_pixel_simd_t sin_theta2,
642 dt_aligned_pixel_simd_t a[2][2])
643{
644 // Write the coefficients of a square symmetrical matrice of rotation of the gradient :
645 // [[ a11, a12 ],
646 // [ a12, a22 ]]
647 // taken from https://www.researchgate.net/publication/220663968
648 // c dampens the gradient direction
649 a[0][0] = cos_theta2 + c2 * sin_theta2;
650 a[1][1] = c2 * cos_theta2 + sin_theta2;
651 a[0][1] = a[1][0] = (c2 - dt_simd_set1(1.f)) * cos_theta_sin_theta;
652}
653
654static inline __attribute__((always_inline)) void rotation_matrix_gradient(
655 const dt_aligned_pixel_simd_t c2, const dt_aligned_pixel_simd_t cos_theta_sin_theta,
656 const dt_aligned_pixel_simd_t cos_theta2, const dt_aligned_pixel_simd_t sin_theta2,
657 dt_aligned_pixel_simd_t a[2][2])
658{
659 // Write the coefficients of a square symmetrical matrice of rotation of the gradient :
660 // [[ a11, a12 ],
661 // [ a12, a22 ]]
662 // based on https://www.researchgate.net/publication/220663968 and inverted
663 // c dampens the isophote direction
664 a[0][0] = c2 * cos_theta2 + sin_theta2;
665 a[1][1] = cos_theta2 + c2 * sin_theta2;
666 a[0][1] = a[1][0] = (dt_simd_set1(1.f) - c2) * cos_theta_sin_theta;
667}
668
669
670static inline __attribute__((always_inline)) void build_matrix(const dt_aligned_pixel_simd_t a[2][2],
671 dt_aligned_pixel_simd_t kernel[9])
672{
673 const dt_aligned_pixel_simd_t half = dt_simd_set1(0.5f);
674 const dt_aligned_pixel_simd_t minus_two = dt_simd_set1(-2.f);
675 const dt_aligned_pixel_simd_t b11 = a[0][1] * half;
676 const dt_aligned_pixel_simd_t b13 = -b11;
677 const dt_aligned_pixel_simd_t b22 = minus_two * (a[0][0] + a[1][1]);
678
679 // build the kernel of rotated anisotropic laplacian
680 // from https://www.researchgate.net/publication/220663968 :
681 // [ [ a12 / 2, a22, -a12 / 2 ],
682 // [ a11, -2 (a11 + a22), a11 ],
683 // [ -a12 / 2, a22, a12 / 2 ] ]
684 // N.B. we have flipped the signs of the a12 terms
685 // compared to the paper. There's probably a mismatch
686 // of coordinate convention between the paper and the
687 // original derivation of this convolution mask
688 // (Witkin 1991, https://doi.org/10.1145/127719.122750).
689 kernel[0] = b11;
690 kernel[1] = a[1][1];
691 kernel[2] = b13;
692 kernel[3] = a[0][0];
693 kernel[4] = b22;
694 kernel[5] = a[0][0];
695 kernel[6] = b13;
696 kernel[7] = a[1][1];
697 kernel[8] = b11;
698}
699
700static inline __attribute__((always_inline)) void isotrope_laplacian(dt_aligned_pixel_simd_t kernel[9])
701{
702 // see in https://eng.aurelienpierre.com/2021/03/rotation-invariant-laplacian-for-2d-grids/#Second-order-isotropic-finite-differences
703 // for references (Oono & Puri)
704 const dt_aligned_pixel_simd_t corner = dt_simd_set1(0.25f);
705 const dt_aligned_pixel_simd_t edge = dt_simd_set1(0.5f);
706 const dt_aligned_pixel_simd_t center = dt_simd_set1(-3.f);
707 kernel[0] = corner;
708 kernel[1] = edge;
709 kernel[2] = corner;
710 kernel[3] = edge;
711 kernel[4] = center;
712 kernel[5] = edge;
713 kernel[6] = corner;
714 kernel[7] = edge;
715 kernel[8] = corner;
716}
717
718static inline __attribute__((always_inline)) void compute_kernel(
719 const dt_aligned_pixel_simd_t c2, const dt_aligned_pixel_simd_t cos_theta_sin_theta,
720 const dt_aligned_pixel_simd_t cos_theta2, const dt_aligned_pixel_simd_t sin_theta2,
721 const dt_isotropy_t isotropy_type, dt_aligned_pixel_simd_t kernel[9])
722{
723 // Build the matrix of rotation with anisotropy
724
725 switch(isotropy_type)
726 {
728 default:
729 {
730 isotrope_laplacian(kernel);
731 break;
732 }
734 {
735 dt_aligned_pixel_simd_t a[2][2] = { { dt_simd_set1(0.f) } };
736 rotation_matrix_isophote(c2, cos_theta_sin_theta, cos_theta2, sin_theta2, a);
737 build_matrix(a, kernel);
738 break;
739 }
741 {
742 dt_aligned_pixel_simd_t a[2][2] = { { dt_simd_set1(0.f) } };
743 rotation_matrix_gradient(c2, cos_theta_sin_theta, cos_theta2, sin_theta2, a);
744 build_matrix(a, kernel);
745 break;
746 }
747 }
748}
749
751static inline void heat_PDE_diffusion(const float *const restrict high_freq, const float *const restrict low_freq,
752 const uint8_t *const restrict mask, const int has_mask,
753 float *const restrict output, const size_t width,
754 const size_t height, const dt_aligned_pixel_simd_t anisotropy,
755 const dt_isotropy_t isotropy_type[4],
756 const float variance_threshold, const int mult,
757 const float normalized_regularization,
758 const dt_aligned_pixel_simd_t ABCD, const float strength,
759 const int use_nontemporal)
760{
761 // Simultaneous inpainting for image structure and texture using anisotropic heat transfer model
762 // https://www.researchgate.net/publication/220663968
763 // modified as follow :
764 // * apply it in a multi-scale wavelet setup : we basically solve it twice, on the wavelets LF and HF layers.
765 // * replace the manual texture direction/distance selection by an automatic detection similar to the structure one,
766 // * generalize the framework for isotropic diffusion and anisotropic weighted on the isophote direction
767 // * add an HF-band energy regularization to better avoid edges.
768 // The sharpness setting mimics the contrast equalizer effect by simply multiplying the HF by some gain.
769
770 float *const restrict out = DT_IS_ALIGNED(output);
771 const float *const restrict LF = DT_IS_ALIGNED(low_freq);
772 const float *const restrict HF = DT_IS_ALIGNED(high_freq);
773 const dt_aligned_pixel_simd_t zero = dt_simd_set1(0.f);
774 const dt_aligned_pixel_simd_t flt_min = dt_simd_set1(1e-8f);
775 const dt_aligned_pixel_simd_t variance_threshold_v = dt_simd_set1(variance_threshold);
776 const dt_aligned_pixel_simd_t normalized_regularization_v = dt_simd_set1(normalized_regularization);
777 const dt_aligned_pixel_simd_t strength_v = dt_simd_set1(strength);
778
780 for(size_t row = 0; row < height; ++row)
781 {
782 // interleave the order in which we process the rows so that we minimize cache misses
783 const size_t i = dwt_interleave_rows(row, height, mult);
784 // compute the 'above' and 'below' coordinates, clamping them to the image, once for the entire row
785 const size_t i_neighbours[3]
786 = { MAX((int)(i - mult * H), (int)0) * width, // x - mult
787 i * width, // x
788 MIN((int)(i + mult * H), (int)height - 1) * width }; // x + mult
789 for(size_t j = 0; j < width; ++j)
790 {
791 const size_t idx = (i * width + j);
792 const size_t index = idx * 4;
793 const uint8_t opacity = (has_mask) ? mask[idx] : 1;
794
795 if(opacity)
796 {
797 // non-local neighbours coordinates
798 const size_t j_neighbours[3]
799 = { MAX((int)(j - mult * H), (int)0), // y - mult
800 j, // y
801 MIN((int)(j + mult * H), (int)width - 1) }; // y + mult
802
803 // fetch non-local pixels and store them locally and contiguously
804 dt_aligned_pixel_simd_t neighbour_pixel_HF[9];
805 dt_aligned_pixel_simd_t neighbour_pixel_LF[9];
806 dt_aligned_pixel_simd_t energy = zero;
807
808 for(size_t ii = 0; ii < 3; ii++)
809 for(size_t jj = 0; jj < 3; jj++)
810 {
811 const size_t neighbor = 4 * (i_neighbours[ii] + j_neighbours[jj]);
812 const dt_aligned_pixel_simd_t hf_value = dt_load_simd_aligned(HF + neighbor);
813 const dt_aligned_pixel_simd_t lf_value = dt_load_simd_aligned(LF + neighbor);
814 neighbour_pixel_HF[3 * ii + jj] = hf_value;
815 neighbour_pixel_LF[3 * ii + jj] = lf_value;
816 // Clamp LF to a strictly positive floor to avoid divide-by-zero in
817 // the HF/LF energy estimate without branching per channel.
818 const dt_aligned_pixel_simd_t safe_lf = dt_simd_max_zero(lf_value - flt_min) + flt_min;
819 const dt_aligned_pixel_simd_t ratio = hf_value / safe_lf;
820 energy += ratio * ratio;
821 }
822
823 // normalized_regularization already folds together the user
824 // regularization, the 3x3-support averaging factor, the physical blur
825 // radius carried by the current wavelet band and its scale normalization.
826 energy = dt_simd_max_zero(variance_threshold_v + energy * normalized_regularization_v - flt_min) + flt_min;
827
828 // build the local anisotropic convolution filters for gradients and laplacians
829 dt_aligned_pixel_simd_t lf_gradient[2], hf_gradient[2]; // x, y for each channel
830 find_gradients(neighbour_pixel_LF, lf_gradient);
831 find_gradients(neighbour_pixel_HF, hf_gradient);
832
833 // c² in https://www.researchgate.net/publication/220663968
834 dt_aligned_pixel_simd_t c2[4];
835 dt_aligned_pixel_simd_t grad_x = lf_gradient[0];
836 dt_aligned_pixel_simd_t grad_y = lf_gradient[1];
837 dt_aligned_pixel_simd_t c2_first = zero;
838 dt_aligned_pixel_simd_t c2_third = zero;
839 dt_aligned_pixel_simd_t cos_theta_grad_sq = zero;
840 dt_aligned_pixel_simd_t sin_theta_grad_sq = zero;
841 dt_aligned_pixel_simd_t cos_theta_sin_theta_grad = zero;
843 {
844 const float magnitude_grad = dt_fast_hypotf(grad_x[c], grad_y[c]);
845 c2_first[c] = -magnitude_grad * anisotropy[0];
846 c2_third[c] = -magnitude_grad * anisotropy[2];
847 // Compute cos/sin(arg(grad)) with a branchless normalization, forcing
848 // arg(grad)=0 when magnitude is zero.
849 const float nonzero = (magnitude_grad != 0.f);
850 const float inv_mag = 1.f / (magnitude_grad + (1.f - nonzero));
851 grad_x[c] = grad_x[c] * inv_mag + (1.f - nonzero); // cos(0)
852 grad_y[c] = grad_y[c] * inv_mag; // sin(0)
853 // Warning : now gradient = { cos(arg(grad)) , sin(arg(grad)) }
854 cos_theta_grad_sq[c] = sqf(grad_x[c]);
855 sin_theta_grad_sq[c] = sqf(grad_y[c]);
856 cos_theta_sin_theta_grad[c] = grad_x[c] * grad_y[c];
857 }
858
859 c2[0] = c2_first;
860 c2[2] = c2_third;
861 dt_aligned_pixel_simd_t lapl_x = hf_gradient[0];
862 dt_aligned_pixel_simd_t lapl_y = hf_gradient[1];
863 dt_aligned_pixel_simd_t c2_second = zero;
864 dt_aligned_pixel_simd_t c2_fourth = zero;
865 dt_aligned_pixel_simd_t cos_theta_lapl_sq = zero;
866 dt_aligned_pixel_simd_t sin_theta_lapl_sq = zero;
867 dt_aligned_pixel_simd_t cos_theta_sin_theta_lapl = zero;
869 {
870 const float magnitude_lapl = dt_fast_hypotf(lapl_x[c], lapl_y[c]);
871 c2_second[c] = -magnitude_lapl * anisotropy[1];
872 c2_fourth[c] = -magnitude_lapl * anisotropy[3];
873 // Compute cos/sin(arg(lapl)) with a branchless normalization, forcing
874 // arg(lapl)=0 when magnitude is zero.
875 const float nonzero = (magnitude_lapl != 0.f);
876 const float inv_mag = 1.f / (magnitude_lapl + (1.f - nonzero));
877 lapl_x[c] = lapl_x[c] * inv_mag + (1.f - nonzero); // cos(0)
878 lapl_y[c] = lapl_y[c] * inv_mag; // sin(0)
879 // Warning : now laplacian = { cos(arg(lapl)) , sin(arg(lapl)) }
880 cos_theta_lapl_sq[c] = sqf(lapl_x[c]);
881 sin_theta_lapl_sq[c] = sqf(lapl_y[c]);
882 cos_theta_sin_theta_lapl[c] = lapl_x[c] * lapl_y[c];
883 }
884 c2[1] = c2_second;
885 c2[3] = c2_fourth;
886
887 // elements of c2 need to be expf(mag*anistropy), but we haven't applied the expf() yet. Do that now.
888 for(size_t k = 0; k < 4; k++)
889 for_each_channel(c) c2[k][c] = dt_fast_expf(c2[k][c]);
890
891 dt_aligned_pixel_simd_t kern_first[9], kern_second[9], kern_third[9], kern_fourth[9];
892 compute_kernel(c2[0], cos_theta_sin_theta_grad, cos_theta_grad_sq, sin_theta_grad_sq, isotropy_type[0],
893 kern_first);
894 compute_kernel(c2[1], cos_theta_sin_theta_lapl, cos_theta_lapl_sq, sin_theta_lapl_sq, isotropy_type[1],
895 kern_second);
896 compute_kernel(c2[2], cos_theta_sin_theta_grad, cos_theta_grad_sq, sin_theta_grad_sq, isotropy_type[2],
897 kern_third);
898 compute_kernel(c2[3], cos_theta_sin_theta_lapl, cos_theta_lapl_sq, sin_theta_lapl_sq, isotropy_type[3],
899 kern_fourth);
900
901 dt_aligned_pixel_simd_t derivatives[4] = { zero, zero, zero, zero };
902 // Convolve filters and accumulate the local HF band energy over the
903 // current 3x3 support. This is not a statistical variance estimator:
904 // HF is a band-pass residual, so we normalize each sample by the
905 // corresponding LF value before squaring it, then normalize the summed
906 // ratio by the physical kernel-variance increment of the current
907 // wavelet band.
908 for(size_t k = 0; k < 9; k++)
909 {
910 derivatives[0] = kern_first[k] * neighbour_pixel_LF[k] + derivatives[0];
911 derivatives[1] = kern_second[k] * neighbour_pixel_LF[k] + derivatives[1];
912 derivatives[2] = kern_third[k] * neighbour_pixel_HF[k] + derivatives[2];
913 derivatives[3] = kern_fourth[k] * neighbour_pixel_HF[k] + derivatives[3];
914 }
915
916 // compute the update
917 dt_aligned_pixel_simd_t update = derivatives[0] * ABCD[0];
918 update = derivatives[1] * ABCD[1] + update;
919 update = derivatives[2] * ABCD[2] + update;
920 update = derivatives[3] * ABCD[3] + update;
921 const dt_aligned_pixel_simd_t acc = neighbour_pixel_HF[4] * strength_v + update / energy;
922
923 if(use_nontemporal)
924 dt_store_simd_nontemporal(out + index, dt_simd_max_zero(acc + neighbour_pixel_LF[4]));
925 else
926 dt_store_simd_aligned(out + index, dt_simd_max_zero(acc + neighbour_pixel_LF[4]));
927 }
928 else
929 {
930 // only copy input to output, do nothing
931 if(use_nontemporal)
932 dt_store_simd_nontemporal(out + index, dt_simd_max_zero(dt_load_simd_aligned(HF + index)
933 + dt_load_simd_aligned(LF + index)));
934 else
935 dt_store_simd_aligned(out + index, dt_simd_max_zero(dt_load_simd_aligned(HF + index)
936 + dt_load_simd_aligned(LF + index)));
937 }
938 }
939 }
940
941
942 if(use_nontemporal)
943 dt_omploop_sfence(); // ensure the final nontemporal writeback completes before the caller reads out
944}
945
946static inline float compute_anisotropy_factor(const float user_param)
947{
948 // compute the inverse of the K param in c evaluation from
949 // https://www.researchgate.net/publication/220663968
950 // but in a perceptually-even way, for better GUI interaction
951 return sqf(user_param);
952}
953
954#if DEBUG_DUMP_PFM
956static void dump_PFM(const char *filename, const float* out, const uint32_t w, const uint32_t h)
957{
958 FILE *f = g_fopen(filename, "wb");
959 fprintf(f, "PF\n%d %d\n-1.0\n", w, h);
960 for(int j = h - 1 ; j >= 0 ; j--)
961 for(int i = 0 ; i < w ; i++)
962 for(int c = 0 ; c < 3 ; c++)
963 fwrite(out + (j * w + i) * 4 + c, 1, sizeof(float), f);
964 fclose(f);
965}
966#endif
967
969static inline int wavelets_process(const dt_dev_pixelpipe_t *pipe,
970 const float *const restrict in, float *const restrict reconstructed,
971 const uint8_t *const restrict mask, const size_t width,
972 const size_t height, const dt_iop_diffuse_data_t *const data,
973 const float zoom, const int scales,
974 const int has_mask,
975 float *const restrict HF[MAX_NUM_SCALES],
976 float *const restrict LF_odd,
977 float *const restrict LF_even)
978{
979 const dt_aligned_pixel_simd_t anisotropy
984
985 const dt_isotropy_t DT_ALIGNED_PIXEL isotropy_type[4]
990
991 const float regularization = powf(10.f, data->regularization) - 1.f;
992 const float variance_threshold = powf(10.f, data->variance_threshold);
993
994 // À trous decimated wavelet decompose
995 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
996 // the wavelets decomposition here is the same as the equalizer/atrous module,
997 float *restrict residual; // will store the temp buffer containing the last step of blur
998 // allocate a one-row temporary buffer for the decomposition
999 size_t padded_size;
1000 float *const tempbuf = dt_pixelpipe_cache_alloc_perthread_float(4 * width, &padded_size); //TODO: alloc in caller
1001 if(IS_NULL_PTR(tempbuf)) return 1;
1002
1003 for(int s = 0; s < scales; ++s)
1004 {
1005 // The pipe abandoned this run: stop between two scales, a run being up to 500 iterations of a
1006 // decomposition and a synthesis over up to MAX_NUM_SCALES full-image passes each.
1008 {
1010 return 1;
1011 }
1012
1013 const int mult = 1 << s;
1014
1015 const float *restrict buffer_in;
1016 float *restrict buffer_out;
1017
1018 if(s == 0)
1019 {
1020 buffer_in = in;
1021 buffer_out = LF_odd;
1022 }
1023 else if(s % 2 != 0)
1024 {
1025 buffer_in = LF_odd;
1026 buffer_out = LF_even;
1027 }
1028 else
1029 {
1030 buffer_in = LF_even;
1031 buffer_out = LF_odd;
1032 }
1033
1034 decompose_2D_Bspline(buffer_in, HF[s], buffer_out, width, height, mult, tempbuf, padded_size);
1035
1036 residual = buffer_out;
1037
1038#if DEBUG_DUMP_PFM
1039 char name[64];
1040 sprintf(name, "/tmp/scale-input-%i.pfm", s);
1041 dump_PFM(name, buffer_in, width, height);
1042
1043 sprintf(name, "/tmp/scale-blur-%i.pfm", s);
1044 dump_PFM(name, buffer_out, width, height);
1045#endif
1046 }
1048
1049 // will store the temp buffer NOT containing the last step of blur
1050 float *restrict temp = (residual == LF_even) ? LF_odd : LF_even;
1051 int count = 0;
1052
1053 for(int s = scales - 1; s > -1; --s)
1054 {
1055 // the same stop, between two scales of the synthesis
1056 if(dt_dev_pixelpipe_has_shutdown(pipe)) return 1;
1057
1058 const int mult = 1 << s;
1059 const float current_radius = equivalent_sigma_at_step(B_SPLINE_SIGMA, s);
1060 const float real_radius = current_radius * zoom;
1061
1062#if DIFFUSE_V3
1063 const float normalized_regularization =
1064 (data->normalize_band_energy)
1065 ? regularization * sqf(real_radius) / 9.f
1066 : regularization / 9.f;
1067#else
1068 const float normalized_regularization = regularization / 9.f * sqf(real_radius);
1069#endif
1070
1071 const float norm = expf(-sqf(real_radius - (float)data->radius_center) / sqf(data->radius));
1072
1073 const dt_aligned_pixel_simd_t ABCD = { data->first * KAPPA * norm,
1074 data->second * KAPPA * norm,
1075 data->third * KAPPA * norm,
1076 data->fourth * KAPPA * norm };
1077 const float strength = data->sharpness * norm + 1.f;
1078
1079 const float *restrict buffer_in;
1080 float *restrict buffer_out;
1081
1082 if(count == 0)
1083 {
1084 buffer_in = residual;
1085 buffer_out = temp;
1086 }
1087 else if(count % 2 != 0)
1088 {
1089 buffer_in = temp;
1090 buffer_out = residual;
1091 }
1092 else
1093 {
1094 buffer_in = residual;
1095 buffer_out = temp;
1096 }
1097
1098 if(s == 0) buffer_out = reconstructed;
1099
1100 heat_PDE_diffusion(HF[s], buffer_in, mask, has_mask, buffer_out, width, height,
1101 anisotropy, isotropy_type, variance_threshold, mult,
1102 normalized_regularization, ABCD, strength, (s == 0));
1103
1104 count++;
1105 }
1106
1107 return 0;
1108}
1109
1110
1112static inline void build_mask(const float *const restrict input, uint8_t *const restrict mask,
1113 const float threshold, const size_t width, const size_t height)
1114{
1115 __OMP_PARALLEL_FOR_SIMD__(aligned(mask, input : 64))
1116 for(size_t k = 0; k < height * width * 4; k += 4)
1117 {
1118 // TRUE if any channel is above threshold
1119 mask[k / 4] = (input[k] > threshold || input[k + 1] > threshold || input[k + 2] > threshold);
1120 }
1121
1122}
1123
1125static inline void inpaint_mask(float *const restrict inpainted, const float *const restrict original,
1126 const uint8_t *const restrict mask, const size_t width,
1127 const size_t height)
1128{
1129 // init the reconstruction with noise inside the masked areas
1131 for(size_t k = 0; k < height * width * 4; k += 4)
1132 {
1133 if(mask[k / 4])
1134 {
1135 const uint32_t i = k / width;
1136 const uint32_t j = k - i;
1137 uint32_t DT_ALIGNED_ARRAY state[4]
1138 = { splitmix32(j + 1), splitmix32((uint64_t)(j + 1) * (i + 3)),
1139 splitmix32(1337), splitmix32(666) };
1144
1145 for_four_channels(c, aligned(inpainted, original, state:64))
1146 inpainted[k + c] = fabsf(gaussian_noise(original[k + c], original[k + c], i % 2 || j % 2, state));
1147 }
1148 else
1149 {
1150 for_four_channels(c, aligned(original, inpainted:64))
1151 inpainted[k + c] = original[k + c];
1152 }
1153 }
1154
1155}
1156
1159 const void *const restrict ivoid, void *const restrict ovoid)
1160{
1161 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1162 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1163 const dt_iop_diffuse_data_t *const data = (dt_iop_diffuse_data_t *)piece->data;
1164
1165 float *restrict in = DT_IS_ALIGNED((float *const restrict)ivoid);
1166 float *const restrict out = DT_IS_ALIGNED((float *const restrict)ovoid);
1167
1168 float *const restrict temp1 = dt_pixelpipe_cache_alloc_align_float((size_t)roi_out->width * roi_out->height * 4, pipe);
1169 float *const restrict temp2 = dt_pixelpipe_cache_alloc_align_float((size_t)roi_out->width * roi_out->height * 4, pipe);
1170
1171 float *restrict temp_in = NULL;
1172 float *restrict temp_out = NULL;
1173 int err = 0;
1174
1175 uint8_t *const restrict mask = dt_pixelpipe_cache_alloc_align(
1176 sizeof(uint8_t) * roi_out->width * roi_out->height,
1177 pipe);
1178
1179 const float zoom = dt_dev_get_module_scale(pipe, roi_in);
1180 const float final_radius = (data->radius + data->radius_center) * 2.f / zoom;
1181 // No legacy iteration remap is applied here anymore. The current solver uses
1182 // the historical a-trous band order and kernel-variance increments exactly,
1183 // so any extra factor would be content-dependent and belong to pixel math,
1184 // not to the user parameter itself.
1185 const int iterations = MAX((int)ceilf((float)data->iterations), 1);
1186 const int diffusion_scales = num_steps_to_reach_equivalent_sigma(B_SPLINE_SIGMA, final_radius);
1187 const int scales = CLAMP(diffusion_scales, 1, MAX_NUM_SCALES);
1188
1189 gboolean out_of_memory = (IS_NULL_PTR(temp1)) || (IS_NULL_PTR(temp2));
1190 // One full-resolution buffer per stored wavelet band.
1191 float *restrict HF[MAX_NUM_SCALES] = { NULL };
1192 for(int s = 0; s < scales; s++)
1193 {
1194 HF[s] = dt_pixelpipe_cache_alloc_align_float(roi_out->width * roi_out->height * 4, pipe);
1195 if(!HF[s]) out_of_memory = TRUE;
1196 }
1197 // Two ping-pong low-pass buffers reused by the decomposition/synthesis.
1198 float *const restrict LF_odd = dt_pixelpipe_cache_alloc_align_float(roi_out->width * roi_out->height * 4, pipe);
1199 float *const restrict LF_even = dt_pixelpipe_cache_alloc_align_float(roi_out->width * roi_out->height * 4, pipe);
1200
1201 // PAUSE !
1202 // check that all buffers exist before processing,
1203 // because we use a lot of memory here.
1204 if(IS_NULL_PTR(mask) || IS_NULL_PTR(temp1) || IS_NULL_PTR(temp2) || IS_NULL_PTR(LF_odd) || IS_NULL_PTR(LF_even) || out_of_memory)
1205 {
1206 err = 1;
1207 goto error;
1208 }
1209
1210 const int has_mask = (data->threshold > 0.f);
1211
1212 if(has_mask)
1213 {
1214 // build a boolean mask, TRUE where image is above threshold, FALSE otherwise
1215 build_mask(in, mask, data->threshold, roi_out->width, roi_out->height);
1216
1217 // init the inpainting area with noise
1218 inpaint_mask(temp1, in, mask, roi_out->width, roi_out->height);
1219
1220 in = temp1;
1221 }
1222
1223 for(int it = 0; it < iterations; it++)
1224 {
1225 if(it == 0)
1226 {
1227 temp_in = in;
1228 temp_out = temp2;
1229 }
1230 else if(it % 2 == 0)
1231 {
1232 temp_in = temp1;
1233 temp_out = temp2;
1234 }
1235 else
1236 {
1237 temp_in = temp2;
1238 temp_out = temp1;
1239 }
1240
1241 if(it == (int)iterations - 1)
1242 temp_out = out;
1243
1244 if(wavelets_process(pipe, temp_in, temp_out, mask, roi_out->width, roi_out->height,
1245 data, zoom, scales, has_mask, HF, LF_odd, LF_even))
1246 {
1247 err = 1;
1248 goto error;
1249 }
1250 }
1251
1252error:
1258 for(int s = 0; s < scales; s++)
1259 if(HF[s]) dt_pixelpipe_cache_free_align(HF[s]);
1260 return err;
1261}
1262
1263#if HAVE_OPENCL
1264static inline cl_int wavelets_process_cl(const int devid, cl_mem in, cl_mem reconstructed, cl_mem mask,
1265 const size_t sizes[3], const int width, const int height,
1266 const dt_iop_diffuse_data_t *const data,
1268 const float zoom, const int scales,
1269 const int has_mask,
1270 cl_mem HF[MAX_NUM_SCALES],
1271 cl_mem LF_odd,
1272 cl_mem LF_even)
1273{
1274 cl_int err = -999;
1275
1276 const dt_aligned_pixel_simd_t anisotropy
1281
1282 /*
1283 fprintf(stdout, "anisotropy : %f ; %f ; %f ; %f \n",
1284 anisotropy[0], anisotropy[1], anisotropy[2], anisotropy[3]);
1285 */
1286
1287 const dt_isotropy_t DT_ALIGNED_PIXEL isotropy_type[4]
1292
1293 /*
1294 fprintf(stdout, "type : %d ; %d ; %d ; %d \n",
1295 isotropy_type[0], isotropy_type[1], isotropy_type[2], isotropy_type[3]);
1296 */
1297
1298 const float regularization = powf(10.f, data->regularization) - 1.f;
1299 const float variance_threshold = powf(10.f, data->variance_threshold);
1300 // Same a-trous decomposition as the CPU path, mirrored in OpenCL.
1301 cl_mem residual;
1302
1303 for(int s = 0; s < scales; ++s)
1304 {
1305 const int mult = 1 << s;
1306
1307 cl_mem buffer_in;
1308 cl_mem buffer_out;
1309
1310 if(s == 0)
1311 {
1312 buffer_in = in;
1313 buffer_out = LF_odd;
1314 }
1315 else if(s % 2 != 0)
1316 {
1317 buffer_in = LF_odd;
1318 buffer_out = LF_even;
1319 }
1320 else
1321 {
1322 buffer_in = LF_even;
1323 buffer_out = LF_odd;
1324 }
1325
1326 // Compute wavelets low-frequency scales
1327 const int clamp_lf = 1;
1328 int hblocksize;
1329 dt_opencl_local_buffer_t hlocopt = (dt_opencl_local_buffer_t){ .xoffset = 2 * mult, .xfactor = 1,
1330 .yoffset = 0, .yfactor = 1,
1331 .cellsize = 4 * sizeof(float), .overhead = 0,
1332 .sizex = 1 << 16, .sizey = 1 };
1334 hblocksize = hlocopt.sizex;
1335 else
1336 hblocksize = 1;
1337
1338 // Keep the same separable order as the CPU path: vertical pass first,
1339 // store its intermediate into HF[s], then horizontal pass builds LF.
1340 int vblocksize;
1341 dt_opencl_local_buffer_t vlocopt = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1,
1342 .yoffset = 2 * mult, .yfactor = 1,
1343 .cellsize = 4 * sizeof(float), .overhead = 0,
1344 .sizex = 1, .sizey = 1 << 16 };
1346 vblocksize = vlocopt.sizey;
1347 else
1348 vblocksize = 1;
1349
1350 if(vblocksize > 1)
1351 {
1352 const size_t vertical_sizes[3] = { ROUNDUPDWD(width, devid), ROUNDUP(height, vblocksize), 1 };
1353 const size_t vertical_local[3] = { 1, vblocksize, 1 };
1354 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 0, sizeof(cl_mem), (void *)&buffer_in);
1355 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 1, sizeof(cl_mem), (void *)&HF[s]);
1356 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 2, sizeof(int), (void *)&width);
1357 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 3, sizeof(int), (void *)&height);
1358 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 4, sizeof(int), (void *)&mult);
1359 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 5, sizeof(int), (void *)&clamp_lf);
1361 (vblocksize + 4 * mult) * 4 * sizeof(float), NULL);
1363 vertical_sizes, vertical_local);
1364 }
1365 else
1366 {
1367 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 0, sizeof(cl_mem), (void *)&buffer_in);
1368 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 1, sizeof(cl_mem), (void *)&HF[s]);
1369 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 2, sizeof(int), (void *)&width);
1370 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 3, sizeof(int), (void *)&height);
1371 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 4, sizeof(int), (void *)&mult);
1372 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 5, sizeof(int), (void *)&clamp_lf);
1374 }
1375 if(err != CL_SUCCESS) return err;
1376
1377 if(hblocksize > 1)
1378 {
1379 const size_t horizontal_sizes[3] = { ROUNDUP(width, hblocksize), ROUNDUPDHT(height, devid), 1 };
1380 const size_t horizontal_local[3] = { hblocksize, 1, 1 };
1381 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 0, sizeof(cl_mem), (void *)&HF[s]);
1382 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 1, sizeof(cl_mem), (void *)&buffer_out);
1383 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 2, sizeof(int), (void *)&width);
1384 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 3, sizeof(int), (void *)&height);
1385 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 4, sizeof(int), (void *)&mult);
1386 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 5, sizeof(int), (void *)&clamp_lf);
1388 (hblocksize + 4 * mult) * 4 * sizeof(float), NULL);
1390 horizontal_sizes, horizontal_local);
1391 }
1392 else
1393 {
1394 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 0, sizeof(cl_mem), (void *)&HF[s]);
1395 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 1, sizeof(cl_mem), (void *)&buffer_out);
1396 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 2, sizeof(int), (void *)&width);
1397 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 3, sizeof(int), (void *)&height);
1398 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 4, sizeof(int), (void *)&mult);
1399 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 5, sizeof(int), (void *)&clamp_lf);
1401 }
1402 if(err != CL_SUCCESS) return err;
1403
1404 // Compute wavelets high-frequency scales and backup the maximum of texture over the RGB channels
1405 // Note : HF = detail - LF
1406 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 0, sizeof(cl_mem), (void *)&buffer_in);
1407 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 1, sizeof(cl_mem), (void *)&buffer_out);
1408 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 2, sizeof(cl_mem), (void *)&HF[s]);
1409 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 3, sizeof(int), (void *)&width);
1410 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 4, sizeof(int), (void *)&height);
1412 if(err != CL_SUCCESS) return err;
1413
1414 residual = buffer_out;
1415 }
1416
1417 // Ping-pong low-pass buffer not currently holding the coarsest residual.
1418 cl_mem temp = (residual == LF_even) ? LF_odd : LF_even;
1419 int count = 0;
1420
1421 for(int s = scales - 1; s > -1; --s)
1422 {
1423 const int mult = 1 << s;
1424 const float current_radius = equivalent_sigma_at_step(B_SPLINE_SIGMA, s);
1425 const float real_radius = current_radius * zoom;
1426
1427#if DIFFUSE_V3
1428 const float normalized_regularization =
1429 (data->normalize_band_energy)
1430 ? regularization * sqf(real_radius) / 9.f
1431 : regularization / 9.f;
1432#else
1433 const float normalized_regularization = regularization / 9.f * sqf(real_radius);
1434#endif
1435
1436 const float norm = expf(-sqf(real_radius - (float)data->radius_center) / sqf(data->radius));
1437
1438 const dt_aligned_pixel_simd_t ABCD = { data->first * KAPPA * norm,
1439 data->second * KAPPA * norm,
1440 data->third * KAPPA * norm,
1441 data->fourth * KAPPA * norm };
1442 const float strength = data->sharpness * norm + 1.f;
1443
1444 cl_mem buffer_in;
1445 cl_mem buffer_out;
1446
1447 if(count == 0)
1448 {
1449 buffer_in = residual;
1450 buffer_out = temp;
1451 }
1452 else if(count % 2 != 0)
1453 {
1454 buffer_in = temp;
1455 buffer_out = residual;
1456 }
1457 else
1458 {
1459 buffer_in = residual;
1460 buffer_out = temp;
1461 }
1462
1463 if(s == 0) buffer_out = reconstructed;
1464
1465 // Compute wavelets low-frequency scales
1466 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 0, sizeof(cl_mem), (void *)&HF[s]);
1467 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 1, sizeof(cl_mem), (void *)&buffer_in);
1468 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 2, sizeof(cl_mem), (void *)&mask);
1469 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 3, sizeof(int), (void *)&has_mask);
1470 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 4, sizeof(cl_mem), (void *)&buffer_out);
1471 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 5, sizeof(int), (void *)&width);
1472 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 6, sizeof(int), (void *)&height);
1473 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 7, 4 * sizeof(float), (void *)&anisotropy);
1474 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 8, 4 * sizeof(dt_isotropy_t), (void *)&isotropy_type);
1475 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 9, sizeof(float), (void *)&normalized_regularization);
1476 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 10, sizeof(float), (void *)&variance_threshold);
1477 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 11, sizeof(int), (void *)&mult);
1478 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 12, 4 * sizeof(float), (void *)&ABCD);
1479 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 13, sizeof(float), (void *)&strength);
1480 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_diffuse_pde, sizes);
1481 if(err != CL_SUCCESS) return err;
1482
1483 count++;
1484 }
1485
1486 return err;
1487}
1488
1489int 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)
1490{
1491 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1492 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1493 const dt_iop_diffuse_data_t *const data = (dt_iop_diffuse_data_t *)piece->data;
1495
1496 int out_of_memory = FALSE;
1497 int success = FALSE;
1498
1499 cl_int err = -999;
1500
1501 const int devid = pipe->devid;
1502 const int width = roi_in->width;
1503 const int height = roi_in->height;
1504
1505 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
1506
1507 cl_mem in = dev_in;
1508
1509 cl_mem temp1 = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1510 cl_mem temp2 = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1511
1512 cl_mem mask = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(uint8_t));
1513
1514 const float zoom = dt_dev_get_module_scale(pipe, roi_in);
1515 const float final_radius = (data->radius + data->radius_center) * 2.f / zoom;
1516 // See the CPU path above: iterations stay in user space because the current
1517 // solver already matches the historical a-trous band ordering and kernel
1518 // variance increments. There is no content-independent remap left to apply.
1519 const int iterations = MAX((int)ceilf((float)data->iterations), 1);
1520 const int diffusion_scales = num_steps_to_reach_equivalent_sigma(B_SPLINE_SIGMA, final_radius);
1521 const int scales = CLAMP(diffusion_scales, 1, MAX_NUM_SCALES);
1522 // One device buffer per stored wavelet band.
1523 cl_mem HF[MAX_NUM_SCALES] = { NULL };
1524 for(int s = 0; s < scales; s++)
1525 {
1526 HF[s] = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1527 if(!HF[s]) out_of_memory = TRUE;
1528 }
1529 // Two low-pass ping-pong buffers reused across all scales.
1530 cl_mem LF_even = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1531 cl_mem LF_odd = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1532
1533 // PAUSE !
1534 // check that all buffers exist before processing,
1535 // because we use a lot of memory here.
1536 if(IS_NULL_PTR(mask) || IS_NULL_PTR(temp1) || IS_NULL_PTR(temp2) || IS_NULL_PTR(LF_odd) || IS_NULL_PTR(LF_even) || out_of_memory)
1537 {
1538 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1539 goto cleanup;
1540 }
1541
1542 const int has_mask = (data->threshold > 0.f);
1543
1544 if(has_mask)
1545 {
1546 // build a boolean mask, TRUE where image is above threshold, FALSE otherwise
1547 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 0, sizeof(cl_mem), (void *)&in);
1548 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 1, sizeof(cl_mem), (void *)&mask);
1549 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 2, sizeof(float), (void *)&data->threshold);
1550 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 3, sizeof(int), (void *)&roi_out->width);
1551 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 4, sizeof(int), (void *)&roi_out->height);
1553 if(err != CL_SUCCESS) goto cleanup;
1554
1555 // init the inpainting area with noise
1556 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 0, sizeof(cl_mem), (void *)&temp1);
1557 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 1, sizeof(cl_mem), (void *)&in);
1558 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 2, sizeof(cl_mem), (void *)&mask);
1559 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 3, sizeof(int), (void *)&roi_out->width);
1560 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 4, sizeof(int), (void *)&roi_out->height);
1562 if(err != CL_SUCCESS) goto cleanup;
1563
1564 in = temp1;
1565 }
1566
1567 // iteration 0 reads `in` and the last one writes `dev_out`; temp1 and temp2 take turns in between
1568 cl_mem ping_pong[2] = { temp1, temp2 };
1569 for(int it = 0; it < iterations; it++)
1570 {
1571 cl_mem temp_in = (it == 0) ? in : ping_pong[it % 2];
1572 cl_mem temp_out = (it == iterations - 1) ? dev_out : ping_pong[(it + 1) % 2];
1573 err = wavelets_process_cl(devid, temp_in, temp_out, mask, sizes, width, height,
1574 data, gd, zoom, scales, has_mask, HF, LF_odd, LF_even);
1575
1576 // The pipe abandoned this run: stop between two iterations. The host enqueues the whole run far
1577 // ahead of the device, so the killswitch is read once this iteration has run on it.
1578 if(err != CL_SUCCESS
1579 || (it < iterations - 1 && dt_opencl_finish(devid) && dt_dev_pixelpipe_has_shutdown(pipe)))
1580 goto cleanup;
1581 }
1582 success = TRUE;
1583
1584cleanup:
1590 for(int s = 0; s < scales; s++) dt_opencl_release_mem_object(HF[s]);
1591
1592 // an abandoned run comes here with the last iteration's CL_SUCCESS: there is no error to report
1593 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_diffuse] couldn't enqueue kernel! %d\n", err);
1594 return success;
1595}
1596
1598{
1599 const int program = 33; // diffuse.cl in programs.conf
1601
1602 module->data = gd;
1603 gd->kernel_diffuse_build_mask = dt_opencl_create_kernel(program, "build_mask");
1604 gd->kernel_diffuse_inpaint_mask = dt_opencl_create_kernel(program, "inpaint_mask");
1605 gd->kernel_diffuse_pde = dt_opencl_create_kernel(program, "diffuse_pde");
1606
1607 const int wavelets = 35; // bspline.cl, from programs.conf
1608 gd->kernel_filmic_bspline_horizontal = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_horizontal");
1609 gd->kernel_filmic_bspline_vertical = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_vertical");
1610 gd->kernel_filmic_bspline_horizontal_local = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_horizontal_local");
1611 gd->kernel_filmic_bspline_vertical_local = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_vertical_local");
1612 gd->kernel_filmic_wavelets_detail = dt_opencl_create_kernel(wavelets, "wavelets_detail_level");
1613}
1614
1615
1630#endif
1631
1632
1633void gui_init(struct dt_iop_module_t *self)
1634{
1636 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1637
1638 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("properties")), FALSE, FALSE, 0);
1639
1640 g->iterations = dt_bauhaus_slider_from_params(self, "iterations");
1641 dt_bauhaus_slider_set_soft_range(g->iterations, 1., 128);
1642 gtk_widget_set_tooltip_text(g->iterations,
1643 _("more iterations make the effect stronger but the module slower.\n"
1644 "this is analogous to giving more time to the diffusion reaction.\n"
1645 "if you plan on sharpening or inpainting, \n"
1646 "more iterations help reconstruction."));
1647
1648 g->radius_center = dt_bauhaus_slider_from_params(self, "radius_center");
1649 dt_bauhaus_slider_set_soft_range(g->radius_center, 0., 512.);
1650 dt_bauhaus_slider_set_format(g->radius_center, " px");
1651 gtk_widget_set_tooltip_text(
1652 g->radius_center, _("main scale of the diffusion.\n"
1653 "zero makes diffusion act on the finest details more heavily.\n"
1654 "non-zero defines the size of the details to diffuse heavily.\n"
1655 "for deblurring and denoising, set to zero.\n"
1656 "increase to act on local contrast instead."));
1657
1658 g->radius = dt_bauhaus_slider_from_params(self, "radius");
1659 dt_bauhaus_slider_set_soft_range(g->radius, 1., 512.);
1660 dt_bauhaus_slider_set_format(g->radius, " px");
1661 gtk_widget_set_tooltip_text(
1662 g->radius, _("width of the diffusion around the central radius.\n"
1663 "high values diffuse on a large band of radii.\n"
1664 "low values diffuse closer to the central radius.\n"
1665 "if you plan on deblurring, \n"
1666 "the radius should be around the width of your lens blur."));
1667
1668 GtkWidget *label_speed = dt_ui_section_label_new(_("speed (sharpen \342\206\224 diffuse)"));
1669 gtk_box_pack_start(GTK_BOX(self->gui->widget), label_speed, FALSE, FALSE, 0);
1670
1671 g->first = dt_bauhaus_slider_from_params(self, "first");
1673 dt_bauhaus_slider_set_format(g->first, "%");
1674 gtk_widget_set_tooltip_text(g->first, _("diffusion speed of low-frequency wavelet layers\n"
1675 "in the direction of 1st order anisotropy (set below).\n\n"
1676 "negative values sharpen, \n"
1677 "positive values diffuse and blur, \n"
1678 "zero does nothing."));
1679
1680 g->second = dt_bauhaus_slider_from_params(self, "second");
1681 dt_bauhaus_slider_set_digits(g->second, 4);
1682 dt_bauhaus_slider_set_format(g->second, "%");
1683 gtk_widget_set_tooltip_text(g->second, _("diffusion speed of low-frequency wavelet layers\n"
1684 "in the direction of 2nd order anisotropy (set below).\n\n"
1685 "negative values sharpen, \n"
1686 "positive values diffuse and blur, \n"
1687 "zero does nothing."));
1688
1689 g->third = dt_bauhaus_slider_from_params(self, "third");
1691 dt_bauhaus_slider_set_format(g->third, "%");
1692 gtk_widget_set_tooltip_text(g->third, _("diffusion speed of high-frequency wavelet layers\n"
1693 "in the direction of 3rd order anisotropy (set below).\n\n"
1694 "negative values sharpen, \n"
1695 "positive values diffuse and blur, \n"
1696 "zero does nothing."));
1697
1698 g->fourth = dt_bauhaus_slider_from_params(self, "fourth");
1699 dt_bauhaus_slider_set_digits(g->fourth, 4);
1700 dt_bauhaus_slider_set_format(g->fourth, "%");
1701 gtk_widget_set_tooltip_text(g->fourth, _("diffusion speed of high-frequency wavelet layers\n"
1702 "in the direction of 4th order anisotropy (set below).\n\n"
1703 "negative values sharpen, \n"
1704 "positive values diffuse and blur, \n"
1705 "zero does nothing."));
1706
1707 GtkWidget *label_direction = dt_ui_section_label_new(_("direction"));
1708 gtk_box_pack_start(GTK_BOX(self->gui->widget), label_direction, FALSE, FALSE, 0);
1709
1710 g->anisotropy_first = dt_bauhaus_slider_from_params(self, "anisotropy_first");
1711 dt_bauhaus_slider_set_digits(g->anisotropy_first, 4);
1712 dt_bauhaus_slider_set_format(g->anisotropy_first, "%");
1713 gtk_widget_set_tooltip_text(g->anisotropy_first, _("direction of 1st order speed (set above).\n\n"
1714 "negative values follow gradients more closely, \n"
1715 "positive values rather avoid edges (isophotes), \n"
1716 "zero affects both equally (isotropic)."));
1717
1718 g->anisotropy_second = dt_bauhaus_slider_from_params(self, "anisotropy_second");
1719 dt_bauhaus_slider_set_digits(g->anisotropy_second, 4);
1720 dt_bauhaus_slider_set_format(g->anisotropy_second, "%");
1721 gtk_widget_set_tooltip_text(g->anisotropy_second,_("direction of 2nd order speed (set above).\n\n"
1722 "negative values follow gradients more closely, \n"
1723 "positive values rather avoid edges (isophotes), \n"
1724 "zero affects both equally (isotropic)."));
1725
1726 g->anisotropy_third = dt_bauhaus_slider_from_params(self, "anisotropy_third");
1727 dt_bauhaus_slider_set_digits(g->anisotropy_third, 4);
1728 dt_bauhaus_slider_set_format(g->anisotropy_third, "%");
1729 gtk_widget_set_tooltip_text(g->anisotropy_third,_("direction of 3rd order speed (set above).\n\n"
1730 "negative values follow gradients more closely, \n"
1731 "positive values rather avoid edges (isophotes), \n"
1732 "zero affects both equally (isotropic)."));
1733
1734 g->anisotropy_fourth = dt_bauhaus_slider_from_params(self, "anisotropy_fourth");
1735 dt_bauhaus_slider_set_digits(g->anisotropy_fourth, 4);
1736 dt_bauhaus_slider_set_format(g->anisotropy_fourth, "%");
1737 gtk_widget_set_tooltip_text(g->anisotropy_fourth,_("direction of 4th order speed (set above).\n\n"
1738 "negative values follow gradients more closely, \n"
1739 "positive values rather avoid edges (isophotes), \n"
1740 "zero affects both equally (isotropic)."));
1741
1742 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("edge management")), FALSE, FALSE, 0);
1743
1744 g->sharpness = dt_bauhaus_slider_from_params(self, "sharpness");
1745 dt_bauhaus_slider_set_format(g->sharpness, "%");
1746 gtk_widget_set_tooltip_text(g->sharpness,
1747 _("increase or decrease the sharpness of the highest frequencies.\n"
1748 "can be used to keep details after blooming,\n"
1749 "for standalone sharpening set speed to negative values."));
1750
1751 g->regularization = dt_bauhaus_slider_from_params(self, "regularization");
1752 gtk_widget_set_tooltip_text(g->regularization,
1753 _("define the sensitivity of the variance penalty for edges.\n"
1754 "increase to exclude more edges from diffusion,\n"
1755 "if fringes or halos appear."));
1756
1757 g->variance_threshold = dt_bauhaus_slider_from_params(self, "variance_threshold");
1758 gtk_widget_set_tooltip_text(g->variance_threshold,
1759 _("define the variance threshold between edge amplification and penalty.\n"
1760 "decrease if you want pixels on smooth surfaces get a boost,\n"
1761 "increase if you see noise appear on smooth surfaces or\n"
1762 "if dark areas seem oversharpened compared to bright areas."));
1763
1764
1765 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("diffusion spatiality")), FALSE, FALSE, 0);
1766
1767 g->threshold = dt_bauhaus_slider_from_params(self, "threshold");
1768 dt_bauhaus_slider_set_format(g->threshold, "%");
1769 dt_bauhaus_slider_set_digits(g->threshold, 2);
1770 gtk_widget_set_tooltip_text(g->threshold,
1771 _("luminance threshold for the mask.\n"
1772 "0. disables the luminance masking and applies the module on the whole image.\n"
1773 "any higher value excludes pixels with luminance lower than the threshold.\n"
1774 "this can be used to inpaint highlights."));
1775}
1776// clang-format off
1777// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1778// vim: shiftwidth=2 expandtab tabstop=2 cindent
1779// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1780// 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 cleanup(dt_imageio_module_format_t *self)
Definition avif.c:170
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1498
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3346
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3410
#define B_SPLINE_SIGMA
Definition bspline.h:38
static unsigned int num_steps_to_reach_equivalent_sigma(const float sigma_filter, const float sigma_final)
Definition bspline.h:65
static float equivalent_sigma_at_step(const float sigma, const unsigned int s)
Definition bspline.h:52
static void decompose_2D_Bspline(const float *const restrict in, float *const restrict HF, float *const restrict LF, const size_t width, const size_t height, const int mult, float *const tempbuf, size_t padded_size)
Definition bspline.h:351
return vector dt_simd_set1(valid ?(scaling+NORM_MIN) :NORM_MIN)
@ IOP_CS_RGB
const float f
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const float threshold
const dt_colormatrix_t dt_aligned_pixel_t out
dt_store_simd_aligned(out, dt_mat3x4_mul_vec4(vin, dt_colormatrix_row_to_simd(matrix, 0), dt_colormatrix_row_to_simd(matrix, 1), dt_colormatrix_row_to_simd(matrix, 2)))
static const int row
static float strength(float value, float strength)
Definition colorzones.c:431
static unsigned int splitmix32(const unsigned long seed)
static float xoshiro128plus(uint state[4])
void dt_iop_params_t
Definition dev_history.h:43
const char ** description(struct dt_iop_module_t *self)
Definition diffuse.c:174
int default_group()
Definition diffuse.c:187
__DT_CLONE_TARGETS__ int process(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 diffuse.c:1158
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition diffuse.c:134
dt_isotropy_t
Definition diffuse.c:143
@ DT_ISOTROPY_ISOPHOTE
Definition diffuse.c:145
@ DT_ISOTROPY_ISOTROPE
Definition diffuse.c:144
@ DT_ISOTROPY_GRADIENT
Definition diffuse.c:146
static float compute_anisotropy_factor(const float user_param)
Definition diffuse.c:946
const char * aliases()
Definition diffuse.c:169
static __DT_CLONE_TARGETS__ void build_mask(const float *const restrict input, uint8_t *const restrict mask, const float threshold, const size_t width, const size_t height)
Definition diffuse.c:1112
static __DT_CLONE_TARGETS__ void heat_PDE_diffusion(const float *const restrict high_freq, const float *const restrict low_freq, const uint8_t *const restrict mask, const int has_mask, float *const restrict output, const size_t width, const size_t height, const dt_aligned_pixel_simd_t anisotropy, const dt_isotropy_t isotropy_type[4], const float variance_threshold, const int mult, const float normalized_regularization, const dt_aligned_pixel_simd_t ABCD, const float strength, const int use_nontemporal)
Definition diffuse.c:751
const char * name()
Definition diffuse.c:164
void gui_init(struct dt_iop_module_t *self)
Definition diffuse.c:1633
void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
Definition diffuse.c:576
void cleanup_global(dt_iop_module_so_t *module)
Definition diffuse.c:1616
static cl_int wavelets_process_cl(const int devid, cl_mem in, cl_mem reconstructed, cl_mem mask, const size_t sizes[3], const int width, const int height, const dt_iop_diffuse_data_t *const data, dt_iop_diffuse_global_data_t *const gd, const float zoom, const int scales, const int has_mask, cl_mem HF[10], cl_mem LF_odd, cl_mem LF_even)
Definition diffuse.c:1264
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition diffuse.c:197
int flags()
Definition diffuse.c:192
static __DT_CLONE_TARGETS__ int wavelets_process(const dt_dev_pixelpipe_t *pipe, const float *const restrict in, float *const restrict reconstructed, const uint8_t *const restrict mask, const size_t width, const size_t height, const dt_iop_diffuse_data_t *const data, const float zoom, const int scales, const int has_mask, float *const restrict HF[10], float *const restrict LF_odd, float *const restrict LF_even)
Definition diffuse.c:969
void init_presets(dt_iop_module_so_t *self)
Definition diffuse.c:296
#define H
Definition diffuse.c:614
#define MAX_NUM_SCALES
Definition diffuse.c:75
static __DT_CLONE_TARGETS__ void init_reconstruct(float *const restrict reconstructed, const size_t width, const size_t height)
Definition diffuse.c:603
#define KAPPA
Definition diffuse.c:615
static dt_isotropy_t check_isotropy_mode(const float anisotropy)
Definition diffuse.c:151
void init_global(dt_iop_module_so_t *module)
Definition diffuse.c:1597
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 diffuse.c:1489
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
Definition diffuse.c:202
static __DT_CLONE_TARGETS__ void inpaint_mask(float *const restrict inpainted, const float *const restrict original, const uint8_t *const restrict mask, const size_t width, const size_t height)
Definition diffuse.c:1125
static int dwt_interleave_rows(const int rowid, const int height, const int stride)
Definition dwt.h:93
void dt_gui_presets_add_generic(const char *name, dt_dev_operation_t op, const int32_t version, const void *params, const int32_t params_size, const int32_t enabled)
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_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_GROUP_SHARPNESS
Definition imageop.h:160
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
static float kernel(const float *x, const float *y)
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
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
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
#define ABCD(A, B, C, D)
@ 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_ALIGNED_ARRAY
Align an object on a cacheline boundary, so AVX2 can load it whole.
Definition mem_alloc.h:80
#define DT_IS_ALIGNED(x)
Promise the compiler that x is already cacheline-aligned, and return it.
Definition mem_alloc.h:51
#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 o
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
int dt_opencl_local_buffer_opt(const int devid, const int kernel, dt_opencl_local_buffer_t *factors)
Definition opencl.c:3786
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2612
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2952
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
gboolean dt_opencl_finish(const int devid)
Definition opencl.c:1712
int dt_opencl_enqueue_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2618
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2863
#define ROUNDUP(a, n)
Definition opencl.h:82
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define dt_omploop_sfence()
Definition openmp.h:164
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:100
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
#define dt_pixelpipe_cache_alloc_align(size, pipe)
#define dt_pixelpipe_cache_free_align(mem)
#define dt_pixelpipe_cache_alloc_align_float(pixels, pipe)
#define dt_pixelpipe_cache_alloc_perthread_float(n, padded_size)
static gboolean dt_dev_pixelpipe_has_shutdown(const dt_dev_pixelpipe_t *pipe)
#define for_each_channel(_var,...)
Definition simd.h:87
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
#define for_four_channels(_var,...)
Definition simd.h:89
const float uint32_t state[4]
unsigned __int64 uint64_t
Definition strptime.c:75
GtkWidget * anisotropy_third
Definition diffuse.c:114
GtkWidget * anisotropy_first
Definition diffuse.c:114
GtkWidget * regularization
Definition diffuse.c:113
GtkWidget * regularization_first
Definition diffuse.c:114
GtkWidget * anisotropy_second
Definition diffuse.c:114
GtkWidget * radius_center
Definition diffuse.c:113
GtkWidget * variance_threshold
Definition diffuse.c:114
GtkWidget * anisotropy_fourth
Definition diffuse.c:114
GtkWidget * widget
Definition imageop_gui.h:47
dt_dev_operation_t op
Definition imageop.h:235
dt_iop_global_data_t * data
Definition imageop.h:238
dt_iop_params_t * default_params
Definition imageop.h:333
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
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 MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
#define DT_GUI_BOX_SPACING