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 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
198{
199 return IOP_CS_RGB;
200}
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
576void 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)
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 float *const restrict in, float *const restrict reconstructed,
970 const uint8_t *const restrict mask, const size_t width,
971 const size_t height, const dt_iop_diffuse_data_t *const data,
972 const float zoom, const int scales,
973 const int has_mask,
974 float *const restrict HF[MAX_NUM_SCALES],
975 float *const restrict LF_odd,
976 float *const restrict LF_even)
977{
978 const dt_aligned_pixel_simd_t anisotropy
983
984 const dt_isotropy_t DT_ALIGNED_PIXEL isotropy_type[4]
989
990 const float regularization = powf(10.f, data->regularization) - 1.f;
991 const float variance_threshold = powf(10.f, data->variance_threshold);
992
993 // À trous decimated wavelet decompose
994 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
995 // the wavelets decomposition here is the same as the equalizer/atrous module,
996 float *restrict residual; // will store the temp buffer containing the last step of blur
997 // allocate a one-row temporary buffer for the decomposition
998 size_t padded_size;
999 float *const tempbuf = dt_pixelpipe_cache_alloc_perthread_float(4 * width, &padded_size); //TODO: alloc in caller
1000 if(IS_NULL_PTR(tempbuf)) return 1;
1001
1002 for(int s = 0; s < scales; ++s)
1003 {
1004 const int mult = 1 << s;
1005
1006 const float *restrict buffer_in;
1007 float *restrict buffer_out;
1008
1009 if(s == 0)
1010 {
1011 buffer_in = in;
1012 buffer_out = LF_odd;
1013 }
1014 else if(s % 2 != 0)
1015 {
1016 buffer_in = LF_odd;
1017 buffer_out = LF_even;
1018 }
1019 else
1020 {
1021 buffer_in = LF_even;
1022 buffer_out = LF_odd;
1023 }
1024
1025 decompose_2D_Bspline(buffer_in, HF[s], buffer_out, width, height, mult, tempbuf, padded_size);
1026
1027 residual = buffer_out;
1028
1029#if DEBUG_DUMP_PFM
1030 char name[64];
1031 sprintf(name, "/tmp/scale-input-%i.pfm", s);
1032 dump_PFM(name, buffer_in, width, height);
1033
1034 sprintf(name, "/tmp/scale-blur-%i.pfm", s);
1035 dump_PFM(name, buffer_out, width, height);
1036#endif
1037 }
1039
1040 // will store the temp buffer NOT containing the last step of blur
1041 float *restrict temp = (residual == LF_even) ? LF_odd : LF_even;
1042 int count = 0;
1043
1044 for(int s = scales - 1; s > -1; --s)
1045 {
1046 const int mult = 1 << s;
1047 const float current_radius = equivalent_sigma_at_step(B_SPLINE_SIGMA, s);
1048 const float real_radius = current_radius * zoom;
1049
1050#if DIFFUSE_V3
1051 const float normalized_regularization =
1052 (data->normalize_band_energy)
1053 ? regularization * sqf(real_radius) / 9.f
1054 : regularization / 9.f;
1055#else
1056 const float normalized_regularization = regularization / 9.f * sqf(real_radius);
1057#endif
1058
1059 const float norm = expf(-sqf(real_radius - (float)data->radius_center) / sqf(data->radius));
1060
1061 const dt_aligned_pixel_simd_t ABCD = { data->first * KAPPA * norm,
1062 data->second * KAPPA * norm,
1063 data->third * KAPPA * norm,
1064 data->fourth * KAPPA * norm };
1065 const float strength = data->sharpness * norm + 1.f;
1066
1067 const float *restrict buffer_in;
1068 float *restrict buffer_out;
1069
1070 if(count == 0)
1071 {
1072 buffer_in = residual;
1073 buffer_out = temp;
1074 }
1075 else if(count % 2 != 0)
1076 {
1077 buffer_in = temp;
1078 buffer_out = residual;
1079 }
1080 else
1081 {
1082 buffer_in = residual;
1083 buffer_out = temp;
1084 }
1085
1086 if(s == 0) buffer_out = reconstructed;
1087
1088 heat_PDE_diffusion(HF[s], buffer_in, mask, has_mask, buffer_out, width, height,
1089 anisotropy, isotropy_type, variance_threshold, mult,
1090 normalized_regularization, ABCD, strength, (s == 0));
1091
1092 count++;
1093 }
1094
1095 return 0;
1096}
1097
1098
1100static inline void build_mask(const float *const restrict input, uint8_t *const restrict mask,
1101 const float threshold, const size_t width, const size_t height)
1102{
1103 __OMP_PARALLEL_FOR_SIMD__(aligned(mask, input : 64))
1104 for(size_t k = 0; k < height * width * 4; k += 4)
1105 {
1106 // TRUE if any channel is above threshold
1107 mask[k / 4] = (input[k] > threshold || input[k + 1] > threshold || input[k + 2] > threshold);
1108 }
1109
1110}
1111
1113static inline void inpaint_mask(float *const restrict inpainted, const float *const restrict original,
1114 const uint8_t *const restrict mask, const size_t width,
1115 const size_t height)
1116{
1117 // init the reconstruction with noise inside the masked areas
1119 for(size_t k = 0; k < height * width * 4; k += 4)
1120 {
1121 if(mask[k / 4])
1122 {
1123 const uint32_t i = k / width;
1124 const uint32_t j = k - i;
1125 uint32_t DT_ALIGNED_ARRAY state[4]
1126 = { splitmix32(j + 1), splitmix32((uint64_t)(j + 1) * (i + 3)),
1127 splitmix32(1337), splitmix32(666) };
1132
1133 for_four_channels(c, aligned(inpainted, original, state:64))
1134 inpainted[k + c] = fabsf(gaussian_noise(original[k + c], original[k + c], i % 2 || j % 2, state));
1135 }
1136 else
1137 {
1138 for_four_channels(c, aligned(original, inpainted:64))
1139 inpainted[k + c] = original[k + c];
1140 }
1141 }
1142
1143}
1144
1147 const void *const restrict ivoid, void *const restrict ovoid)
1148{
1149 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1150 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1151 const dt_iop_diffuse_data_t *const data = (dt_iop_diffuse_data_t *)piece->data;
1152
1153 float *restrict in = DT_IS_ALIGNED((float *const restrict)ivoid);
1154 float *const restrict out = DT_IS_ALIGNED((float *const restrict)ovoid);
1155
1156 float *const restrict temp1 = dt_pixelpipe_cache_alloc_align_float((size_t)roi_out->width * roi_out->height * 4, pipe);
1157 float *const restrict temp2 = dt_pixelpipe_cache_alloc_align_float((size_t)roi_out->width * roi_out->height * 4, pipe);
1158
1159 float *restrict temp_in = NULL;
1160 float *restrict temp_out = NULL;
1161 int err = 0;
1162
1163 uint8_t *const restrict mask = dt_pixelpipe_cache_alloc_align(
1164 sizeof(uint8_t) * roi_out->width * roi_out->height,
1165 pipe);
1166
1167 const float zoom = dt_dev_get_module_scale(pipe, roi_in);
1168 const float final_radius = (data->radius + data->radius_center) * 2.f / zoom;
1169 // No legacy iteration remap is applied here anymore. The current solver uses
1170 // the historical a-trous band order and kernel-variance increments exactly,
1171 // so any extra factor would be content-dependent and belong to pixel math,
1172 // not to the user parameter itself.
1173 const int iterations = MAX((int)ceilf((float)data->iterations), 1);
1174 const int diffusion_scales = num_steps_to_reach_equivalent_sigma(B_SPLINE_SIGMA, final_radius);
1175 const int scales = CLAMP(diffusion_scales, 1, MAX_NUM_SCALES);
1176
1177 gboolean out_of_memory = (IS_NULL_PTR(temp1)) || (IS_NULL_PTR(temp2));
1178 // One full-resolution buffer per stored wavelet band.
1179 float *restrict HF[MAX_NUM_SCALES] = { NULL };
1180 for(int s = 0; s < scales; s++)
1181 {
1182 HF[s] = dt_pixelpipe_cache_alloc_align_float(roi_out->width * roi_out->height * 4, pipe);
1183 if(!HF[s]) out_of_memory = TRUE;
1184 }
1185 // Two ping-pong low-pass buffers reused by the decomposition/synthesis.
1186 float *const restrict LF_odd = dt_pixelpipe_cache_alloc_align_float(roi_out->width * roi_out->height * 4, pipe);
1187 float *const restrict LF_even = dt_pixelpipe_cache_alloc_align_float(roi_out->width * roi_out->height * 4, pipe);
1188
1189 // PAUSE !
1190 // check that all buffers exist before processing,
1191 // because we use a lot of memory here.
1192 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)
1193 {
1194 err = 1;
1195 goto error;
1196 }
1197
1198 const int has_mask = (data->threshold > 0.f);
1199
1200 if(has_mask)
1201 {
1202 // build a boolean mask, TRUE where image is above threshold, FALSE otherwise
1203 build_mask(in, mask, data->threshold, roi_out->width, roi_out->height);
1204
1205 // init the inpainting area with noise
1206 inpaint_mask(temp1, in, mask, roi_out->width, roi_out->height);
1207
1208 in = temp1;
1209 }
1210
1211 for(int it = 0; it < iterations; it++)
1212 {
1213 if(it == 0)
1214 {
1215 temp_in = in;
1216 temp_out = temp2;
1217 }
1218 else if(it % 2 == 0)
1219 {
1220 temp_in = temp1;
1221 temp_out = temp2;
1222 }
1223 else
1224 {
1225 temp_in = temp2;
1226 temp_out = temp1;
1227 }
1228
1229 if(it == (int)iterations - 1)
1230 temp_out = out;
1231
1232 if(wavelets_process(temp_in, temp_out, mask, roi_out->width, roi_out->height,
1233 data, zoom, scales, has_mask, HF, LF_odd, LF_even))
1234 {
1235 err = 1;
1236 goto error;
1237 }
1238 }
1239
1240error:
1246 for(int s = 0; s < scales; s++)
1247 if(HF[s]) dt_pixelpipe_cache_free_align(HF[s]);
1248 return err;
1249}
1250
1251#if HAVE_OPENCL
1252static inline cl_int wavelets_process_cl(const int devid, cl_mem in, cl_mem reconstructed, cl_mem mask,
1253 const size_t sizes[3], const int width, const int height,
1254 const dt_iop_diffuse_data_t *const data,
1256 const float zoom, const int scales,
1257 const int has_mask,
1258 cl_mem HF[MAX_NUM_SCALES],
1259 cl_mem LF_odd,
1260 cl_mem LF_even)
1261{
1262 cl_int err = -999;
1263
1264 const dt_aligned_pixel_simd_t anisotropy
1269
1270 /*
1271 fprintf(stdout, "anisotropy : %f ; %f ; %f ; %f \n",
1272 anisotropy[0], anisotropy[1], anisotropy[2], anisotropy[3]);
1273 */
1274
1275 const dt_isotropy_t DT_ALIGNED_PIXEL isotropy_type[4]
1280
1281 /*
1282 fprintf(stdout, "type : %d ; %d ; %d ; %d \n",
1283 isotropy_type[0], isotropy_type[1], isotropy_type[2], isotropy_type[3]);
1284 */
1285
1286 const float regularization = powf(10.f, data->regularization) - 1.f;
1287 const float variance_threshold = powf(10.f, data->variance_threshold);
1288 // Same a-trous decomposition as the CPU path, mirrored in OpenCL.
1289 cl_mem residual;
1290
1291 for(int s = 0; s < scales; ++s)
1292 {
1293 const int mult = 1 << s;
1294
1295 cl_mem buffer_in;
1296 cl_mem buffer_out;
1297
1298 if(s == 0)
1299 {
1300 buffer_in = in;
1301 buffer_out = LF_odd;
1302 }
1303 else if(s % 2 != 0)
1304 {
1305 buffer_in = LF_odd;
1306 buffer_out = LF_even;
1307 }
1308 else
1309 {
1310 buffer_in = LF_even;
1311 buffer_out = LF_odd;
1312 }
1313
1314 // Compute wavelets low-frequency scales
1315 const int clamp_lf = 1;
1316 int hblocksize;
1317 dt_opencl_local_buffer_t hlocopt = (dt_opencl_local_buffer_t){ .xoffset = 2 * mult, .xfactor = 1,
1318 .yoffset = 0, .yfactor = 1,
1319 .cellsize = 4 * sizeof(float), .overhead = 0,
1320 .sizex = 1 << 16, .sizey = 1 };
1322 hblocksize = hlocopt.sizex;
1323 else
1324 hblocksize = 1;
1325
1326 // Keep the same separable order as the CPU path: vertical pass first,
1327 // store its intermediate into HF[s], then horizontal pass builds LF.
1328 int vblocksize;
1329 dt_opencl_local_buffer_t vlocopt = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1,
1330 .yoffset = 2 * mult, .yfactor = 1,
1331 .cellsize = 4 * sizeof(float), .overhead = 0,
1332 .sizex = 1, .sizey = 1 << 16 };
1334 vblocksize = vlocopt.sizey;
1335 else
1336 vblocksize = 1;
1337
1338 if(vblocksize > 1)
1339 {
1340 const size_t vertical_sizes[3] = { ROUNDUPDWD(width, devid), ROUNDUP(height, vblocksize), 1 };
1341 const size_t vertical_local[3] = { 1, vblocksize, 1 };
1342 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 0, sizeof(cl_mem), (void *)&buffer_in);
1343 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 1, sizeof(cl_mem), (void *)&HF[s]);
1344 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 2, sizeof(int), (void *)&width);
1345 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 3, sizeof(int), (void *)&height);
1346 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 4, sizeof(int), (void *)&mult);
1347 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 5, sizeof(int), (void *)&clamp_lf);
1349 (vblocksize + 4 * mult) * 4 * sizeof(float), NULL);
1351 vertical_sizes, vertical_local);
1352 }
1353 else
1354 {
1355 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 0, sizeof(cl_mem), (void *)&buffer_in);
1356 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 1, sizeof(cl_mem), (void *)&HF[s]);
1357 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 2, sizeof(int), (void *)&width);
1358 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 3, sizeof(int), (void *)&height);
1359 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 4, sizeof(int), (void *)&mult);
1360 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 5, sizeof(int), (void *)&clamp_lf);
1362 }
1363 if(err != CL_SUCCESS) return err;
1364
1365 if(hblocksize > 1)
1366 {
1367 const size_t horizontal_sizes[3] = { ROUNDUP(width, hblocksize), ROUNDUPDHT(height, devid), 1 };
1368 const size_t horizontal_local[3] = { hblocksize, 1, 1 };
1369 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 0, sizeof(cl_mem), (void *)&HF[s]);
1370 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 1, sizeof(cl_mem), (void *)&buffer_out);
1371 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 2, sizeof(int), (void *)&width);
1372 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 3, sizeof(int), (void *)&height);
1373 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 4, sizeof(int), (void *)&mult);
1374 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 5, sizeof(int), (void *)&clamp_lf);
1376 (hblocksize + 4 * mult) * 4 * sizeof(float), NULL);
1378 horizontal_sizes, horizontal_local);
1379 }
1380 else
1381 {
1382 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 0, sizeof(cl_mem), (void *)&HF[s]);
1383 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 1, sizeof(cl_mem), (void *)&buffer_out);
1384 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 2, sizeof(int), (void *)&width);
1385 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 3, sizeof(int), (void *)&height);
1386 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 4, sizeof(int), (void *)&mult);
1387 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 5, sizeof(int), (void *)&clamp_lf);
1389 }
1390 if(err != CL_SUCCESS) return err;
1391
1392 // Compute wavelets high-frequency scales and backup the maximum of texture over the RGB channels
1393 // Note : HF = detail - LF
1394 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 0, sizeof(cl_mem), (void *)&buffer_in);
1395 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 1, sizeof(cl_mem), (void *)&buffer_out);
1396 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 2, sizeof(cl_mem), (void *)&HF[s]);
1397 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 3, sizeof(int), (void *)&width);
1398 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 4, sizeof(int), (void *)&height);
1400 if(err != CL_SUCCESS) return err;
1401
1402 residual = buffer_out;
1403 }
1404
1405 // Ping-pong low-pass buffer not currently holding the coarsest residual.
1406 cl_mem temp = (residual == LF_even) ? LF_odd : LF_even;
1407 int count = 0;
1408
1409 for(int s = scales - 1; s > -1; --s)
1410 {
1411 const int mult = 1 << s;
1412 const float current_radius = equivalent_sigma_at_step(B_SPLINE_SIGMA, s);
1413 const float real_radius = current_radius * zoom;
1414
1415#if DIFFUSE_V3
1416 const float normalized_regularization =
1417 (data->normalize_band_energy)
1418 ? regularization * sqf(real_radius) / 9.f
1419 : regularization / 9.f;
1420#else
1421 const float normalized_regularization = regularization / 9.f * sqf(real_radius);
1422#endif
1423
1424 const float norm = expf(-sqf(real_radius - (float)data->radius_center) / sqf(data->radius));
1425
1426 const dt_aligned_pixel_simd_t ABCD = { data->first * KAPPA * norm,
1427 data->second * KAPPA * norm,
1428 data->third * KAPPA * norm,
1429 data->fourth * KAPPA * norm };
1430 const float strength = data->sharpness * norm + 1.f;
1431
1432 cl_mem buffer_in;
1433 cl_mem buffer_out;
1434
1435 if(count == 0)
1436 {
1437 buffer_in = residual;
1438 buffer_out = temp;
1439 }
1440 else if(count % 2 != 0)
1441 {
1442 buffer_in = temp;
1443 buffer_out = residual;
1444 }
1445 else
1446 {
1447 buffer_in = residual;
1448 buffer_out = temp;
1449 }
1450
1451 if(s == 0) buffer_out = reconstructed;
1452
1453 // Compute wavelets low-frequency scales
1454 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 0, sizeof(cl_mem), (void *)&HF[s]);
1455 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 1, sizeof(cl_mem), (void *)&buffer_in);
1456 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 2, sizeof(cl_mem), (void *)&mask);
1457 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 3, sizeof(int), (void *)&has_mask);
1458 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 4, sizeof(cl_mem), (void *)&buffer_out);
1459 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 5, sizeof(int), (void *)&width);
1460 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 6, sizeof(int), (void *)&height);
1461 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 7, 4 * sizeof(float), (void *)&anisotropy);
1462 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 8, 4 * sizeof(dt_isotropy_t), (void *)&isotropy_type);
1463 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 9, sizeof(float), (void *)&normalized_regularization);
1464 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 10, sizeof(float), (void *)&variance_threshold);
1465 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 11, sizeof(int), (void *)&mult);
1466 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 12, 4 * sizeof(float), (void *)&ABCD);
1467 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_pde, 13, sizeof(float), (void *)&strength);
1468 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_diffuse_pde, sizes);
1469 if(err != CL_SUCCESS) return err;
1470
1471 count++;
1472 }
1473
1474 return err;
1475}
1476
1477int 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)
1478{
1479 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1480 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1481 const dt_iop_diffuse_data_t *const data = (dt_iop_diffuse_data_t *)piece->data;
1483
1484 int out_of_memory = FALSE;
1485
1486 cl_int err = -999;
1487
1488 const int devid = pipe->devid;
1489 const int width = roi_in->width;
1490 const int height = roi_in->height;
1491
1492 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
1493
1494 cl_mem in = dev_in;
1495
1496 cl_mem temp1 = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1497 cl_mem temp2 = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1498
1499 cl_mem temp_in = NULL;
1500 cl_mem temp_out = NULL;
1501
1502 cl_mem mask = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(uint8_t));
1503
1504 const float zoom = dt_dev_get_module_scale(pipe, roi_in);
1505 const float final_radius = (data->radius + data->radius_center) * 2.f / zoom;
1506 // See the CPU path above: iterations stay in user space because the current
1507 // solver already matches the historical a-trous band ordering and kernel
1508 // variance increments. There is no content-independent remap left to apply.
1509 const int iterations = MAX((int)ceilf((float)data->iterations), 1);
1510 const int diffusion_scales = num_steps_to_reach_equivalent_sigma(B_SPLINE_SIGMA, final_radius);
1511 const int scales = CLAMP(diffusion_scales, 1, MAX_NUM_SCALES);
1512 // One device buffer per stored wavelet band.
1513 cl_mem HF[MAX_NUM_SCALES] = { NULL };
1514 for(int s = 0; s < scales; s++)
1515 {
1516 HF[s] = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1517 if(!HF[s]) out_of_memory = TRUE;
1518 }
1519 // Two low-pass ping-pong buffers reused across all scales.
1520 cl_mem LF_even = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1521 cl_mem LF_odd = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1522
1523 // PAUSE !
1524 // check that all buffers exist before processing,
1525 // because we use a lot of memory here.
1526 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)
1527 {
1528 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1529 goto error;
1530 }
1531
1532 const int has_mask = (data->threshold > 0.f);
1533
1534 if(has_mask)
1535 {
1536 // build a boolean mask, TRUE where image is above threshold, FALSE otherwise
1537 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 0, sizeof(cl_mem), (void *)&in);
1538 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 1, sizeof(cl_mem), (void *)&mask);
1539 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 2, sizeof(float), (void *)&data->threshold);
1540 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 3, sizeof(int), (void *)&roi_out->width);
1541 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_build_mask, 4, sizeof(int), (void *)&roi_out->height);
1543 if(err != CL_SUCCESS) goto error;
1544
1545 // init the inpainting area with noise
1546 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 0, sizeof(cl_mem), (void *)&temp1);
1547 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 1, sizeof(cl_mem), (void *)&in);
1548 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 2, sizeof(cl_mem), (void *)&mask);
1549 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 3, sizeof(int), (void *)&roi_out->width);
1550 dt_opencl_set_kernel_arg(devid, gd->kernel_diffuse_inpaint_mask, 4, sizeof(int), (void *)&roi_out->height);
1552 if(err != CL_SUCCESS) goto error;
1553
1554 in = temp1;
1555 }
1556
1557 for(int it = 0; it < iterations; it++)
1558 {
1559 if(it == 0)
1560 {
1561 temp_in = in;
1562 temp_out = temp2;
1563 }
1564 else if(it % 2 == 0)
1565 {
1566 temp_in = temp1;
1567 temp_out = temp2;
1568 }
1569 else
1570 {
1571 temp_in = temp2;
1572 temp_out = temp1;
1573 }
1574
1575 if(it == (int)iterations - 1) temp_out = dev_out;
1576 err = wavelets_process_cl(devid, temp_in, temp_out, mask, sizes, width, height,
1577 data, gd, zoom, scales, has_mask, HF, LF_odd, LF_even);
1578 if(err != CL_SUCCESS) goto error;
1579 }
1580
1581 // cleanup and exit on success
1587 for(int s = 0; s < scales; s++) dt_opencl_release_mem_object(HF[s]);
1588 return TRUE;
1589
1590error:
1596 for(int s = 0; s < scales; s++) dt_opencl_release_mem_object(HF[s]);
1597
1598 dt_print(DT_DEBUG_OPENCL, "[opencl_diffuse] couldn't enqueue kernel! %d\n", err);
1599 return FALSE;
1600}
1601
1603{
1604 const int program = 33; // diffuse.cl in programs.conf
1606
1607 module->data = gd;
1608 gd->kernel_diffuse_build_mask = dt_opencl_create_kernel(program, "build_mask");
1609 gd->kernel_diffuse_inpaint_mask = dt_opencl_create_kernel(program, "inpaint_mask");
1610 gd->kernel_diffuse_pde = dt_opencl_create_kernel(program, "diffuse_pde");
1611
1612 const int wavelets = 35; // bspline.cl, from programs.conf
1613 gd->kernel_filmic_bspline_horizontal = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_horizontal");
1614 gd->kernel_filmic_bspline_vertical = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_vertical");
1615 gd->kernel_filmic_bspline_horizontal_local = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_horizontal_local");
1616 gd->kernel_filmic_bspline_vertical_local = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_vertical_local");
1617 gd->kernel_filmic_wavelets_detail = dt_opencl_create_kernel(wavelets, "wavelets_detail_level");
1618}
1619
1620
1635#endif
1636
1637
1638void gui_init(struct dt_iop_module_t *self)
1639{
1641 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1642
1643 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("properties")), FALSE, FALSE, 0);
1644
1645 g->iterations = dt_bauhaus_slider_from_params(self, "iterations");
1646 dt_bauhaus_slider_set_soft_range(g->iterations, 1., 128);
1647 gtk_widget_set_tooltip_text(g->iterations,
1648 _("more iterations make the effect stronger but the module slower.\n"
1649 "this is analogous to giving more time to the diffusion reaction.\n"
1650 "if you plan on sharpening or inpainting, \n"
1651 "more iterations help reconstruction."));
1652
1653 g->radius_center = dt_bauhaus_slider_from_params(self, "radius_center");
1654 dt_bauhaus_slider_set_soft_range(g->radius_center, 0., 512.);
1655 dt_bauhaus_slider_set_format(g->radius_center, " px");
1656 gtk_widget_set_tooltip_text(
1657 g->radius_center, _("main scale of the diffusion.\n"
1658 "zero makes diffusion act on the finest details more heavily.\n"
1659 "non-zero defines the size of the details to diffuse heavily.\n"
1660 "for deblurring and denoising, set to zero.\n"
1661 "increase to act on local contrast instead."));
1662
1663 g->radius = dt_bauhaus_slider_from_params(self, "radius");
1664 dt_bauhaus_slider_set_soft_range(g->radius, 1., 512.);
1665 dt_bauhaus_slider_set_format(g->radius, " px");
1666 gtk_widget_set_tooltip_text(
1667 g->radius, _("width of the diffusion around the central radius.\n"
1668 "high values diffuse on a large band of radii.\n"
1669 "low values diffuse closer to the central radius.\n"
1670 "if you plan on deblurring, \n"
1671 "the radius should be around the width of your lens blur."));
1672
1673 GtkWidget *label_speed = dt_ui_section_label_new(_("speed (sharpen \342\206\224 diffuse)"));
1674 gtk_box_pack_start(GTK_BOX(self->gui->widget), label_speed, FALSE, FALSE, 0);
1675
1676 g->first = dt_bauhaus_slider_from_params(self, "first");
1678 dt_bauhaus_slider_set_format(g->first, "%");
1679 gtk_widget_set_tooltip_text(g->first, _("diffusion speed of low-frequency wavelet layers\n"
1680 "in the direction of 1st order anisotropy (set below).\n\n"
1681 "negative values sharpen, \n"
1682 "positive values diffuse and blur, \n"
1683 "zero does nothing."));
1684
1685 g->second = dt_bauhaus_slider_from_params(self, "second");
1686 dt_bauhaus_slider_set_digits(g->second, 4);
1687 dt_bauhaus_slider_set_format(g->second, "%");
1688 gtk_widget_set_tooltip_text(g->second, _("diffusion speed of low-frequency wavelet layers\n"
1689 "in the direction of 2nd order anisotropy (set below).\n\n"
1690 "negative values sharpen, \n"
1691 "positive values diffuse and blur, \n"
1692 "zero does nothing."));
1693
1694 g->third = dt_bauhaus_slider_from_params(self, "third");
1696 dt_bauhaus_slider_set_format(g->third, "%");
1697 gtk_widget_set_tooltip_text(g->third, _("diffusion speed of high-frequency wavelet layers\n"
1698 "in the direction of 3rd order anisotropy (set below).\n\n"
1699 "negative values sharpen, \n"
1700 "positive values diffuse and blur, \n"
1701 "zero does nothing."));
1702
1703 g->fourth = dt_bauhaus_slider_from_params(self, "fourth");
1704 dt_bauhaus_slider_set_digits(g->fourth, 4);
1705 dt_bauhaus_slider_set_format(g->fourth, "%");
1706 gtk_widget_set_tooltip_text(g->fourth, _("diffusion speed of high-frequency wavelet layers\n"
1707 "in the direction of 4th order anisotropy (set below).\n\n"
1708 "negative values sharpen, \n"
1709 "positive values diffuse and blur, \n"
1710 "zero does nothing."));
1711
1712 GtkWidget *label_direction = dt_ui_section_label_new(_("direction"));
1713 gtk_box_pack_start(GTK_BOX(self->gui->widget), label_direction, FALSE, FALSE, 0);
1714
1715 g->anisotropy_first = dt_bauhaus_slider_from_params(self, "anisotropy_first");
1716 dt_bauhaus_slider_set_digits(g->anisotropy_first, 4);
1717 dt_bauhaus_slider_set_format(g->anisotropy_first, "%");
1718 gtk_widget_set_tooltip_text(g->anisotropy_first, _("direction of 1st order speed (set above).\n\n"
1719 "negative values follow gradients more closely, \n"
1720 "positive values rather avoid edges (isophotes), \n"
1721 "zero affects both equally (isotropic)."));
1722
1723 g->anisotropy_second = dt_bauhaus_slider_from_params(self, "anisotropy_second");
1724 dt_bauhaus_slider_set_digits(g->anisotropy_second, 4);
1725 dt_bauhaus_slider_set_format(g->anisotropy_second, "%");
1726 gtk_widget_set_tooltip_text(g->anisotropy_second,_("direction of 2nd order speed (set above).\n\n"
1727 "negative values follow gradients more closely, \n"
1728 "positive values rather avoid edges (isophotes), \n"
1729 "zero affects both equally (isotropic)."));
1730
1731 g->anisotropy_third = dt_bauhaus_slider_from_params(self, "anisotropy_third");
1732 dt_bauhaus_slider_set_digits(g->anisotropy_third, 4);
1733 dt_bauhaus_slider_set_format(g->anisotropy_third, "%");
1734 gtk_widget_set_tooltip_text(g->anisotropy_third,_("direction of 3rd order speed (set above).\n\n"
1735 "negative values follow gradients more closely, \n"
1736 "positive values rather avoid edges (isophotes), \n"
1737 "zero affects both equally (isotropic)."));
1738
1739 g->anisotropy_fourth = dt_bauhaus_slider_from_params(self, "anisotropy_fourth");
1740 dt_bauhaus_slider_set_digits(g->anisotropy_fourth, 4);
1741 dt_bauhaus_slider_set_format(g->anisotropy_fourth, "%");
1742 gtk_widget_set_tooltip_text(g->anisotropy_fourth,_("direction of 4th order speed (set above).\n\n"
1743 "negative values follow gradients more closely, \n"
1744 "positive values rather avoid edges (isophotes), \n"
1745 "zero affects both equally (isotropic)."));
1746
1747 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("edge management")), FALSE, FALSE, 0);
1748
1749 g->sharpness = dt_bauhaus_slider_from_params(self, "sharpness");
1750 dt_bauhaus_slider_set_format(g->sharpness, "%");
1751 gtk_widget_set_tooltip_text(g->sharpness,
1752 _("increase or decrease the sharpness of the highest frequencies.\n"
1753 "can be used to keep details after blooming,\n"
1754 "for standalone sharpening set speed to negative values."));
1755
1756 g->regularization = dt_bauhaus_slider_from_params(self, "regularization");
1757 gtk_widget_set_tooltip_text(g->regularization,
1758 _("define the sensitivity of the variance penalty for edges.\n"
1759 "increase to exclude more edges from diffusion,\n"
1760 "if fringes or halos appear."));
1761
1762 g->variance_threshold = dt_bauhaus_slider_from_params(self, "variance_threshold");
1763 gtk_widget_set_tooltip_text(g->variance_threshold,
1764 _("define the variance threshold between edge amplification and penalty.\n"
1765 "decrease if you want pixels on smooth surfaces get a boost,\n"
1766 "increase if you see noise appear on smooth surfaces or\n"
1767 "if dark areas seem oversharpened compared to bright areas."));
1768
1769
1770 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("diffusion spatiality")), FALSE, FALSE, 0);
1771
1772 g->threshold = dt_bauhaus_slider_from_params(self, "threshold");
1773 dt_bauhaus_slider_set_format(g->threshold, "%");
1774 dt_bauhaus_slider_set_digits(g->threshold, 2);
1775 gtk_widget_set_tooltip_text(g->threshold,
1776 _("luminance threshold for the mask.\n"
1777 "0. disables the luminance masking and applies the module on the whole image.\n"
1778 "any higher value excludes pixels with luminance lower than the threshold.\n"
1779 "this can be used to inpaint highlights."));
1780}
1781// clang-format off
1782// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1783// vim: shiftwidth=2 expandtab tabstop=2 cindent
1784// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1785// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_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:3343
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
#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
static __DT_CLONE_TARGETS__ int wavelets_process(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
__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:1146
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:1100
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:1638
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:1621
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:1252
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
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:1602
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:1477
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:1113
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: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_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
#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 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:3713
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2894
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
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:2560
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#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)
#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
struct dt_iop_module_t *void * data
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