Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
denoiseprofile.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011 Bruce Guenter.
4 Copyright (C) 2010-2014, 2016, 2018 johannes hanika.
5 Copyright (C) 2011 Henrik Andersson.
6 Copyright (C) 2011 Kanstantsin Shautsou.
7 Copyright (C) 2011 Pascal de Bruijn.
8 Copyright (C) 2011 Robert Bieber.
9 Copyright (C) 2011-2019 Tobias Ellinghaus.
10 Copyright (C) 2011-2017, 2020 Ulrich Pegelow.
11 Copyright (C) 2012 Jean-Sébastien Pédron.
12 Copyright (C) 2012 Jesper Pedersen.
13 Copyright (C) 2012, 2018-2022 Pascal Obry.
14 Copyright (C) 2012 Richard Wonka.
15 Copyright (C) 2013 Dennis Gnad.
16 Copyright (C) 2013 Edouard Gomez.
17 Copyright (C) 2013, 2021 parafin.
18 Copyright (C) 2013-2016 Roman Lebedev.
19 Copyright (C) 2013 Simon Spannagel.
20 Copyright (C) 2013 Thomas Pryds.
21 Copyright (C) 2015 Jan Kundrát.
22 Copyright (C) 2015 Pedro Côrte-Real.
23 Copyright (C) 2017-2018 Heiko Bauke.
24 Copyright (C) 2017, 2019, 2022 luzpaz.
25 Copyright (C) 2018-2020, 2022-2023, 2025-2026 Aurélien PIERRE.
26 Copyright (C) 2018 Bill Ferguson.
27 Copyright (C) 2018-2019 Edgardo Hoszowski.
28 Copyright (C) 2018 Maurizio Paglia.
29 Copyright (C) 2018-2021 rawfiner.
30 Copyright (C) 2019 Andreas Schneider.
31 Copyright (C) 2019-2022 Diederik Ter Rahe.
32 Copyright (C) 2019 Diederik ter Rahe.
33 Copyright (C) 2019 Hartmut Knaack.
34 Copyright (C) 2019 Sam Smith.
35 Copyright (C) 2020 Aldric Renaudin.
36 Copyright (C) 2020-2021 Chris Elston.
37 Copyright (C) 2020-2021 Hubert Kowalski.
38 Copyright (C) 2020-2021 Ralf Brown.
39 Copyright (C) 2021 Dan Torop.
40 Copyright (C) 2022 Hanno Schwalm.
41 Copyright (C) 2022 Martin Bařinka.
42 Copyright (C) 2022 Philipp Lutz.
43
44 darktable is free software: you can redistribute it and/or modify
45 it under the terms of the GNU General Public License as published by
46 the Free Software Foundation, either version 3 of the License, or
47 (at your option) any later version.
48
49 darktable is distributed in the hope that it will be useful,
50 but WITHOUT ANY WARRANTY; without even the implied warranty of
51 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
52 GNU General Public License for more details.
53
54 You should have received a copy of the GNU General Public License
55 along with darktable. If not, see <http://www.gnu.org/licenses/>.
56*/
57#ifdef HAVE_CONFIG_H
60#include "common/conf.h"
61#include "config.h"
62#endif
63#include "widgets/bauhaus.h"
64#include "system/macros.h"
65#include "system/openmp.h"
67#include "system/mem_alloc.h"
68#include "system/simd.h"
69#include "common/logging.h"
71#include <json-glib/json-glib.h>
72#include "pixel/eaw.h"
73#include "common/imagebuf.h"
74#include "pixel/nlmeans_core.h"
76#include "common/opencl.h"
77#include "develop/imageop.h"
79#include "develop/imageop_gui.h"
81#include "develop/tiling.h"
82
83#include "widgets/draw.h"
84#include "gui/presets.h"
85#include "iop/iop_api.h"
86
87#include <gtk/gtk.h>
88#include <math.h>
89#include <stdlib.h>
90#include "widgets/label.h"
91#include "widgets/notebook.h"
92#include "widgets/scroll_wrap.h"
93#include "gui/screen_metrics.h"
94// which version of the non-local means code should be used? 0=old (this file), 1=new (src/common/nlmeans_core.c)
95#define USE_NEW_IMPL_CL 0
96
97// should we dump the generated wavelet scales to files in /tmp ?
98//#define DEBUG_SCALES
99
100#define REDUCESIZE 64
101// number of intermediate buffers used by OpenCL code path. Needs to match value in src/common/nlmeans_core.c
102// to correctly compute tiling
103#define NUM_BUCKETS 4
104
105#define DT_IOP_DENOISE_PROFILE_INSET DT_PIXEL_APPLY_DPI(5)
106#define DT_IOP_DENOISE_PROFILE_RES 64
107#define DT_IOP_DENOISE_PROFILE_V8_BANDS 5
108#define DT_IOP_DENOISE_PROFILE_BANDS 7
109
110// the following fulcrum is used to help user to set shadows and strength
111// parameters.
112// applying precondition on this value will give the same value even
113// if shadows slider is changed, as strength will be adjusted to
114// guarantee that.
115// from a user point of view, it separates "shadows" area from the rest
116// of the image.
117#define DT_IOP_DENOISE_PROFILE_P_FULCRUM 0.05f
118
126
128 MODE_RGB = 0, // $DESCRIPTION: "RGB"
129 MODE_Y0U0V0 = 1 // $DESCRIPTION: "Y0U0V0"
131
132#define DT_DENOISE_PROFILE_NONE_V9 4
143
144// this is the version of the modules parameters,
145// and includes version information about compile-time dt
147
149{
150 float radius; // search radius
151 float strength; // noise level after equalization
152 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
153 dt_iop_denoiseprofile_mode_t mode; // switch between nlmeans and wavelets
155
157{
158 float radius; // search radius
159 float strength; // noise level after equalization
160 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
161 dt_iop_denoiseprofile_mode_t mode; // switch between nlmeans and wavelets
163 float y[DT_DENOISE_PROFILE_NONE_V9][DT_IOP_DENOISE_PROFILE_V8_BANDS]; // values to change wavelet force by frequency
165
167{
168 float radius; // patch size
169 float nbhood; // search radius
170 float strength; // noise level after equalization
171 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
172 dt_iop_denoiseprofile_mode_t mode; // switch between nlmeans and wavelets
174 float y[DT_DENOISE_PROFILE_NONE_V9][DT_IOP_DENOISE_PROFILE_V8_BANDS]; // values to change wavelet force by frequency
176
178{
179 float radius; // patch size
180 float nbhood; // search radius
181 float strength; // noise level after equalization
182 float scattering; // spread the patch search zone without increasing number of patches
183 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
184 dt_iop_denoiseprofile_mode_t mode; // switch between nlmeans and wavelets
186 float y[DT_DENOISE_PROFILE_NONE_V9][DT_IOP_DENOISE_PROFILE_V8_BANDS]; // values to change wavelet force by frequency
188
190{
191 float radius; // patch size
192 float nbhood; // search radius
193 float strength; // noise level after equalization
194 float scattering; // spread the patch search zone without increasing number of patches
195 float central_pixel_weight; // increase central pixel's weight in patch comparison
196 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
197 dt_iop_denoiseprofile_mode_t mode; // switch between nlmeans and wavelets
199 float y[DT_DENOISE_PROFILE_NONE_V9][DT_IOP_DENOISE_PROFILE_V8_BANDS]; // values to change wavelet force by frequency
200 gboolean wb_adaptive_anscombe; // whether to adapt anscombe transform to wb coeffs
201 // backward compatibility options
204
206{
207 float radius; // patch size
208 float nbhood; // search radius
209 float strength; // noise level after equalization
210 float shadows; // control the impact on shadows
211 float bias; // allows to reduce backtransform bias
212 float scattering; // spread the patch search zone without increasing number of patches
213 float central_pixel_weight; // increase central pixel's weight in patch comparison
214 float overshooting; // adjusts the way parameters are autoset
215 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
216 dt_iop_denoiseprofile_mode_t mode; // switch between nlmeans and wavelets
218 float y[DT_DENOISE_PROFILE_NONE_V9][DT_IOP_DENOISE_PROFILE_V8_BANDS]; // values to change wavelet force by frequency
219 gboolean wb_adaptive_anscombe; // whether to adapt anscombe transform to wb coeffs
220 // backward compatibility options
222 gboolean use_new_vst;
224
226{
227 float radius; // patch size
228 float nbhood; // search radius
229 float strength; // noise level after equalization
230 float shadows; // control the impact on shadows
231 float bias; // allows to reduce backtransform bias
232 float scattering; // spread the patch search zone without increasing number of patches
233 float central_pixel_weight; // increase central pixel's weight in patch comparison
234 float overshooting; // adjusts the way parameters are autoset
235 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
236 dt_iop_denoiseprofile_mode_t mode; // switch between nlmeans and wavelets
238 float y[DT_DENOISE_PROFILE_NONE_V9][DT_IOP_DENOISE_PROFILE_BANDS]; // values to change wavelet force by frequency
239 gboolean wb_adaptive_anscombe; // whether to adapt anscombe transform to wb coeffs
240 // backward compatibility options
242 gboolean use_new_vst;
244
246{
247 float radius; /* patch size
248 $MIN: 0.0 $MAX: 12.0 $DEFAULT: 1.0 $DESCRIPTION: "patch size" */
249 float nbhood; /* search radius
250 $MIN: 1.0 $MAX: 30.0 $DEFAULT: 7.0 $DESCRIPTION: "search radius" */
251 float strength; /* noise level after equalization
252 $MIN: 0.001 $MAX: 1000.0 $DEFAULT: 1.0 */
253 float shadows; /* control the impact on shadows
254 $MIN: 0.0 $MAX: 1.8 $DEFAULT: 1.0 $DESCRIPTION: "preserve shadows" */
255 float bias; /* allows to reduce backtransform bias
256 $MIN: -1000.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION: "bias correction" */
257 float scattering; /* spread the patch search zone without increasing number of patches
258 $MIN: 0.0 $MAX: 20.0 $DEFAULT: 0.0 $DESCRIPTION: "scattering" */
259 float central_pixel_weight; /* increase central pixel's weight in patch comparison
260 $MIN: 0.0 $MAX: 10.0 $DEFAULT: 0.1 $DESCRIPTION: "central pixel weight" */
261 float overshooting; /* adjusts the way parameters are autoset
262 $MIN: 0.001 $MAX: 1000.0 $DEFAULT: 1.0 $DESCRIPTION: "adjust autoset parameters" */
263 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
264 dt_iop_denoiseprofile_mode_t mode; /* switch between nlmeans and wavelets
265 $DEFAULT: MODE_NLMEANS */
267 float y[DT_DENOISE_PROFILE_NONE][DT_IOP_DENOISE_PROFILE_BANDS]; /* values to change wavelet force by frequency
268 $DEFAULT: 0.5 */
269 gboolean wb_adaptive_anscombe; // $DEFAULT: TRUE $DESCRIPTION: "whitebalance-adaptive transform" whether to adapt anscombe transform to wb coeffs
270 gboolean fix_anscombe_and_nlmeans_norm; // $DEFAULT: TRUE $DESCRIPTION: "fix various bugs in algorithm" backward compatibility options
271 gboolean use_new_vst; // $DEFAULT: TRUE $DESCRIPTION: "upgrade profiled transform" backward compatibility options
272 dt_iop_denoiseprofile_wavelet_mode_t wavelet_color_mode; /* switch between RGB and Y0U0V0 modes.
273 $DEFAULT: MODE_Y0U0V0 $DESCRIPTION: "color mode"*/
275
277{
278 float radius; /* patch size
279 $MIN: 0.0 $MAX: 12.0 $DEFAULT: 1.0 $DESCRIPTION: "patch size" */
280 float nbhood; /* search radius
281 $MIN: 1.0 $MAX: 30.0 $DEFAULT: 7.0 $DESCRIPTION: "search radius" */
282 float strength; /* noise level after equalization
283 $MIN: 0.001 $MAX: 1000.0 $DEFAULT: 1.0 */
284 float shadows; /* control the impact on shadows
285 $MIN: 0.0 $MAX: 1.8 $DEFAULT: 1.0 $DESCRIPTION: "preserve shadows" */
286 float bias; /* allows to reduce backtransform bias
287 $MIN: -1000.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION: "bias correction" */
288 float scattering; /* spread the patch search zone without increasing number of patches
289 $MIN: 0.0 $MAX: 20.0 $DEFAULT: 0.0 $DESCRIPTION: "scattering" */
290 float central_pixel_weight; /* increase central pixel's weight in patch comparison
291 $MIN: 0.0 $MAX: 10.0 $DEFAULT: 0.1 $DESCRIPTION: "central pixel weight" */
292 float overshooting; /* adjusts the way parameters are autoset
293 $MIN: 0.001 $MAX: 1000.0 $DEFAULT: 1.0 $DESCRIPTION: "adjust autoset parameters" */
294 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
295 dt_iop_denoiseprofile_mode_t mode; /* switch between nlmeans and wavelets
296 $DEFAULT: MODE_WAVELETS */
298 float y[DT_DENOISE_PROFILE_NONE][DT_IOP_DENOISE_PROFILE_BANDS]; /* values to change wavelet force by frequency
299 $DEFAULT: 0.5 */
300 gboolean wb_adaptive_anscombe; // $DEFAULT: TRUE $DESCRIPTION: "whitebalance-adaptive transform" whether to adapt anscombe transform to wb coeffs
301 gboolean fix_anscombe_and_nlmeans_norm; // $DEFAULT: TRUE $DESCRIPTION: "fix various bugs in algorithm" backward compatibility options
302 gboolean use_new_vst; // $DEFAULT: TRUE $DESCRIPTION: "upgrade profiled transform" backward compatibility options
303 dt_iop_denoiseprofile_wavelet_mode_t wavelet_color_mode; /* switch between RGB and Y0U0V0 modes.
304 $DEFAULT: MODE_Y0U0V0 $DESCRIPTION: "color mode"*/
306
308{
320 dt_noiseprofile_t interpolated; // don't use name, maker or model, they may point to garbage
321 GList *profiles;
325 dt_draw_curve_t *transition_curve; // curve for gui to draw
326 GtkDrawingArea *area;
327 GtkNotebook *channel_tabs;
339 GtkLabel *label_var;
341 GtkLabel *label_var_R;
343 GtkLabel *label_var_G;
345 GtkLabel *label_var_B;
346 // backward compatibility options
350
352{
353 float radius; // patch radius
354 float nbhood; // search radius
355 float strength; // noise level after equalization
356 float shadows; // controls noise reduction in shadows
357 float bias; // controls bias in backtransform
358 float scattering; // spread the search zone without changing number of patches
359 float central_pixel_weight; // increase central pixel's weight in patch comparison
360 float overshooting; // adjusts the way parameters are autoset
361 float a[3], b[3]; // fit for poissonian-gaussian noise per color channel.
362 dt_iop_denoiseprofile_mode_t mode; // switch between nlmeans and wavelets
366 gboolean wb_adaptive_anscombe; // whether to adapt anscombe transform to wb coeffs
367 gboolean fix_anscombe_and_nlmeans_norm; // backward compatibility options
368 gboolean use_new_vst; // backward compatibility options
369 dt_iop_denoiseprofile_wavelet_mode_t wavelet_color_mode; // switch between RGB and Y0U0V0 modes.
371
392
394
395#ifdef DEBUG_SCALES
397static void debug_dump_PFM(const dt_dev_pixelpipe_iop_t *const piece, const char *const namespec,
398 const float* const restrict buf, const int width, const int height, const int scale)
399{
400 if(!dt_dev_pixelpipe_has_preview_output(piece->module->dev, self->dev->preview_pipe, NULL))
401 {
402 char filename[512];
403 snprintf(filename, sizeof(filename), namespec, scale);
404 FILE *f = g_fopen(filename, "wb");
405 if (f)
406 {
407 fprintf(f, "PF\n%d %d\n-1.0\n", width, height);
408 const size_t n = (size_t)width * height;
409 for(size_t k=0; k<n; k++)
410 fwrite(buf+4U*k, sizeof(float), 3, f);
411 fclose(f);
412 }
413 }
414}
415#else
416#define debug_dump_PFM(p,n,b,w,h,s)
417#endif
418
419int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
420 void *new_params, const int new_version)
421{
422 if((old_version == 1 || old_version == 2 || old_version == 3) && new_version == 4)
423 {
424 const dt_iop_denoiseprofile_params_v1_t *o = old_params;
426 if(old_version == 1)
427 {
429 }
430 else
431 {
432 n->mode = o->mode;
433 }
434 n->radius = o->radius;
435 n->strength = o->strength;
436 memcpy(n->a, o->a, sizeof(float) * 3);
437 memcpy(n->b, o->b, sizeof(float) * 3);
438 // init curves coordinates
439 for(int b = 0; b < DT_IOP_DENOISE_PROFILE_V8_BANDS; b++)
440 {
441 for(int c = 0; c < DT_DENOISE_PROFILE_NONE_V9; c++)
442 {
443 n->x[c][b] = b / (DT_IOP_DENOISE_PROFILE_V8_BANDS - 1.0f);
444 n->y[c][b] = 0.5f;
445 }
446 }
447 // autodetect current profile:
448 if(IS_NULL_PTR(self->dev))
449 {
450 // we are probably handling a style or preset, do nothing for them, we can't do anything to detect if
451 // autodetection was used or not
452 return 0;
453 }
455 // if the profile in old_version is an autodetected one (this would mean a+b params match the interpolated
456 // one, AND
457 // the profile is actually the first selected one - however we can only detect the params, but most people
458 // did probably
459 // not set the exact ISO on purpose instead of the "found match" - they probably still want
460 // autodetection!)
461 if(!memcmp(interpolated.a, o->a, sizeof(float) * 3) && !memcmp(interpolated.b, o->b, sizeof(float) * 3))
462 {
463 // set the param a[0] to -1.0 to signal the autodetection
464 n->a[0] = -1.0f;
465 }
466 return 0;
467 }
468 else if(new_version == 5)
469 {
471 if(old_version < 4)
472 {
473 // first update to v4
474 if(legacy_params(self, old_params, old_version, &v4, 4))
475 return 1;
476 }
477 else
478 memcpy(&v4, old_params, sizeof(v4)); // was v4 already
479
480 dt_iop_denoiseprofile_params_v5_t *v5 = new_params;
481 v5->radius = v4.radius;
482 v5->strength = v4.strength;
483 v5->mode = v4.mode;
484 for(int k=0;k<3;k++)
485 {
486 v5->a[k] = v4.a[k];
487 v5->b[k] = v4.b[k];
488 }
489 for(int b = 0; b < DT_IOP_DENOISE_PROFILE_V8_BANDS; b++)
490 {
491 for(int c = 0; c < DT_DENOISE_PROFILE_NONE_V9; c++)
492 {
493 v5->x[c][b] = v4.x[c][b];
494 v5->y[c][b] = v4.y[c][b];
495 }
496 }
497 v5->nbhood = 7; // set to old hardcoded default
498 return 0;
499 }
500 else if(new_version == 6)
501 {
503 if(old_version < 5)
504 {
505 // first update to v5
506 if(legacy_params(self, old_params, old_version, &v5, 5)) return 1;
507 }
508 else
509 memcpy(&v5, old_params, sizeof(v5)); // was v5 already
510
511 dt_iop_denoiseprofile_params_v6_t *v6 = new_params;
512 v6->radius = v5.radius;
513 v6->strength = v5.strength;
514 v6->mode = v5.mode;
515 v6->nbhood = v5.nbhood;
516 for(int k = 0; k < 3; k++)
517 {
518 v6->a[k] = v5.a[k];
519 v6->b[k] = v5.b[k];
520 }
521 for(int b = 0; b < DT_IOP_DENOISE_PROFILE_V8_BANDS; b++)
522 {
523 for(int c = 0; c < DT_DENOISE_PROFILE_NONE_V9; c++)
524 {
525 v6->x[c][b] = v5.x[c][b];
526 v6->y[c][b] = v5.y[c][b];
527 }
528 }
529 v6->scattering = 0.0; // no scattering
530 return 0;
531 }
532 else if(new_version == 7)
533 {
535 if(old_version < 6)
536 {
537 // first update to v6
538 if(legacy_params(self, old_params, old_version, &v6, 6)) return 1;
539 }
540 else
541 memcpy(&v6, old_params, sizeof(v6)); // was v6 already
542 dt_iop_denoiseprofile_params_v7_t *v7 = new_params;
543 v7->radius = v6.radius;
544 v7->strength = v6.strength;
545 v7->mode = v6.mode;
546 v7->nbhood = v6.nbhood;
547 for(int k = 0; k < 3; k++)
548 {
549 v7->a[k] = v6.a[k];
550 v7->b[k] = v6.b[k];
551 }
552 for(int b = 0; b < DT_IOP_DENOISE_PROFILE_V8_BANDS; b++)
553 {
554 for(int c = 0; c < DT_DENOISE_PROFILE_NONE_V9; c++)
555 {
556 v7->x[c][b] = v6.x[c][b];
557 v7->y[c][b] = v6.y[c][b];
558 }
559 }
560 v7->scattering = v6.scattering;
561 v7->central_pixel_weight = 0.0;
562 v7->fix_anscombe_and_nlmeans_norm = FALSE; // don't fix anscombe and norm to ensure backward compatibility
564 return 0;
565 }
566 else if(new_version == 8)
567 {
569 if(old_version < 7)
570 {
571 // first update to v7
572 if(legacy_params(self, old_params, old_version, &v7, 7)) return 1;
573 }
574 else
575 memcpy(&v7, old_params, sizeof(v7)); // was v7 already
576 dt_iop_denoiseprofile_params_v8_t *v8 = new_params;
577 v8->radius = v7.radius;
578 v8->strength = v7.strength;
579 v8->mode = v7.mode;
580 v8->nbhood = v7.nbhood;
581 for(int k = 0; k < 3; k++)
582 {
583 v8->a[k] = v7.a[k];
584 v8->b[k] = v7.b[k];
585 }
586 for(int b = 0; b < DT_IOP_DENOISE_PROFILE_V8_BANDS; b++)
587 {
588 for(int c = 0; c < DT_DENOISE_PROFILE_NONE_V9; c++)
589 {
590 v8->x[c][b] = v7.x[c][b];
591 v8->y[c][b] = v7.y[c][b];
592 }
593 }
594 v8->scattering = v7.scattering;
598 v8->shadows = 1.0f;
599 v8->bias = 0.0f;
600 v8->use_new_vst = FALSE;
601 v8->overshooting = 1.0f;
602 return 0;
603 }
604 else if(new_version == 9)
605 {
607 if(old_version < 8)
608 {
609 // first update to v8
610 if(legacy_params(self, old_params, old_version, &v8, 8)) return 1;
611 }
612 else
613 memcpy(&v8, old_params, sizeof(v8)); // was v8 already
614 dt_iop_denoiseprofile_params_t *v9 = new_params;
615 v9->radius = v8.radius;
616 v9->strength = v8.strength;
617 v9->mode = v8.mode;
618 v9->nbhood = v8.nbhood;
619 for(int k = 0; k < 3; k++)
620 {
621 v9->a[k] = v8.a[k];
622 v9->b[k] = v8.b[k];
623 }
624 for(int b = 0; b < DT_IOP_DENOISE_PROFILE_BANDS; b++)
625 {
626 for(int c = 0; c < DT_DENOISE_PROFILE_NONE_V9; c++)
627 {
628 v9->x[c][b] = b / (DT_IOP_DENOISE_PROFILE_BANDS - 1.0f);
629 v9->y[c][b] = 0.0f;
630 }
631 }
632 for(int b = 0; b < DT_IOP_DENOISE_PROFILE_V8_BANDS; b++)
633 {
634 for(int c = 0; c < DT_DENOISE_PROFILE_NONE_V9; c++)
635 {
637 }
638 }
639 v9->scattering = v8.scattering;
643 v9->shadows = v8.shadows;
644 v9->bias = v8.bias;
645 v9->use_new_vst = v8.use_new_vst;
646 v9->overshooting = v8.overshooting;
647 return 0;
648 }
649 else if(new_version == 10)
650 {
652 if(old_version < 9)
653 {
654 // first update to v9
655 if(legacy_params(self, old_params, old_version, &v9, 9)) return 1;
656 }
657 else
658 memcpy(&v9, old_params, sizeof(v9)); // was v9 already
659 dt_iop_denoiseprofile_params_t *v10 = new_params;
660
661 // start with a clean default
663 *v10 = *d;
664
665 v10->radius = v9.radius;
666 v10->strength = v9.strength;
667 v10->mode = v9.mode;
668 v10->nbhood = v9.nbhood;
669 for(int k = 0; k < 3; k++)
670 {
671 v10->a[k] = v9.a[k];
672 v10->b[k] = v9.b[k];
673 }
674 for(int b = 0; b < DT_IOP_DENOISE_PROFILE_BANDS; b++)
675 {
676 for(int c = 0; c < DT_DENOISE_PROFILE_NONE_V9; c++)
677 {
678 v10->x[c][b] = v9.x[c][b];
679 v10->y[c][b] = v9.y[c][b];
680 }
682 {
683 v10->x[c][b] = b / (DT_IOP_DENOISE_PROFILE_BANDS - 1.0f);
684 v10->y[c][b] = 0.5f;
685 }
686 }
687 v10->scattering = v9.scattering;
691 v10->shadows = v9.shadows;
692 v10->bias = v9.bias;
693 v10->use_new_vst = v9.use_new_vst;
694 v10->overshooting = v9.overshooting;
696 return 0;
697 }
698 else if(new_version == 11)
699 {
700 // v11 and v10 are the same, just need to update strength when needed.
701 dt_iop_denoiseprofile_params_t *v11 = new_params;
702 if(old_version < 10)
703 {
704 if(legacy_params(self, old_params, old_version, v11, 10)) return 1;
705 }
706 else
707 memcpy(v11, old_params, sizeof(*v11)); // was v10 already
708
709 if((v11->mode == MODE_WAVELETS || v11->mode == MODE_WAVELETS_AUTO) && v11->wavelet_color_mode == MODE_Y0U0V0)
710 {
711 // in Y0U0V0, in v11, we always increase strength in the algorithm, so that
712 // the amount of smoothing is closer to what we get with the other modes.
713 const float compensate_strength = 2.5f;
714 v11->strength /= compensate_strength;
715 }
716 return 0;
717 }
718 return 1;
719}
720
722{
724 memset(&p, 0, sizeof(p));
725
726 // set some default values
727 p.radius = 1.0;
728 p.nbhood = 7.0;
729
730 // then the wavelet ones
731 p.mode = MODE_WAVELETS;
732 p.wavelet_color_mode = MODE_Y0U0V0;
733 p.strength = 1.2f;
734 p.use_new_vst = TRUE;
735 // disable variance stabilization transform to avoid any bias
736 // (wavelets perform well even without the VST):
737 p.shadows = 0.0f;
738 p.bias = 0.0f;
739 // this influences as well the way Y0U0V0 is computed:
740 p.wb_adaptive_anscombe = TRUE;
741 p.a[0] = -1.0f; // autodetect profile
742 p.central_pixel_weight = 0.1f;
743 p.overshooting = 1.0f;
744 p.fix_anscombe_and_nlmeans_norm = TRUE;
745 for(int b = 0; b < DT_IOP_DENOISE_PROFILE_BANDS; b++)
746 {
747 for(int c = 0; c < DT_DENOISE_PROFILE_NONE; c++)
748 {
749 p.x[c][b] = b / (DT_IOP_DENOISE_PROFILE_BANDS - 1.0f);
750 p.y[c][b] = 0.5f;
751 }
753 p.y[DT_DENOISE_PROFILE_Y0][b] = 0.0f;
754 }
755 dt_gui_presets_add_generic(_("wavelets: chroma only"), self->op, 11, &p,
756 sizeof(p), 1);
757}
758
759const char *name()
760{
761 return _("de_noise (profiled)");
762}
763
764const char **description(struct dt_iop_module_t *self)
765{
766 return dt_iop_set_description(self,
767 _("denoise using noise statistics profiled on sensors"),
768 _("corrective"),
769 _("linear, RGB, scene-referred"),
770 _("linear, RGB"),
771 _("linear, RGB, scene-referred"));
772}
773
775{
776 return IOP_GROUP_REPAIR;
777}
778
783
785{
786 return IOP_CS_RGB;
787}
788
789typedef union floatint_t
790{
791 float f;
792 uint32_t i;
794
795void 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)
796{
797 const dt_iop_roi_t *const roi_in = &piece->roi_in;
799
800 if(d->mode == MODE_NLMEANS || d->mode == MODE_NLMEANS_AUTO)
801 {
802 const int P = ceilf(d->radius * fminf(fminf(roi_in->scale, 2.0f), 1.0f)); // pixel filter size
803 const int K = ceilf(d->nbhood * fminf(fminf(roi_in->scale, 2.0f), 1.0f)); // nbhood
804 const int K_scattered = ceilf(d->scattering * (K * K * K + 7.0 * K * sqrt(K)) / 6.0) + K;
805
806 tiling->factor = 2.0f + 0.25f; // in + out + tmp
807 tiling->factor_cl = 4.0f + 0.25f * NUM_BUCKETS; // in + out + (2 + NUM_BUCKETS * 0.25) tmp
808 tiling->maxbuf = 1.0f;
809 tiling->overhead = 0;
810 tiling->overlap = P + K_scattered;
811 tiling->xalign = 1;
812 tiling->yalign = 1;
813 }
814 else
815 {
816 const int max_max_scale = DT_IOP_DENOISE_PROFILE_BANDS; // hard limit
817 int max_scale = 0;
818 const float scale = fminf(roi_in->scale, 1.0f);
819 // largest desired filter on input buffer (20% of input dim)
820 const float supp0
821 = fminf(2 * (2u << (max_max_scale - 1)) + 1,
822 fmaxf(piece->buf_in.height, piece->buf_in.width) * 0.2f);
823 const float i0 = dt_log2f((supp0 - 1.0f) * .5f);
824 for(; max_scale < max_max_scale; max_scale++)
825 {
826 // actual filter support on scaled buffer
827 const float supp = 2 * (2u << max_scale) + 1;
828 // approximates this filter size on unscaled input image:
829 const float supp_in = supp * (1.0f / scale);
830 const float i_in = dt_log2f((supp_in - 1) * .5f) - 1.0f;
831 // i_in = max_scale .. .. .. 0
832 const float t = 1.0f - (i_in + .5f) / i0;
833 if(t < 0.0f) break;
834 }
835
836 const int max_filter_radius = (1u << max_scale); // 2 * 2^max_scale
837
838 tiling->factor = 5.0f; // in + out + precond + tmp + reducebuffer
839 tiling->factor_cl = 3.5f + max_scale; // in + out + tmp + reducebuffer + scale buffers
840 tiling->maxbuf = 1.0f;
841 tiling->maxbuf_cl = 1.0f;
842 tiling->overhead = 0;
843 tiling->overlap = max_filter_radius;
844 tiling->xalign = 1;
845 tiling->yalign = 1;
846 }
847
848}
849
851static inline void precondition(const float *const in, float *const buf, const int wd, const int ht,
852 const dt_aligned_pixel_t a, const dt_aligned_pixel_t b)
853{
854 const dt_aligned_pixel_t sigma2_plus_3_8
855 = { (b[0] / a[0]) * (b[0] / a[0]) + 3.f / 8.f,
856 (b[1] / a[1]) * (b[1] / a[1]) + 3.f / 8.f,
857 (b[2] / a[2]) * (b[2] / a[2]) + 3.f / 8.f,
858 0.0f };
859 const size_t npixels = (size_t)wd * ht;
861 for(size_t j = 0; j < 4U * npixels; j += 4)
862 {
863 for_each_channel(c,aligned(in,buf,a,sigma2_plus_3_8))
864 {
865 const float d = fmaxf(0.0f, in[j+c] / a[c] + sigma2_plus_3_8[c]);
866 buf[j+c] = 2.0f * sqrtf(d);
867 }
868 }
869}
870
872static inline void backtransform(float *const buf, const int wd, const int ht, const dt_aligned_pixel_t a,
873 const dt_aligned_pixel_t b)
874{
875 const dt_aligned_pixel_t sigma2_plus_1_8
876 = { (b[0] / a[0]) * (b[0] / a[0]) + 1.f / 8.f,
877 (b[1] / a[1]) * (b[1] / a[1]) + 1.f / 8.f,
878 (b[2] / a[2]) * (b[2] / a[2]) + 1.f / 8.f,
879 0.0f };
880 const size_t npixels = (size_t)wd * ht;
881 const float sqrt_3_2 = sqrtf(3.0f / 2.0f);
883 for(size_t j = 0; j < 4U * npixels; j += 4)
884 {
885 for_each_channel(c,aligned(buf,sigma2_plus_1_8))
886 {
887 const float x = buf[j+c], x2 = x * x;
888 // closed form approximation to unbiased inverse (input range was 0..200 for fit, not 0..1)
889 buf[j+c] = (x < 0.5f)
890 ? 0.0f
891 : a[c] * (1.f / 4.f * x2 + 1.f / 4.f * sqrt_3_2 / x - 11.f / 8.f / x2
892 + 5.f / 8.f * sqrt_3_2 / (x * x2) - sigma2_plus_1_8[c]);
893 // asymptotic form:
894 // buf[j+c] = fmaxf(0.0f, 1./4.*x*x - 1./8. - sigma2[c]);
895 // buf[j+c] *= a[c];
896 }
897 }
898}
899
900// the "v2" variance stabilizing transform is an extension of the generalized
901// anscombe transform.
902// In the generalized anscombe transform, the profiles gives a and b such as:
903// V(X) = a * E[X] + b
904// In this new transform, we have an additional parameter, p, such as:
905// V(X) = a * (E[X] + b) ^ p
906// When p == 1, we get back the equation of generalized anscombe transform.
907// Now, let's see how we derive the precondition.
908// The goal of a VST f is to make variance constant: V(f(X)) = constant
909// Using a Taylor expansion, we have:
910// V(f(X)) ~= V(f(E[X])+f'(X)(X-E[X]))
911// = V(f'(X)(X-E[X]))
912// = f'(X)^2 * V(X-E[X])
913// = f'(X)^2 * V(X)
914// So the condition V(f(X)) = constant gives us the following condition:
915// V(X) = constant / f'(X)^2
916// Usually, we take constant = 1
917// If we have V(X) = a * (E[X] + b) ^ p
918// then: f'(X) = 1 / sqrt(a) * (E[X] + b) ^ (-p / 2)
919// then: f(x) = 1 / (sqrt(a) * (1 - p / 2)) * (x + b) ^ (1 - p / 2)
920// = 2 * (x + b) ^ (1 - p / 2) / (sqrt(a) * (2 - p))
921// is a suitable function.
922// This is the function we use here.
924static inline void precondition_v2(const float *const in, float *const buf, const int wd, const int ht,
925 const float a, const dt_aligned_pixel_t p, const float b,
926 const dt_aligned_pixel_t wb)
927{
928 const size_t npixels = (size_t)wd * ht;
929 const dt_aligned_pixel_t expon = { -p[0] / 2 + 1, -p[1] / 2 + 1, -p[2] / 2 + 1, 1.0f };
930 const dt_aligned_pixel_t denom = { (-p[0] + 2) * sqrtf(a), (-p[1] + 2) * sqrtf(a),
931 (-p[2] + 2) * sqrtf(a), 1.0f };
933 for(size_t j = 0; j < 4U * npixels; j += 4)
934 {
935 for_each_channel(c,aligned(in,buf,wb))
936 {
937 buf[j+c] = 2.0f * powf(MAX(in[j+c] / wb[c] + b, 0.0f), expon[c]) / denom[c];
938 }
939 }
940}
941
942// this backtransform aims at being a low bias backtransform
943// you can see that it is not equal to f-1
944// this is because E[X] != f-1(E[f(X)])
945// so let's try to find a better backtransform than f-1:
946// we want to find E[X] knowing E[f(X)]
947// let's apply Taylor expansion to E[f(X)] to see if we can get something better:
948// E[f(X)] ~= E[f(E[X]) + f'(E[X])(X-E[X])]
949// = E[f(E[X]) + f'(E[X]) * X - f'(E[X]) * E[X]]
950// = f(E[X]) + f'(E[X]) * E[X] - f'(E[X]) * E[X]
951// = f(E[X])
952// so first order Taylor expansion is not useful.
953// going to the second order:
954// E[f(X)] ~= E[f(E[X]) + f'(E[X])(X-E[X]) + f"(E[X])/2 * (X-E[X])^2]
955// = f(E[X]) + f"(E[X])/2 * E[(X-E[X])^2]
956// = f(E[X]) + f"(E[X])/2 * V(X)
957// and we know that V(X) = constant / f'(X)^2
958// the constant here is not 1, due to problems in noise profiling tool
959// so in fact constant depends on the image (and is approximately in [10;15])
960// so:
961// E[f(X)] ~= f(E[X]) + f"(E[X])/2 * constant / f'(E[X])^2
962// we have:
963// f(x) = 2 * (x + b) ^ (1 - p / 2) / (sqrt(a) * (2 - p))
964// f'(x) = 1 / sqrt(a) * (x + b) ^ (-p / 2)
965// 1/f'(x)^2 = a * (x + b) ^ p
966// f"(x) = 1 / sqrt(a) * (-p / 2) * (x + b) ^ (- p / 2 - 1)
967// let's replace f, f', and f" by their analytical expressions in our equation:
968// let x = E[X]
969// E[f(X)] ~= 2 * (x + b) ^ (1 - p / 2) / (sqrt(a) * (2 - p))
970// + constant / 2 * (1 / sqrt(a) * (-p / 2) * (x + b) ^ (- p / 2 - 1)) * (a * (x + b) ^ p)
971// = 2 * (x + b) ^ (1 - p / 2) / (sqrt(a) * (2 - p))
972// + constant / 2 * 1 / sqrt(a) * (-p / 2) * a * (x + b) ^ (p / 2 - 1)
973// = 2 * (x + b) ^ (1 - p / 2) / (sqrt(a) * (2 - p))
974// - constant / 4 * sqrt(a) * p * (x + b) ^ (p / 2 - 1)
975// let z = (x + b) ^ (1 - p / 2)
976// E[f(X)] ~= 2 / (sqrt(a) * (2 - p)) * z
977// - constant / 4 * sqrt(a) * p * z^(-1)
978// let y = E[f(X)]
979// y ~= 2 / (sqrt(a) * (2 - p)) * z - constant / 4 * sqrt(a) * p * z^(-1)
980// y * z = 2 / (sqrt(a) * (2 - p)) * z^2 - constant / 4 * sqrt(a) * p
981// 0 = 2 / (sqrt(a) * (2 - p)) * z^2 - y * z - constant / 4 * sqrt(a) * p
982// let's solve this equation:
983// delta = y ^ 2 - 4 * 2 / (sqrt(a) * (2 - p)) * (- constant / 4 * sqrt(a))
984// = y ^ 2 + 2 * p * constant / (2 - p)
985// delta >= 0
986// the 2 solutions are:
987// z0 = (y - sqrt(delta)) / (2 * 2 / (sqrt(a) * (2 - p)))
988// z1 = (y + sqrt(delta)) / (2 * 2 / (sqrt(a) * (2 - p)))
989// as delta > y^2, sqrt(delta) > y, so z0 is negative
990// so z1 is the only possible solution.
991// Then, to find E[X], we only have to do:
992// z = (x + b) ^ (1 - p / 2) <=> x = z ^ (1 / (1 - p / 2)) - b
993//
994// What we see here is that a bias compensation term is in delta:
995// the term: 2 * p * constant / (2 - p)
996// But we are not sure at all what the value of the constant is
997// That's why we introduce a user-controled bias parameter to be able to
998// control the bias:
999// we replace the 2 * p * constant / (2 - p) part of delta by user
1000// defined bias controller.
1002static inline void backtransform_v2(float *const buf, const int wd, const int ht, const float a,
1003 const dt_aligned_pixel_t p, const float b, const float bias,
1004 const dt_aligned_pixel_t wb)
1005{
1006 const size_t npixels = (size_t)wd * ht;
1007 const dt_aligned_pixel_t expon = { 1.0f / (1.0f - p[0] / 2.0f), 1.0f / (1.0f - p[1] / 2.0f),
1008 1.0f / (1.0f - p[2] / 2.0f), 1.0f };
1009 const dt_aligned_pixel_t denom = { 4.0f / (sqrtf(a) * (2.0f - p[0])), 4.0f / (sqrtf(a) * (2.0f - p[1])),
1010 4.0f / (sqrtf(a) * (2.0f - p[2])), 1.0f };
1012 for(size_t j = 0; j < 4U * npixels; j += 4)
1013 {
1014 for_each_channel(c,aligned(buf,wb))
1015 {
1016 const float x = MAX(buf[j+c], 0.0f);
1017 const float delta = x * x + bias;
1018 const float z1 = (x + sqrtf(MAX(delta, 0.0f))) / denom[c];
1019 buf[j+c] = wb[c] * (powf(z1, expon[c]) - b);
1020 }
1021 }
1022}
1023
1025static inline void precondition_Y0U0V0(const float *const in, float *const buf, const int wd, const int ht,
1026 const float a, const dt_aligned_pixel_t p, const float b,
1027 const dt_colormatrix_t toY0U0V0)
1028{
1029 const dt_aligned_pixel_t expon = { -p[0] / 2 + 1, -p[1] / 2 + 1, -p[2] / 2 + 1, 1.0f };
1030 const dt_aligned_pixel_t scale = { 2.0f / ((-p[0] + 2) * sqrtf(a)),
1031 2.0f / ((-p[1] + 2) * sqrtf(a)),
1032 2.0f / ((-p[2] + 2) * sqrtf(a)),
1033 1.0f };
1035 for(size_t j = 0; j < (size_t)4 * ht * wd; j += 4)
1036 {
1037 dt_aligned_pixel_t tmp; // "unused" fourth element enables vectorization
1038 for_each_channel(c,aligned(in))
1039 {
1040 tmp[c] = powf(MAX(in[j+c] + b, 0.0f), expon[c]) * scale[c];
1041 }
1042 for(int c = 0; c < 3; c++)
1043 {
1044 float sum = 0.0f;
1045 for_each_channel(k,aligned(toY0U0V0))
1046 {
1047 sum += toY0U0V0[c][k] * tmp[k];
1048 }
1049 buf[j+c] = sum;
1050 }
1051 buf[j+3] = 0;
1052 }
1053}
1054
1056static inline void backtransform_Y0U0V0(float *const buf, const int wd, const int ht, const float a,
1057 const dt_aligned_pixel_t p, const float b, const float bias,
1058 const dt_aligned_pixel_t wb, const dt_colormatrix_t toRGB)
1059{
1060 const dt_aligned_pixel_t bias_wb = { bias * wb[0], bias * wb[1], bias * wb[2], 0.0f };
1061 const dt_aligned_pixel_t expon = { 1.0f / (1.0f - p[0] / 2.0f),
1062 1.0f / (1.0f - p[1] / 2.0f),
1063 1.0f / (1.0f - p[2] / 2.0f),
1064 1.0f };
1065 const dt_aligned_pixel_t scale = { (sqrtf(a) * (2.0f - p[0])) / 4.0f,
1066 (sqrtf(a) * (2.0f - p[1])) / 4.0f,
1067 (sqrtf(a) * (2.0f - p[2])) / 4.0f,
1068 1.0f };
1070 for(size_t j = 0; j < (size_t)4 * ht * wd; j += 4)
1071 {
1072 dt_aligned_pixel_t rgb = { 0.0f }; // "unused" fourth element enables vectorization
1073 for(int k = 0; k < 3; k++)
1074 {
1075 for_each_channel(c,aligned(toRGB,buf))
1076 {
1077 rgb[k] += toRGB[k][c] * buf[j+c];
1078 }
1079 }
1080 for_each_channel(c,aligned(buf))
1081 {
1082 const float x = MAX(rgb[c], 0.0f);
1083 const float delta = x * x + bias_wb[c];
1084 const float z1 = (x + sqrtf(MAX(delta, 0.0f))) * scale[c];
1085 buf[j+c] = powf(z1, expon[c]) - b;
1086 }
1087 }
1088}
1089
1090// =====================================================================================
1091// begin common functions
1092// =====================================================================================
1093
1094// called by: process_wavelets, nlmeans_precondition, nlmeans_precondition_cl, process_variance,
1095// process_wavelets_cl
1098 const dt_dev_pixelpipe_iop_t *const piece, const dt_aligned_pixel_t weights)
1099{
1100 const float wb_mean = (piece->dsc_in.temperature.coeffs[0] + piece->dsc_in.temperature.coeffs[1]
1101 + piece->dsc_in.temperature.coeffs[2])
1102 / 3.0f;
1103 // we init wb by the mean of the coeffs, which corresponds to the mean
1104 // amplification that is done in addition to the "ISO" related amplification
1105 wb[0] = wb[1] = wb[2] = wb[3] = wb_mean;
1106 if(d->fix_anscombe_and_nlmeans_norm)
1107 {
1108 if(wb_mean != 0.0f && d->wb_adaptive_anscombe)
1109 {
1110 for(int i = 0; i < 3; i++) wb[i] = piece->dsc_in.temperature.coeffs[i];
1111 }
1112 else if(wb_mean == 0.0f)
1113 {
1114 // temperature coeffs are equal to 0 if we open a JPG image.
1115 // in this case consider them equal to 1.
1117 wb[i] = 1.0f;
1118 }
1119 // else, wb_adaptive_anscombe is false and our wb array is
1120 // filled with the wb_mean
1121 }
1122 else
1123 {
1125 wb[i] = weights[i] * piece->dsc_in.processed_maximum[i];
1126 }
1127 return;
1128}
1129
1130// =====================================================================================
1131
1132static inline __attribute__((always_inline)) gboolean invert_matrix(const dt_colormatrix_t in, dt_colormatrix_t out)
1133{
1134 // use same notation as https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices
1135 const float biga = in[1][1] * in[2][2] - in[1][2] * in[2][1];
1136 const float bigb = -in[1][0] * in[2][2] + in[1][2] * in[2][0];
1137 const float bigc = in[1][0] * in[2][1] - in[1][1] * in[2][0];
1138 const float bigd = -in[0][1] * in[2][2] + in[0][2] * in[2][1];
1139 const float bige = in[0][0] * in[2][2] - in[0][2] * in[2][0];
1140 const float bigf = -in[0][0] * in[2][1] + in[0][1] * in[2][0];
1141 const float bigg = in[0][1] * in[1][2] - in[0][2] * in[1][1];
1142 const float bigh = -in[0][0] * in[1][2] + in[0][2] * in[1][0];
1143 const float bigi = in[0][0] * in[1][1] - in[0][1] * in[1][0];
1144
1145 const float det = in[0][0] * biga + in[0][1] * bigb + in[0][2] * bigc;
1146 if(det == 0.0f)
1147 {
1148 return FALSE;
1149 }
1150
1151 out[0][0] = 1.0f / det * biga;
1152 out[0][1] = 1.0f / det * bigd;
1153 out[0][2] = 1.0f / det * bigg;
1154 out[0][3] = 0.0f;
1155 out[1][0] = 1.0f / det * bigb;
1156 out[1][1] = 1.0f / det * bige;
1157 out[1][2] = 1.0f / det * bigh;
1158 out[1][3] = 0.0f;
1159 out[2][0] = 1.0f / det * bigc;
1160 out[2][1] = 1.0f / det * bigf;
1161 out[2][2] = 1.0f / det * bigi;
1162 out[2][3] = 0.0f;
1163 return TRUE;
1164}
1165
1166// create the white balance adaptative conversion matrices
1167// supposes toY0U0V0 already contains the "normal" conversion matrix
1170 const dt_aligned_pixel_t wb)
1171{
1172 // for an explanation of the spirit of the choice of the coefficients of the
1173 // Y0U0V0 conversion matrix, see part 12.3.3 page 190 of
1174 // "From Theory to Practice, a Tour of Image Denoising"
1175 // https://hal.archives-ouvertes.fr/tel-01114299
1176 // we adapt a bit the coefficients, in a way that follows the same spirit.
1177
1178 float sum_invwb = 1.0f/wb[0] + 1.0f/wb[1] + 1.0f/wb[2];
1179 // we change the coefs to Y0, but keeping the goal of making SNR higher:
1180 // these were all equal to 1/3 to get the Y0 the least noisy possible, assuming
1181 // that all channels have equal noise variance.
1182 // as white balance influences noise variance, we do a weighted mean depending
1183 // on white balance. Note that it is equivalent to keeping the 1/3 coefficients
1184 // if we divide by the white balance coefficients beforehand.
1185 // we then normalize the line so that variance becomes equal to 1:
1186 // var(Y0) = 1/9 * (var(R) + var(G) + var(B)) = 1/3
1187 // var(sqrt(3)Y0) = 1
1188 sum_invwb *= sqrtf(3);
1189 toY0U0V0[0][0] = sum_invwb / wb[0];
1190 toY0U0V0[0][1] = sum_invwb / wb[1];
1191 toY0U0V0[0][2] = sum_invwb / wb[2];
1192 toY0U0V0[0][3] = 0.0f;
1193 // we also normalize the other line in a way that should give a variance of 1
1194 // if var(B/wb[B]) == 1, then var(B) = wb[B]^2
1195 // note that we don't change the coefs of U0 and V0 depending on white balance,
1196 // apart of the normalization: these coefficients do differences of RGB channels
1197 // to try to reduce or cancel the signal. If we change these depending on white
1198 // balance, we will not reduce/cancel the signal anymore.
1199 const float stddevU0 = sqrtf(0.5f * 0.5f * wb[0] * wb[0] + 0.5f * 0.5f * wb[2] * wb[2]);
1200 const float stddevV0 = sqrtf(0.25f * 0.25f * wb[0] * wb[0] + 0.5f * 0.5f * wb[1] * wb[1] + 0.25f * 0.25f * wb[2] * wb[2]);
1201 toY0U0V0[1][0] /= stddevU0;
1202 toY0U0V0[1][1] /= stddevU0;
1203 toY0U0V0[1][2] /= stddevU0;
1204 toY0U0V0[1][3] = 0.0f;
1205 toY0U0V0[2][0] /= stddevV0;
1206 toY0U0V0[2][1] /= stddevV0;
1207 toY0U0V0[2][2] /= stddevV0;
1208 toY0U0V0[2][3] = 0.0f;
1209 const gboolean is_invertible = invert_matrix(toY0U0V0, toRGB);
1210 if(!is_invertible)
1211 {
1212 // use standard form if whitebalance adapted matrix is not invertible
1213 float stddevY0 = sqrtf(1.0f / 9.0f * (wb[0] * wb[0] + wb[1] * wb[1] + wb[2] * wb[2]));
1214 toY0U0V0[0][0] = 1.0f / (3.0f * stddevY0);
1215 toY0U0V0[0][1] = 1.0f / (3.0f * stddevY0);
1216 toY0U0V0[0][2] = 1.0f / (3.0f * stddevY0);
1217 toY0U0V0[0][3] = 0.0f;
1218 invert_matrix(toY0U0V0, toRGB);
1219 }
1220}
1221
1222static inline __attribute__((always_inline)) void variance_stabilizing_xform(dt_aligned_pixel_t thrs, const int scale, const int max_scale, const size_t npixels,
1223 const float *const sum_y2, const dt_iop_denoiseprofile_data_t *const d)
1224{
1225 // variance stabilizing transform maps sigma to unity.
1226 const float sigma = 1.0f;
1227 // it is then transformed by wavelet scales via the 5 tap a-trous filter:
1228 const float varf = sqrtf(2.0f + 2.0f * 4.0f * 4.0f + 6.0f * 6.0f) / 16.0f; // about 0.5
1229 const float sigma_band = powf(varf, scale) * sigma;
1230 // determine thrs as bayesshrink
1231 const float sb2 = sigma_band * sigma_band;
1232 const dt_aligned_pixel_t var_y = { sum_y2[0] / (npixels - 1.0f), sum_y2[1] / (npixels - 1.0f),
1233 sum_y2[2] / (npixels - 1.0f), 0.0f };
1234 const dt_aligned_pixel_t std_x = { sqrtf(MAX(1e-6f, var_y[0] - sb2)), sqrtf(MAX(1e-6f, var_y[1] - sb2)),
1235 sqrtf(MAX(1e-6f, var_y[2] - sb2)), 1.0f };
1236 // add 8.0 here because it seemed a little weak
1237 dt_aligned_pixel_t adjt = { 8.0f, 8.0f, 8.0f, 0.0f };
1238
1239 const int offset_scale = DT_IOP_DENOISE_PROFILE_BANDS - max_scale;
1240 const int band_index = DT_IOP_DENOISE_PROFILE_BANDS - (scale + offset_scale + 1);
1241
1242 if(d->wavelet_color_mode == MODE_RGB)
1243 {
1244 // current scale number is scale+offset_scale
1245 // for instance, largest scale is DT_IOP_DENOISE_PROFILE_BANDS
1246 // max_scale only indicates the number of scales to process at THIS
1247 // zoom level, it does NOT corresponds to the the maximum number of scales.
1248 // in other words, max_scale is the maximum number of VISIBLE scales.
1249 // That is why we have this "scale+offset_scale"
1250 float band_force_exp_2
1251 = d->force[DT_DENOISE_PROFILE_ALL][band_index];
1252 band_force_exp_2 *= band_force_exp_2;
1253 band_force_exp_2 *= 4;
1255 {
1256 adjt[ch] *= band_force_exp_2;
1257 }
1258 band_force_exp_2 = d->force[DT_DENOISE_PROFILE_R][band_index];
1259 band_force_exp_2 *= band_force_exp_2;
1260 band_force_exp_2 *= 4;
1261 adjt[0] *= band_force_exp_2;
1262 band_force_exp_2 = d->force[DT_DENOISE_PROFILE_G][band_index];
1263 band_force_exp_2 *= band_force_exp_2;
1264 band_force_exp_2 *= 4;
1265 adjt[1] *= band_force_exp_2;
1266 band_force_exp_2 = d->force[DT_DENOISE_PROFILE_B][band_index];
1267 band_force_exp_2 *= band_force_exp_2;
1268 band_force_exp_2 *= 4;
1269 adjt[2] *= band_force_exp_2;
1270 }
1271 else
1272 {
1273 float band_force_exp_2 = d->force[DT_DENOISE_PROFILE_Y0][band_index];
1274 band_force_exp_2 *= band_force_exp_2;
1275 band_force_exp_2 *= 4;
1276 adjt[0] *= band_force_exp_2;
1277 band_force_exp_2 = d->force[DT_DENOISE_PROFILE_U0V0][band_index];
1278 band_force_exp_2 *= band_force_exp_2;
1279 band_force_exp_2 *= 4;
1280 adjt[1] *= band_force_exp_2;
1281 adjt[2] *= band_force_exp_2;
1282 }
1284 thrs[c] = adjt[c] * sb2 / std_x[c];
1285}
1286
1288static int process_wavelets(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
1289 const dt_dev_pixelpipe_iop_t *piece,
1290 const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in,
1291 const dt_iop_roi_t *const roi_out, const eaw_dn_decompose_t decompose,
1292 const eaw_synthesize_t synthesize)
1293{
1294 // this is called for preview and full pipe separately, each with its own pixelpipe piece.
1295 // get our data struct:
1297
1298#define MAX_MAX_SCALE DT_IOP_DENOISE_PROFILE_BANDS // hard limit
1299
1300 int max_scale = 0;
1301 const float in_scale = fminf(roi_in->scale, 1.0f);
1302 // largest desired filter on input buffer (20% of input dim)
1303 const float supp0 = MIN(2 * (2u << (MAX_MAX_SCALE - 1)) + 1,
1304 MAX(piece->buf_in.height, piece->buf_in.width) * 0.2f);
1305 const float i0 = dt_log2f((supp0 - 1.0f) * .5f);
1306 for(; max_scale < MAX_MAX_SCALE; max_scale++)
1307 {
1308 // actual filter support on scaled buffer
1309 const float supp = 2 * (2u << max_scale) + 1;
1310 // approximates this filter size on unscaled input image:
1311 const float supp_in = supp * (1.0f / in_scale);
1312 const float i_in = dt_log2f((supp_in - 1) * .5f) - 1.0f;
1313 // i_in = max_scale .. .. .. 0
1314 const float t = 1.0f - (i_in + .5f) / i0;
1315 if(t < 0.0f) break;
1316 }
1317
1318 const int max_mult = 1u << (max_scale - 1);
1319 const int width = roi_in->width, height = roi_in->height;
1320 const size_t npixels = (size_t)width*height;
1321 const float *const restrict in = (const float*)ivoid;
1322 float *const restrict out = (float*)ovoid;
1323
1324 // corner case of extremely small image. this is not really likely to happen but would
1325 // lead to out of bounds memory access
1326 if(width < 2 * max_mult || height < 2 * max_mult)
1327 {
1328 memcpy(out, in, sizeof(float) * 4 * npixels);
1329 return 0;
1330 }
1331
1332 float *buf = NULL;
1333 float *restrict precond = NULL;
1334 float *restrict tmp = NULL;
1335
1336 if (dt_iop_alloc_image_buffers(self, roi_in, roi_out, 4, &precond, 4, &tmp, 4, &buf, 0))
1337 {
1338 dt_iop_copy_image_roi(out, in, piece->dsc_in.channels, roi_in, roi_out, TRUE);
1339 return 1;
1340 }
1341
1342 dt_aligned_pixel_t wb; // the "unused" fourth element enables vectorization
1343 const dt_aligned_pixel_t wb_weights = { 2.0f, 1.0f, 2.0f, 0.0f };
1344 compute_wb_factors(wb,d,piece,wb_weights);
1345
1346 // adaptive p depending on white balance (the "unused" fourth element enables vectorization
1347 const dt_aligned_pixel_t p = { MAX(d->shadows + 0.1 * logf(in_scale / wb[0]), 0.0f),
1348 MAX(d->shadows + 0.1 * logf(in_scale / wb[1]), 0.0f),
1349 MAX(d->shadows + 0.1 * logf(in_scale / wb[2]), 0.0f),
1350 0.0f };
1351
1352 const float compensate_p = DT_IOP_DENOISE_PROFILE_P_FULCRUM / powf(DT_IOP_DENOISE_PROFILE_P_FULCRUM, d->shadows);
1353
1354 // conversion to Y0U0V0 space as defined in Secrets of image denoising cuisine
1355 dt_colormatrix_t toY0U0V0 = { { 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f },
1356 { 0.5f, 0.0f, -0.5f },
1357 { 0.25f, -0.5f, 0.25f } };
1358 dt_colormatrix_t toRGB = { { 0.0f, 0.0f, 0.0f }, // "unused" fourth element enables vectorization
1359 { 0.0f, 0.0f, 0.0f },
1360 { 0.0f, 0.0f, 0.0f } };
1361 set_up_conversion_matrices(toY0U0V0, toRGB, wb);
1362
1363 // more strength in Y0U0V0 in order to get a similar smoothing as in other modes
1364 // otherwise, result was much less denoised in Y0U0V0 mode.
1365 const float compensate_strength = (d->wavelet_color_mode == MODE_RGB) ? 1.0f : 2.5f;
1366 // update the coeffs with strength and scale
1367 for(size_t k = 0; k < 3; k++)
1369 {
1370 toY0U0V0[k][c] /= (d->strength * compensate_strength * in_scale);
1371 toRGB[k][c] *= (d->strength * compensate_strength * in_scale);
1372 }
1373 for_each_channel(i) wb[i] *= d->strength * compensate_strength * in_scale;
1374
1375 // only use green channel + wb for now: (the "unused" fourth element enables vectorization)
1376 const dt_aligned_pixel_t aa = { d->a[1] * wb[0], d->a[1] * wb[1], d->a[1] * wb[2], 0.0f };
1377 const dt_aligned_pixel_t bb = { d->b[1] * wb[0], d->b[1] * wb[1], d->b[1] * wb[2], 0.0f };
1378
1379 if(!d->use_new_vst)
1380 {
1381 precondition(in, precond, width, height, aa, bb);
1382 }
1383 else if(d->wavelet_color_mode == MODE_RGB)
1384 {
1385 precondition_v2(in, precond, width, height, d->a[1] * compensate_p, p, d->b[1], wb);
1386 }
1387 else
1388 {
1389 precondition_Y0U0V0(in, precond, width, height, d->a[1] * compensate_p, p, d->b[1], toY0U0V0);
1390 }
1391
1392 debug_dump_PFM(piece,"/tmp/transformed.pfm",precond,width,height,0);
1393
1394 float *restrict buf1 = precond;
1395 float *restrict buf2 = tmp;
1396
1397 // clear the output buffer, which will be accumulating all of the detail scales
1398 memset(out, 0, sizeof(float) * 4 * npixels);
1399
1400 for(int scale = 0; scale < max_scale; scale++)
1401 {
1402 const float sigma = 1.0f;
1403 const float varf = sqrtf(2.0f + 2.0f * 4.0f * 4.0f + 6.0f * 6.0f) / 16.0f; // about 0.5
1404 const float sigma_band = powf(varf, scale) * sigma;
1405 dt_aligned_pixel_t sum_y2;
1406 decompose(buf2, buf1, buf, sum_y2, scale, 1.0f / (sigma_band * sigma_band), width, height);
1407 debug_dump_PFM(piece,"/tmp/coarse_%d.pfm",buf2,width,height,scale);
1408 debug_dump_PFM(piece,"/tmp/detail_%d.pfm",buf,width,height,scale);
1409
1410 const dt_aligned_pixel_t boost = { 1.0f, 1.0f, 1.0f, 1.0f };
1412 variance_stabilizing_xform(thrs, scale, max_scale, npixels, sum_y2, d);
1413 synthesize(out, out, buf, thrs, boost, width, height);
1414
1415 float *buf3 = buf2;
1416 buf2 = buf1;
1417 buf1 = buf3;
1418 }
1419
1420 // add in the final residue
1421 __OMP_SIMD__(aligned(buf1, out : 64))
1422 for (size_t k = 0; k < 4U * npixels; k++)
1423 out[k] += buf1[k];
1424
1425 if(!d->use_new_vst)
1426 {
1427 backtransform(out, width, height, aa, bb);
1428 }
1429 else if(d->wavelet_color_mode == MODE_RGB)
1430 {
1431 backtransform_v2(out, width, height, d->a[1] * compensate_p, p, d->b[1], d->bias - 0.5 * logf(in_scale), wb);
1432 }
1433 else
1434 {
1435 backtransform_Y0U0V0(out, width, height, d->a[1] * compensate_p, p, d->b[1], d->bias - 0.5 * logf(in_scale), wb, toRGB);
1436 }
1437
1441
1442 if(pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, width, height);
1443
1444#undef MAX_MAX_SCALE
1445 return 0;
1446}
1447
1448#if defined(HAVE_OPENCL) && !USE_NEW_IMPL_CL
1449static int sign(int a)
1450{
1451 return (a > 0) - (a < 0);
1452}
1453#endif
1454
1455// called by: process_nlmeans_cpu, process_nlmeans_cl
1456static inline __attribute__((always_inline)) float nlmeans_norm(const int P, const dt_iop_denoiseprofile_data_t *const d)
1457{
1458 // Each patch has a width of 2P+1 and a height of 2P+1
1459 // thus, divide by (2P+1)^2.
1460 // The 0.045 was derived from the old formula, to keep the
1461 // norm identical when P=1, as the norm for P=1 seemed
1462 // to work quite well: 0.045 = 0.015 * (2 * P + 1) with P=1.
1463 float norm = .045f / ((2 * P + 1) * (2 * P + 1));
1464 if(!d->fix_anscombe_and_nlmeans_norm)
1465 {
1466 // use old formula
1467 norm = .015f / (2 * P + 1);
1468 }
1469 return norm;
1470}
1471
1472// adjust the user-specified scattering factor and search radius to account for the type of pixelpipe
1473// called by: process_nlmeans_cpu, process_nlmeans_cl
1474static inline __attribute__((always_inline)) float nlmeans_scattering(const dt_dev_pixelpipe_t *pipe, int *nbhood,
1475 const dt_iop_denoiseprofile_data_t *const d,
1476 const dt_dev_pixelpipe_iop_t *const piece, const float scale)
1477{
1478 int K = *nbhood;
1479 float scattering = d->scattering;
1480
1481 if(dt_dev_pixelpipe_has_preview_output(piece->module->dev, pipe, NULL)
1482 || pipe->type == DT_DEV_PIXELPIPE_THUMBNAIL)
1483 {
1484 // much faster slightly more inaccurate preview
1485 const int maxk = (K * K * K + 7.0 * K * sqrt(K)) * scattering / 6.0 + K;
1486 K = MIN(3, K);
1487 scattering = (maxk - K) * 6.0 / (K * K * K + 7.0 * K * sqrt(K));
1488 }
1489 if(!dt_dev_pixelpipe_has_preview_output(piece->module->dev, pipe, NULL))
1490 {
1491 // much faster slightly more inaccurate preview
1492 const int maxk = (K * K * K + 7.0 * K * sqrt(K)) * scattering / 6.0 + K;
1493 K = MAX(MIN(4, K), K * scale);
1494 scattering = (maxk - K) * 6.0 / (K * K * K + 7.0 * K * sqrt(K));
1495 }
1496 *nbhood = K;
1497 return scattering;
1498}
1499
1500// called by process_nlmeans_cpu
1501// must keep synchronized with nlmeans_precondition_cl below
1502static inline __attribute__((always_inline)) float nlmeans_precondition(const dt_iop_denoiseprofile_data_t *const d,
1503 const dt_dev_pixelpipe_iop_t *const piece, dt_aligned_pixel_t wb,
1504 const void *const ivoid, const dt_iop_roi_t *const roi_in,
1505 float scale, float *in, dt_aligned_pixel_t aa,
1507{
1508 // the "unused" fourth array element enables vectorization
1509 const dt_aligned_pixel_t wb_weights = { 1.0f, 1.0f, 1.0f, 0.0f };
1510 compute_wb_factors(wb,d,piece,wb_weights);
1511
1512 // adaptive p depending on white balance
1513 p[0] = MAX(d->shadows + 0.1 * logf(scale / wb[0]), 0.0f);
1514 p[1] = MAX(d->shadows + 0.1 * logf(scale / wb[1]), 0.0f);
1515 p[2] = MAX(d->shadows + 0.1 * logf(scale / wb[2]), 0.0f);
1516 p[3] = 0.0f;
1517
1518 // update the coeffs with strength and scale
1519 for_each_channel(i,aligned(wb,aa,bb))
1520 {
1521 wb[i] *= d->strength * scale;
1522 // only use green channel + wb for now:
1523 aa[i] = d->a[1] * wb[i];
1524 bb[i] = d->b[1] * wb[i];
1525 }
1526 const float compensate_p = DT_IOP_DENOISE_PROFILE_P_FULCRUM / powf(DT_IOP_DENOISE_PROFILE_P_FULCRUM, d->shadows);
1527 if(!d->use_new_vst)
1528 {
1529 precondition((float *)ivoid, in, roi_in->width, roi_in->height, aa, bb);
1530 }
1531 else
1532 {
1533 precondition_v2((float *)ivoid, in, roi_in->width, roi_in->height, d->a[1] * compensate_p, p, d->b[1], wb);
1534 }
1535 return compensate_p;
1536}
1537
1538#ifdef HAVE_OPENCL
1539// called by process_nlmeans_cl
1540// must keep synchronized with nlmeans_precondition above
1542 const dt_dev_pixelpipe_iop_t *const piece, dt_aligned_pixel_t wb,
1543 float scale, dt_aligned_pixel_t aa, dt_aligned_pixel_t bb,
1545{
1546 // the "unused" fourth element enables vectorization
1547 const dt_aligned_pixel_t wb_weights = { 1.0f, 1.0f, 1.0f, 0.0f };
1548 compute_wb_factors(wb,d,piece,wb_weights);
1549 wb[3] = 0.0;
1550
1551 // adaptive p depending on white balance
1552 p[0] = MAX(d->shadows + 0.1 * logf(scale / wb[0]), 0.0f);
1553 p[1] = MAX(d->shadows + 0.1 * logf(scale / wb[1]), 0.0f);
1554 p[2] = MAX(d->shadows + 0.1 * logf(scale / wb[2]), 0.0f);
1555 p[3] = 1.0f;
1556
1557 // update the coeffs with strength and scale
1558 for_each_channel(i,aligned(wb,aa,bb))
1559 {
1560 wb[i] *= d->strength * scale;
1561 // only use green channel + wb for now:
1562 aa[i] = d->a[1] * wb[i];
1563 bb[i] = d->b[1] * wb[i];
1564 }
1565 aa[3] = 1.0f;
1566 bb[3] = 1.0f;
1567 const float compensate_p = DT_IOP_DENOISE_PROFILE_P_FULCRUM / powf(DT_IOP_DENOISE_PROFILE_P_FULCRUM, d->shadows);
1568 if(d->use_new_vst)
1569 {
1570 for_each_channel(c,aligned(aa,bb))
1571 {
1572 aa[c] = d->a[1] * compensate_p;
1573 bb[c] = d->b[1];
1574 }
1575 }
1576 return compensate_p;
1577}
1578#endif /* HAVE_OPENCL */
1579
1580// called by process_nlmeans_cpu
1581static inline __attribute__((always_inline)) void nlmeans_backtransform(const dt_iop_denoiseprofile_data_t *const d, float *ovoid,
1582 const dt_iop_roi_t *const roi_in, const float scale,
1583 const float compensate_p, const dt_aligned_pixel_t wb,
1584 const dt_aligned_pixel_t aa, const dt_aligned_pixel_t bb,
1585 const dt_aligned_pixel_t p)
1586{
1587 if(!d->use_new_vst)
1588 {
1589 backtransform((float *)ovoid, roi_in->width, roi_in->height, aa, bb);
1590 }
1591 else
1592 {
1593 backtransform_v2((float *)ovoid, roi_in->width, roi_in->height, d->a[1] * compensate_p, p, d->b[1], d->bias - 0.5 * logf(scale), wb);
1594 }
1595 return;
1596}
1597
1598static inline __attribute__((always_inline)) int process_nlmeans_cpu(const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1599 const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in,
1600 const dt_iop_roi_t *const roi_out,
1601 void (*denoiser)(const float *const inbuf, float *const outbuf,
1602 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
1603 const dt_nlmeans_param_t *const params))
1604{
1605 // this is called for preview and full pipe separately, each with its own pixelpipe piece.
1606 // get our data struct:
1608
1609 float *restrict in;
1610 if (dt_iop_alloc_image_buffers(piece->module, roi_in, roi_out, 4 | DT_IMGSZ_INPUT, &in, 0))
1611 return 1;
1612
1613 // adjust to zoom size:
1614 const float scale = fminf(fminf(roi_in->scale, 2.0f), 1.0f);
1615 const int P = ceilf(d->radius * scale); // pixel filter size
1616 int K = d->nbhood; // nbhood
1617 const float scattering = nlmeans_scattering(pipe, &K, d, piece, scale);
1618 const float norm = nlmeans_norm(P,d);
1619 const float central_pixel_weight = d->central_pixel_weight * scale;
1620
1621 // P == 0 : this will degenerate to a (fast) bilateral filter.
1622
1623 dt_aligned_pixel_t wb; // the "unused" fourth array element enables vectorization
1625 dt_aligned_pixel_t aa, bb;
1626 const float compensate_p = nlmeans_precondition(d,piece,wb,ivoid,roi_in,scale,in,aa,bb,p);
1627
1628 const dt_aligned_pixel_t norm2 = { 1.0f, 1.0f, 1.0f, 1.0f };
1629 const dt_nlmeans_param_t params = { .scattering = scattering,
1630 .scale = scale,
1631 .luma = 1.0, //no blending
1632 .chroma = 1.0,
1633 .center_weight = central_pixel_weight,
1634 .sharpness = norm,
1635 .patch_radius = P,
1636 .search_radius = K,
1637 .decimate = 0,
1638 .norm = norm2 };
1639 denoiser(in,ovoid,roi_in,roi_out,&params);
1640
1642 nlmeans_backtransform(d,ovoid,roi_in,scale,compensate_p,wb,aa,bb,p);
1643
1645 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
1646 return 0;
1647}
1648
1649static inline __attribute__((always_inline)) int process_nlmeans(const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1650 const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in,
1651 const dt_iop_roi_t *const roi_out)
1652{
1653 return process_nlmeans_cpu(pipe, piece, ivoid, ovoid, roi_in, roi_out, nlmeans_denoise);
1654}
1655
1657static void sum_rec(const size_t npixels, const float *in, float *out)
1658{
1659 if(npixels <= 3)
1660 {
1661 for_each_channel(c,aligned(out))
1662 {
1663 out[c] = 0.0;
1664 }
1665 for(size_t i = 0; i < npixels; i++)
1666 {
1667 for_each_channel(c,aligned(in,out))
1668 {
1669 out[c] += in[i * 4 + c];
1670 }
1671 }
1672 return;
1673 }
1674
1675 const size_t npixels_first_half = npixels >> 1;
1676 const size_t npixels_second_half = npixels - npixels_first_half;
1677 sum_rec(npixels_first_half, in, out);
1678 sum_rec(npixels_second_half, in + 4U * npixels_first_half, out + 4U * npixels_first_half);
1679 for_each_channel(c,aligned(out))
1680 {
1681 out[c] += out[4U * npixels_first_half + c];
1682 }
1683}
1684
1685/* this gives (npixels-1)*V[X] */
1687static void variance_rec(const size_t npixels, const float *in, float *out, const dt_aligned_pixel_t mean)
1688{
1689 if(npixels <= 3)
1690 {
1691 for_each_channel(c,aligned(out))
1692 {
1693 out[c] = 0.0;
1694 }
1695 for(size_t i = 0; i < npixels; i++)
1696 {
1697 for_each_channel(c,aligned(in,out))
1698 {
1699 const float diff = in[i * 4 + c] - mean[c];
1700 out[c] += diff * diff;
1701 }
1702 }
1703 return;
1704 }
1705
1706 const size_t npixels_first_half = npixels >> 1;
1707 const size_t npixels_second_half = npixels - npixels_first_half;
1708 variance_rec(npixels_first_half, in, out, mean);
1709 variance_rec(npixels_second_half, in + 4U * npixels_first_half, out + 4U * npixels_first_half, mean);
1710 for_each_channel(c,aligned(out))
1711 {
1712 out[c] += out[4U * npixels_first_half + c];
1713 }
1714}
1715
1716static inline __attribute__((always_inline)) int process_variance(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
1717 const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
1718 void *const ovoid, const dt_iop_roi_t *const roi_in,
1719 const dt_iop_roi_t *const roi_out)
1720{
1721 const dt_iop_denoiseprofile_data_t *const d = piece->data;
1723
1724 const int width = roi_in->width, height = roi_in->height;
1725 size_t npixels = (size_t)width * height;
1726
1727 memcpy(ovoid, ivoid, sizeof(float) * 4 * npixels);
1728 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out) || (IS_NULL_PTR(g)))
1729 {
1730 return 0;
1731 }
1732
1733 float *restrict in;
1734 if (dt_iop_alloc_image_buffers(self, roi_in, roi_out, 4 | DT_IMGSZ_INPUT, &in, 0))
1735 return 1;
1736
1737 dt_aligned_pixel_t wb; // the "unused" fourth element enables vectorization
1738 const dt_aligned_pixel_t wb_weights = { 1.0f, 1.0f, 1.0f, 0.0f };
1739 compute_wb_factors(wb,d,piece,wb_weights);
1740
1741 // adaptive p depending on white balance
1742 const dt_aligned_pixel_t p = { MAX(d->shadows - 0.1 * logf(wb[0]), 0.0f),
1743 MAX(d->shadows - 0.1 * logf(wb[1]), 0.0f),
1744 MAX(d->shadows - 0.1 * logf(wb[2]), 0.0f),
1745 0.0f };
1746
1747 // update the coeffs with strength
1748 for_each_channel(i) wb[i] *= d->strength;
1749
1750 const float compensate_p = DT_IOP_DENOISE_PROFILE_P_FULCRUM / powf(DT_IOP_DENOISE_PROFILE_P_FULCRUM, d->shadows);
1751 precondition_v2((float *)ivoid, (float *)ovoid, roi_in->width, roi_in->height, d->a[1] * compensate_p, p, d->b[1], wb);
1752
1753 float *out = (float *)ovoid;
1754 // we use out as a temporary buffer here
1755 // compute mean
1756 sum_rec(npixels, in, out);
1757 dt_aligned_pixel_t mean; // the "unused" fourth array element enables vectorization
1758 for_each_channel(c,aligned(out))
1759 {
1760 mean[c] = out[c] / npixels;
1761 }
1762 variance_rec(npixels, in, out, mean);
1763 dt_aligned_pixel_t var; // the "unused" fourth array element enables vectorization
1764 for_each_channel(c,aligned(out))
1765 {
1766 var[c] = out[c] / (npixels - 1);
1767 }
1768 g->variance_R = var[0];
1769 g->variance_G = var[1];
1770 g->variance_B = var[2];
1771
1772 memcpy(ovoid, ivoid, sizeof(float) * 4 * npixels);
1773 return 0;
1774}
1775
1776#if defined(HAVE_OPENCL) && !USE_NEW_IMPL_CL
1777static int bucket_next(unsigned int *state, unsigned int max)
1778{
1779 unsigned int current = *state;
1780 unsigned int next = (current >= max - 1 ? 0 : current + 1);
1781
1782 *state = next;
1783
1784 return next;
1785}
1786#endif
1787
1788#if defined(HAVE_OPENCL)
1789static int process_nlmeans_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
1790 const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
1791 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
1792{
1795#if USE_NEW_IMPL_CL
1796 const int width = roi_in->width;
1797 const int height = roi_in->height;
1798
1799 cl_int err = -999;
1800
1801 const float scale = fminf(fminf(roi_in->scale, 2.0f), 1.0f);
1802 const int P = ceilf(d->radius * scale); // pixel filter size
1803 int K = d->nbhood; // nbhood
1804 const float scattering = nlmeans_scattering(pipe, &K, d, piece, scale);
1805 const float norm = nlmeans_norm(P,d);
1806 const float central_pixel_weight = d->central_pixel_weight * scale;
1807
1812 (void)nlmeans_precondition_cl(d,piece,wb,scale,aa,bb,p);
1813
1814 // allocate a buffer for a preconditioned copy of the image
1815 const int devid = pipe->devid;
1816 cl_mem dev_tmp = dt_opencl_alloc_device(devid, width, height, sizeof(float) * 4);
1817 if(IS_NULL_PTR(dev_tmp))
1818 {
1819 dt_print(DT_DEBUG_OPENCL, "[opencl_denoiseprofile] couldn't allocate GPU buffer\n");
1820 return FALSE;
1821 }
1822
1823 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
1824 const float sigma2[4] = { (bb[0] / aa[0]) * (bb[0] / aa[0]), (bb[1] / aa[1]) * (bb[1] / aa[1]),
1825 (bb[2] / aa[2]) * (bb[2] / aa[2]), 0.0f };
1826
1827 if(!d->use_new_vst)
1828 {
1829 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 0, sizeof(cl_mem), (void *)&dev_in);
1830 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 1, sizeof(cl_mem), (void *)&dev_tmp);
1831 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 2, sizeof(int), (void *)&width);
1832 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 3, sizeof(int), (void *)&height);
1833 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 4, 4 * sizeof(float), (void *)&aa);
1834 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 5, 4 * sizeof(float), (void *)&sigma2);
1836 }
1837 else
1838 {
1839 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 0, sizeof(cl_mem), (void *)&dev_in);
1840 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 1, sizeof(cl_mem), (void *)&dev_tmp);
1841 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 2, sizeof(int), (void *)&width);
1842 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 3, sizeof(int), (void *)&height);
1843 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 4, 4 * sizeof(float), (void *)&aa);
1844 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 5, 4 * sizeof(float), (void *)&p);
1845 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 6, 4 * sizeof(float), (void *)&bb);
1846 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 7, 4 * sizeof(float), (void *)&wb);
1848 }
1849
1850 // allocate a buffer to receive the denoised image
1851 cl_mem dev_U2 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * width * height);
1852 if(IS_NULL_PTR(dev_U2)) err = -999;
1853
1854 if (err == CL_SUCCESS)
1855 {
1856 const dt_aligned_pixel_t norm2 = { 1.0f, 1.0f, 1.0f, 1.0f };
1857 const dt_nlmeans_param_t params =
1858 {
1859 .scattering = scattering,
1860 .scale = scale,
1861 .luma = 1.0f,
1862 .chroma = 1.0f,
1863 .center_weight = central_pixel_weight,
1864 .sharpness = norm,
1865 .patch_radius = P,
1866 .search_radius = K,
1867 .decimate = 0,
1868 .norm = norm2,
1869 .pipetype = pipe->type,
1870 .kernel_init = gd->kernel_denoiseprofile_init,
1871 .kernel_dist = gd->kernel_denoiseprofile_dist,
1872 .kernel_horiz = gd->kernel_denoiseprofile_horiz,
1873 .kernel_vert = gd->kernel_denoiseprofile_vert,
1874 .kernel_accu = gd->kernel_denoiseprofile_accu
1875 };
1876 err = nlmeans_denoiseprofile_cl(&params, devid, dev_tmp, dev_U2, roi_in);
1877 }
1878 if (err == CL_SUCCESS)
1879 {
1880 if(!d->use_new_vst)
1881 {
1882 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 0, sizeof(cl_mem), (void *)&dev_in);
1883 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 1, sizeof(cl_mem), (void *)&dev_U2);
1884 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 2, sizeof(cl_mem), (void *)&dev_out);
1885 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 3, sizeof(int), (void *)&width);
1886 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 4, sizeof(int), (void *)&height);
1887 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 5, 4 * sizeof(float), (void *)&aa);
1888 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 6, 4 * sizeof(float), (void *)&sigma2);
1890 }
1891 else
1892 {
1893 const float bias = d->bias - 0.5 * logf(scale);
1894 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 0, sizeof(cl_mem), (void *)&dev_in);
1895 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 1, sizeof(cl_mem), (void *)&dev_U2);
1896 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 2, sizeof(cl_mem), (void *)&dev_out);
1897 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 3, sizeof(int), (void *)&width);
1898 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 4, sizeof(int), (void *)&height);
1899 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 5, 4 * sizeof(float), (void *)&aa);
1900 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 6, 4 * sizeof(float), (void *)&p);
1901 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 7, 4 * sizeof(float), (void *)&bb);
1902 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 8, sizeof(float), (void *)&bias);
1903 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 9, 4 * sizeof(float), (void *)&wb);
1905 }
1906 }
1909 if (err == CL_SUCCESS)
1910 return TRUE;
1911 dt_print(DT_DEBUG_OPENCL, "[opencl_denoiseprofile] couldn't enqueue kernel! %d\n", err);
1912 return FALSE;
1913
1914#else
1915 const int width = roi_in->width;
1916 const int height = roi_in->height;
1917
1918 cl_int err = -999;
1919
1920 const float scale = fminf(fminf(roi_in->scale, 2.0f), 1.0f);
1921 const int P = ceilf(d->radius * scale); // pixel filter size
1922 int K = d->nbhood; // nbhood
1923 const float scattering = nlmeans_scattering(pipe, &K, d, piece, scale);
1924 const float norm = nlmeans_norm(P,d);
1925 const float central_pixel_weight = d->central_pixel_weight * scale;
1926
1931 (void)nlmeans_precondition_cl(d,piece,wb,scale,aa,bb,p);
1932
1933 const dt_aligned_pixel_t sigma2 = { (bb[0] / aa[0]) * (bb[0] / aa[0]), (bb[1] / aa[1]) * (bb[1] / aa[1]),
1934 (bb[2] / aa[2]) * (bb[2] / aa[2]), 0.0f };
1935
1936 const int devid = pipe->devid;
1937 cl_mem dev_tmp = dt_opencl_alloc_device(devid, width, height, sizeof(float) * 4);
1938 if(IS_NULL_PTR(dev_tmp)) goto error;
1939
1940 cl_mem dev_U2 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * width * height);
1941 if(IS_NULL_PTR(dev_U2)) goto error;
1942
1943 cl_mem buckets[NUM_BUCKETS] = { NULL };
1944 unsigned int state = 0;
1945 for(int k = 0; k < NUM_BUCKETS; k++)
1946 {
1947 buckets[k] = dt_opencl_alloc_device_buffer(devid, sizeof(float) * width * height);
1948 if(buckets[k] == NULL) goto error;
1949 }
1950
1951 int hblocksize;
1953 = (dt_opencl_local_buffer_t){ .xoffset = 2 * P, .xfactor = 1, .yoffset = 0, .yfactor = 1,
1954 .cellsize = sizeof(float), .overhead = 0,
1955 .sizex = 1u << 16, .sizey = 1 };
1956
1958 hblocksize = hlocopt.sizex;
1959 else
1960 hblocksize = 1;
1961
1962 int vblocksize;
1964 = (dt_opencl_local_buffer_t){ .xoffset = 1, .xfactor = 1, .yoffset = 2 * P, .yfactor = 1,
1965 .cellsize = sizeof(float), .overhead = 0,
1966 .sizex = 1, .sizey = 1u << 16 };
1967
1969 vblocksize = vlocopt.sizey;
1970 else
1971 vblocksize = 1;
1972
1973
1974 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
1975 size_t sizesl[3];
1976 size_t local[3];
1977
1978 if(!d->use_new_vst)
1979 {
1980 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 0, sizeof(cl_mem), (void *)&dev_in);
1981 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 1, sizeof(cl_mem), (void *)&dev_tmp);
1982 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 2, sizeof(int), (void *)&width);
1983 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 3, sizeof(int), (void *)&height);
1984 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 4, 4 * sizeof(float), (void *)&aa);
1985 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 5, 4 * sizeof(float), (void *)&sigma2);
1987 if(err != CL_SUCCESS) goto error;
1988 }
1989 else
1990 {
1991 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 0, sizeof(cl_mem), (void *)&dev_in);
1992 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 1, sizeof(cl_mem), (void *)&dev_tmp);
1993 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 2, sizeof(int), (void *)&width);
1994 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 3, sizeof(int), (void *)&height);
1995 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 4, 4 * sizeof(float), (void *)&aa);
1996 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 5, 4 * sizeof(float), (void *)&p);
1997 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 6, 4 * sizeof(float), (void *)&bb);
1998 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 7, 4 * sizeof(float), (void *)&wb);
2000 if(err != CL_SUCCESS) goto error;
2001 }
2002
2003 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_init, 0, sizeof(cl_mem), (void *)&dev_U2);
2004 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_init, 1, sizeof(int), (void *)&width);
2005 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_init, 2, sizeof(int), (void *)&height);
2007 if(err != CL_SUCCESS) goto error;
2008
2009 const size_t bwidth = ROUNDUP(width, hblocksize);
2010 const size_t bheight = ROUNDUP(height, vblocksize);
2011
2012 for(int kj_index = -K; kj_index <= 0; kj_index++)
2013 {
2014 for(int ki_index = -K; ki_index <= K; ki_index++)
2015 {
2016 // This formula is made for:
2017 // - ensuring that j = kj_index and i = ki_index when d->scattering is 0
2018 // - ensuring that no patch can appear twice (provided that d->scattering is in 0,1 range)
2019 // - avoiding grid artifacts by trying to take patches on various lines and columns
2020 const int abs_kj = abs(kj_index);
2021 const int abs_ki = abs(ki_index);
2022 const int j = scale * ((abs_kj * abs_kj * abs_kj + 7.0 * abs_kj * sqrt(abs_ki)) * sign(kj_index) * scattering / 6.0 + kj_index);
2023 const int i = scale * ((abs_ki * abs_ki * abs_ki + 7.0 * abs_ki * sqrt(abs_kj)) * sign(ki_index) * scattering / 6.0 + ki_index);
2024 int q[2] = { i, j };
2025
2026 cl_mem dev_U4 = buckets[bucket_next(&state, NUM_BUCKETS)];
2027 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_dist, 0, sizeof(cl_mem), (void *)&dev_tmp);
2028 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_dist, 1, sizeof(cl_mem), (void *)&dev_U4);
2029 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_dist, 2, sizeof(int), (void *)&width);
2030 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_dist, 3, sizeof(int), (void *)&height);
2031 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_dist, 4, 2 * sizeof(int), (void *)&q);
2033 if(err != CL_SUCCESS) goto error;
2034
2035 sizesl[0] = bwidth;
2036 sizesl[1] = ROUNDUPDHT(height, devid);
2037 sizesl[2] = 1;
2038 local[0] = hblocksize;
2039 local[1] = 1;
2040 local[2] = 1;
2041 cl_mem dev_U4_t = buckets[bucket_next(&state, NUM_BUCKETS)];
2042 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_horiz, 0, sizeof(cl_mem), (void *)&dev_U4);
2043 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_horiz, 1, sizeof(cl_mem), (void *)&dev_U4_t);
2044 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_horiz, 2, sizeof(int), (void *)&width);
2045 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_horiz, 3, sizeof(int), (void *)&height);
2046 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_horiz, 4, 2 * sizeof(int), (void *)&q);
2047 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_horiz, 5, sizeof(int), (void *)&P);
2048 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_horiz, 6, sizeof(float) * (hblocksize + 2 * P),
2049 NULL);
2051 if(err != CL_SUCCESS) goto error;
2052
2053 sizesl[0] = ROUNDUPDWD(width, devid);
2054 sizesl[1] = bheight;
2055 sizesl[2] = 1;
2056 local[0] = 1;
2057 local[1] = vblocksize;
2058 local[2] = 1;
2059 cl_mem dev_U4_tt = buckets[bucket_next(&state, NUM_BUCKETS)];
2060 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 0, sizeof(cl_mem), (void *)&dev_U4_t);
2061 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 1, sizeof(cl_mem), (void *)&dev_U4_tt);
2062 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 2, sizeof(int), (void *)&width);
2063 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 3, sizeof(int), (void *)&height);
2064 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 4, 2 * sizeof(int), (void *)&q);
2065 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 5, sizeof(int), (void *)&P);
2066 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 6, sizeof(float), (void *)&norm);
2067 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 7, sizeof(float) * (vblocksize + 2 * P),
2068 NULL);
2069 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 8, sizeof(float),
2070 (void *)&central_pixel_weight);
2071 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_vert, 9, sizeof(cl_mem), ((void *)&dev_U4));
2073 if(err != CL_SUCCESS) goto error;
2074
2075
2076 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_accu, 0, sizeof(cl_mem), (void *)&dev_tmp);
2077 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_accu, 1, sizeof(cl_mem), (void *)&dev_U2);
2078 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_accu, 2, sizeof(cl_mem), (void *)&dev_U4_tt);
2079 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_accu, 3, sizeof(int), (void *)&width);
2080 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_accu, 4, sizeof(int), (void *)&height);
2081 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_accu, 5, 2 * sizeof(int), (void *)&q);
2083 if(err != CL_SUCCESS) goto error;
2084 }
2085 }
2086
2087 if(!d->use_new_vst)
2088 {
2089 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 0, sizeof(cl_mem), (void *)&dev_in);
2090 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 1, sizeof(cl_mem), (void *)&dev_U2);
2091 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 2, sizeof(cl_mem), (void *)&dev_out);
2092 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 3, sizeof(int), (void *)&width);
2093 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 4, sizeof(int), (void *)&height);
2094 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 5, 4 * sizeof(float), (void *)&aa);
2095 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish, 6, 4 * sizeof(float), (void *)&sigma2);
2097 if(err != CL_SUCCESS) goto error;
2098 }
2099 else
2100 {
2101 const float bias = d->bias - 0.5 * logf(scale);
2102 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 0, sizeof(cl_mem), (void *)&dev_in);
2103 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 1, sizeof(cl_mem), (void *)&dev_U2);
2104 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 2, sizeof(cl_mem), (void *)&dev_out);
2105 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 3, sizeof(int), (void *)&width);
2106 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 4, sizeof(int), (void *)&height);
2107 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 5, 4 * sizeof(float), (void *)&aa);
2108 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 6, 4 * sizeof(float), (void *)&p);
2109 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 7, 4 * sizeof(float), (void *)&bb);
2110 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 8, sizeof(float), (void *)&bias);
2111 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_finish_v2, 9, 4 * sizeof(float), (void *)&wb);
2113 if(err != CL_SUCCESS) goto error;
2114 }
2115
2116 for(int k = 0; k < NUM_BUCKETS; k++)
2117 {
2119 }
2122 return TRUE;
2123
2124error:
2125 for(int k = 0; k < NUM_BUCKETS; k++)
2126 {
2128 }
2131 dt_print(DT_DEBUG_OPENCL, "[opencl_denoiseprofile] couldn't enqueue kernel! %d\n", err);
2132 return FALSE;
2133#endif /* USE_NEW_IMPL_CL */
2134}
2135
2136
2137static int process_wavelets_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
2138 const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
2139 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
2140{
2143
2144 const int max_max_scale = DT_IOP_DENOISE_PROFILE_BANDS; // hard limit
2145 int max_scale = 0;
2146 const float scale = fminf(roi_in->scale, 1.0f);
2147 // largest desired filter on input buffer (20% of input dim)
2148 const float supp0
2149 = MIN(2 * (2u << (max_max_scale - 1)) + 1,
2150 MAX(piece->buf_in.height, piece->buf_in.width) * 0.2f);
2151 const float i0 = dt_log2f((supp0 - 1.0f) * .5f);
2152 for(; max_scale < max_max_scale; max_scale++)
2153 {
2154 // actual filter support on scaled buffer
2155 const float supp = 2 * (2u << max_scale) + 1;
2156 // approximates this filter size on unscaled input image:
2157 const float supp_in = supp * (1.0f / scale);
2158 const float i_in = dt_log2f((supp_in - 1) * .5f) - 1.0f;
2159 // i_in = max_scale .. .. .. 0
2160 const float t = 1.0f - (i_in + .5f) / i0;
2161 if(t < 0.0f) break;
2162 }
2163
2164 const int devid = pipe->devid;
2165 cl_int err = -999;
2166 const int width = roi_in->width;
2167 const int height = roi_in->height;
2168 const size_t npixels = (size_t)width * height;
2169
2170 cl_mem dev_tmp = NULL;
2171 cl_mem dev_buf1 = NULL;
2172 cl_mem dev_buf2 = NULL;
2173 cl_mem dev_m = NULL;
2174 cl_mem dev_r = NULL;
2175 cl_mem dev_filter = NULL;
2176 cl_mem *dev_detail = calloc(max_max_scale, sizeof(cl_mem));
2177 float *sumsum = NULL;
2178
2179 // corner case of extremely small image. this is not really likely to happen but would cause issues later
2180 // when we divide by (n-1). so let's be prepared
2181 if(npixels < 2)
2182 {
2183 // copy original input from dev_in -> dev_out
2184 size_t origin[] = { 0, 0, 0 };
2185 size_t region[] = { width, height, 1 };
2186 err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, origin, origin, region);
2187 if(err != CL_SUCCESS) goto error;
2188 dt_free(dev_detail);
2189 return TRUE;
2190 }
2191
2193 = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1, .yoffset = 0, .yfactor = 1,
2194 .cellsize = 4 * sizeof(float), .overhead = 0,
2195 .sizex = 1u << 4, .sizey = 1u << 4 };
2196
2198 goto error;
2199
2200 const size_t bwidth = ROUNDUP(width, flocopt.sizex);
2201 const size_t bheight = ROUNDUP(height, flocopt.sizey);
2202
2203 const int bufsize = (bwidth / flocopt.sizex) * (bheight / flocopt.sizey);
2204
2206 = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1, .yoffset = 0, .yfactor = 1,
2207 .cellsize = 4 * sizeof(float), .overhead = 0,
2208 .sizex = 1u << 16, .sizey = 1 };
2209
2211 goto error;
2212
2213 const int reducesize = MIN(REDUCESIZE, ROUNDUP(bufsize, slocopt.sizex) / slocopt.sizex);
2214
2215 dev_m = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * bufsize);
2216 if(IS_NULL_PTR(dev_m)) goto error;
2217
2218 dev_r = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4 * reducesize);
2219 if(IS_NULL_PTR(dev_r)) goto error;
2220
2221 sumsum = (float *)dt_pixelpipe_cache_alloc_align(sizeof(float) * 4 * (size_t)reducesize, pipe);
2222 if(IS_NULL_PTR(sumsum)) goto error;
2223 sumsum = (float *)__builtin_assume_aligned(sumsum, DT_CACHELINE_BYTES);
2224
2225 dev_tmp = dt_opencl_alloc_device(devid, width, height, sizeof(float) * 4);
2226 if(IS_NULL_PTR(dev_tmp)) goto error;
2227
2228 float m[] = { 0.0625f, 0.25f, 0.375f, 0.25f, 0.0625f }; // 1/16, 4/16, 6/16, 4/16, 1/16
2229 float mm[5][5];
2230 for(int j = 0; j < 5; j++)
2231 for(int i = 0; i < 5; i++) mm[j][i] = m[i] * m[j];
2232
2233 dev_filter = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 25, mm);
2234 if(IS_NULL_PTR(dev_filter)) goto error;
2235
2236 for(int k = 0; k < max_scale; k++)
2237 {
2238 dev_detail[k] = dt_opencl_alloc_device(devid, width, height, sizeof(float) * 4);
2239 if(dev_detail[k] == NULL) goto error;
2240 }
2241
2242 dt_aligned_pixel_t wb; // the "unused" fourth element enables vectorization
2243 const dt_aligned_pixel_t wb_weights = { 2.0f, 1.0f, 2.0f, 0.0f };
2244 compute_wb_factors(wb,d,piece,wb_weights);
2245 wb[3] = 0.0f;
2246
2247 // adaptive p depending on white balance
2248 const dt_aligned_pixel_t p = { MAX(d->shadows + 0.1 * logf(scale / wb[0]), 0.0f),
2249 MAX(d->shadows + 0.1 * logf(scale / wb[1]), 0.0f),
2250 MAX(d->shadows + 0.1 * logf(scale / wb[2]), 0.0f), 1.0f};
2251
2252 // conversion to Y0U0V0 space as defined in Secrets of image denoising cuisine
2253 dt_colormatrix_t toY0U0V0_tmp = { { 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f },
2254 { 0.5f, 0.0f, -0.5f },
2255 { 0.25f, -0.5f, 0.25f } };
2256 dt_colormatrix_t toRGB_tmp = { { 0.0f, 0.0f, 0.0f }, // "unused" fourth element enables vectorization
2257 { 0.0f, 0.0f, 0.0f },
2258 { 0.0f, 0.0f, 0.0f } };
2259 set_up_conversion_matrices(toY0U0V0_tmp, toRGB_tmp, wb);
2260
2261 // more strength in Y0U0V0 in order to get a similar smoothing as in other modes
2262 // otherwise, result was much less denoised in Y0U0V0 mode.
2263 const float compensate_strength = (d->wavelet_color_mode == MODE_RGB) ? 1.0f : 2.5f;
2264
2265 // update the coeffs with strength and scale
2266 float toY0U0V0[9]; //TODO: change OpenCL kernels to use 3x4 matrices
2267 float toRGB[9] ;
2268 for(size_t k = 0; k < 3; k++)
2269 for(size_t c = 0; c < 3; c++)
2270 //(we can't use for_each_channel here because it can iterate over four elements)
2271 {
2272 toRGB[3*k+c] = toRGB_tmp[k][c] * d->strength * compensate_strength * scale;
2273 toY0U0V0[3*k+c] = toY0U0V0_tmp[k][c] / (d->strength * compensate_strength * scale);
2274 }
2275
2276 // update the coeffs with strength and scale
2277 for_each_channel(i) wb[i] *= d->strength * compensate_strength * scale;
2278
2279 dt_aligned_pixel_t aa = { d->a[1] * wb[0], d->a[1] * wb[1], d->a[1] * wb[2], 1.0f };
2280 dt_aligned_pixel_t bb = { d->b[1] * wb[0], d->b[1] * wb[1], d->b[1] * wb[2], 1.0f };
2281 const dt_aligned_pixel_t sigma2 = { (bb[0] / aa[0]) * (bb[0] / aa[0]), (bb[1] / aa[1]) * (bb[1] / aa[1]),
2282 (bb[2] / aa[2]) * (bb[2] / aa[2]), 0.0f };
2283 const float compensate_p = DT_IOP_DENOISE_PROFILE_P_FULCRUM / powf(DT_IOP_DENOISE_PROFILE_P_FULCRUM, d->shadows);
2284 if(d->use_new_vst)
2285 {
2287 {
2288 aa[c] = d->a[1] * compensate_p;
2289 bb[c] = d->b[1];
2290 }
2291 }
2292
2293 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
2294
2295 if(!d->use_new_vst)
2296 {
2297 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 0, sizeof(cl_mem), (void *)&dev_in);
2298 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 1, sizeof(cl_mem), (void *)&dev_out);
2299 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 2, sizeof(int), (void *)&width);
2300 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 3, sizeof(int), (void *)&height);
2301 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 4, 4 * sizeof(float), (void *)&aa);
2302 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition, 5, 4 * sizeof(float), (void *)&sigma2);
2304 if(err != CL_SUCCESS) goto error;
2305 }
2306 else if(d->wavelet_color_mode == MODE_RGB)
2307 {
2308 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 0, sizeof(cl_mem), (void *)&dev_in);
2309 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 1, sizeof(cl_mem), (void *)&dev_out);
2310 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 2, sizeof(int), (void *)&width);
2311 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 3, sizeof(int), (void *)&height);
2312 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 4, 4 * sizeof(float), (void *)&aa);
2313 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 5, 4 * sizeof(float), (void *)&p);
2314 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 6, 4 * sizeof(float), (void *)&bb);
2315 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_v2, 7, 4 * sizeof(float), (void *)&wb);
2317 if(err != CL_SUCCESS) goto error;
2318 }
2319 else
2320 {
2321 cl_mem dev_Y0U0V0 = NULL;
2322 dev_Y0U0V0 = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 9, toY0U0V0);
2323 if(!IS_NULL_PTR(dev_Y0U0V0))
2324 {
2325 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_Y0U0V0, 0, sizeof(cl_mem), (void *)&dev_in);
2326 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_Y0U0V0, 1, sizeof(cl_mem), (void *)&dev_out);
2327 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_Y0U0V0, 2, sizeof(int), (void *)&width);
2329 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_Y0U0V0, 4, 4 * sizeof(float), (void *)&aa);
2330 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_Y0U0V0, 5, 4 * sizeof(float), (void *)&p);
2331 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_Y0U0V0, 6, 4 * sizeof(float), (void *)&bb);
2332 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_precondition_Y0U0V0, 7, sizeof(cl_mem), (void *)&dev_Y0U0V0);
2334 dt_opencl_release_mem_object(dev_Y0U0V0);
2335 if(err != CL_SUCCESS) goto error;
2336 }
2337 else
2338 {
2339 dt_opencl_release_mem_object(dev_Y0U0V0);
2340 goto error;
2341 }
2342 }
2343
2344 dev_buf1 = dev_out;
2345 dev_buf2 = dev_tmp;
2346
2347 /* decompose image into detail scales and coarse */
2348 for(int s = 0; s < max_scale; s++)
2349 {
2350 const float sigma = 1.0f;
2351 const float varf = sqrtf(2.0f + 2.0f * 4.0f * 4.0f + 6.0f * 6.0f) / 16.0f; // about 0.5
2352 const float sigma_band = powf(varf, s) * sigma;
2353 const float inv_sigma2 = 1.0f / (sigma_band * sigma_band);
2354
2355 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_decompose, 0, sizeof(cl_mem), (void *)&dev_buf1);
2356 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_decompose, 1, sizeof(cl_mem), (void *)&dev_buf2);
2357 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_decompose, 2, sizeof(cl_mem),
2358 (void *)&dev_detail[s]);
2359 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_decompose, 3, sizeof(int), (void *)&width);
2360 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_decompose, 4, sizeof(int), (void *)&height);
2361 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_decompose, 5, sizeof(unsigned int),
2362 (void *)&s);
2363 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_decompose, 6, sizeof(float),
2364 (void *)&inv_sigma2);
2365 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_decompose, 7, sizeof(cl_mem),
2366 (void *)&dev_filter);
2368 if(err != CL_SUCCESS) goto error;
2369
2370 // swap buffers
2371 cl_mem dev_buf3 = dev_buf2;
2372 dev_buf2 = dev_buf1;
2373 dev_buf1 = dev_buf3;
2374 }
2375
2376 /* now synthesize again */
2377 for(int s = max_scale - 1; s >= 0; s--)
2378 {
2379 // variance stabilizing transform maps sigma to unity.
2380 const float sigma = 1.0f;
2381 // it is then transformed by wavelet scales via the 5 tap a-trous filter:
2382 const float varf = sqrtf(2.0f + 2.0f * 4.0f * 4.0f + 6.0f * 6.0f) / 16.0f; // about 0.5
2383 const float sigma_band = powf(varf, s) * sigma;
2384
2385 // determine thrs as bayesshrink
2386 dt_aligned_pixel_t sum_y2 = { 0.0f };
2387
2388 size_t lsizes[3];
2389 size_t llocal[3];
2390
2391 lsizes[0] = bwidth;
2392 lsizes[1] = bheight;
2393 lsizes[2] = 1;
2394 llocal[0] = flocopt.sizex;
2395 llocal[1] = flocopt.sizey;
2396 llocal[2] = 1;
2398 &(dev_detail[s]));
2401 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_reduce_first, 3, sizeof(cl_mem), &dev_m);
2403 sizeof(float) * 4 * flocopt.sizex * flocopt.sizey, NULL);
2405 llocal);
2406 if(err != CL_SUCCESS) goto error;
2407
2408
2409 lsizes[0] = (size_t)reducesize * slocopt.sizex;
2410 lsizes[1] = 1;
2411 lsizes[2] = 1;
2412 llocal[0] = slocopt.sizex;
2413 llocal[1] = 1;
2414 llocal[2] = 1;
2415 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_reduce_second, 0, sizeof(cl_mem), &dev_m);
2416 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_reduce_second, 1, sizeof(cl_mem), &dev_r);
2417 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_reduce_second, 2, sizeof(int), &bufsize);
2418 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_reduce_second, 3, sizeof(float) * 4 * slocopt.sizex,
2419 NULL);
2421 llocal);
2422 if(err != CL_SUCCESS) goto error;
2423
2424 err = dt_opencl_read_buffer_from_device(devid, (void *)sumsum, dev_r, 0,
2425 sizeof(float) * 4 * reducesize, CL_TRUE);
2426 if(err != CL_SUCCESS)
2427 goto error;
2428
2429 for(int k = 0; k < reducesize; k++)
2430 {
2432 {
2433 sum_y2[c] += sumsum[4 * k + c];
2434 }
2435 }
2436
2437 const float sb2 = sigma_band * sigma_band;
2438 const dt_aligned_pixel_t var_y = { sum_y2[0] / (npixels - 1.0f), sum_y2[1] / (npixels - 1.0f),
2439 sum_y2[2] / (npixels - 1.0f), 0.0f };
2440 const dt_aligned_pixel_t std_x = { sqrtf(MAX(1e-6f, var_y[0] - sb2)), sqrtf(MAX(1e-6f, var_y[1] - sb2)),
2441 sqrtf(MAX(1e-6f, var_y[2] - sb2)), 1.0f };
2442 // add 8.0 here because it seemed a little weak
2443 dt_aligned_pixel_t adjt = { 8.0f, 8.0f, 8.0f, 0.0f };
2444
2445 const int offset_scale = DT_IOP_DENOISE_PROFILE_BANDS - max_scale;
2446 const int band_index = DT_IOP_DENOISE_PROFILE_BANDS - (s + offset_scale + 1);
2447
2448 if(d->wavelet_color_mode == MODE_RGB)
2449 {
2450 // current scale number is s+offset_scale
2451 // for instance, largest s is DT_IOP_DENOISE_PROFILE_BANDS
2452 // max_scale only indicates the number of scales to process at THIS
2453 // zoom level, it does NOT corresponds to the the maximum number of scales.
2454 // in other words, max_s is the maximum number of VISIBLE scales.
2455 // That is why we have this "s+offset_scale"
2456 float band_force_exp_2 = d->force[DT_DENOISE_PROFILE_ALL][band_index];
2457 band_force_exp_2 *= band_force_exp_2;
2458 band_force_exp_2 *= 4;
2460 {
2461 adjt[ch] *= band_force_exp_2;
2462 }
2463 band_force_exp_2 = d->force[DT_DENOISE_PROFILE_R][band_index];
2464 band_force_exp_2 *= band_force_exp_2;
2465 band_force_exp_2 *= 4;
2466 adjt[0] *= band_force_exp_2;
2467 band_force_exp_2 = d->force[DT_DENOISE_PROFILE_G][band_index];
2468 band_force_exp_2 *= band_force_exp_2;
2469 band_force_exp_2 *= 4;
2470 adjt[1] *= band_force_exp_2;
2471 band_force_exp_2 = d->force[DT_DENOISE_PROFILE_B][band_index];
2472 band_force_exp_2 *= band_force_exp_2;
2473 band_force_exp_2 *= 4;
2474 adjt[2] *= band_force_exp_2;
2475 }
2476 else
2477 {
2478 float band_force_exp_2 = d->force[DT_DENOISE_PROFILE_Y0][band_index];
2479 band_force_exp_2 *= band_force_exp_2;
2480 band_force_exp_2 *= 4;
2481 adjt[0] *= band_force_exp_2;
2482 band_force_exp_2 = d->force[DT_DENOISE_PROFILE_U0V0][band_index];
2483 band_force_exp_2 *= band_force_exp_2;
2484 band_force_exp_2 *= 4;
2485 adjt[1] *= band_force_exp_2;
2486 adjt[2] *= band_force_exp_2;
2487 }
2488
2489 const dt_aligned_pixel_t thrs = { adjt[0] * sb2 / std_x[0], adjt[1] * sb2 / std_x[1],
2490 adjt[2] * sb2 / std_x[2], 0.0f };
2491 // fprintf(stderr, "scale %d thrs %f %f %f\n", s, thrs[0], thrs[1], thrs[2]);
2492
2493 const dt_aligned_pixel_t boost = { 1.0f, 1.0f, 1.0f, 1.0f };
2494
2495 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 0, sizeof(cl_mem),
2496 (void *)&dev_buf1);
2497 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 1, sizeof(cl_mem),
2498 (void *)&dev_detail[s]);
2499 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 2, sizeof(cl_mem),
2500 (void *)&dev_buf2);
2501 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 3, sizeof(int), (void *)&width);
2502 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 4, sizeof(int), (void *)&height);
2503 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 5, sizeof(float), (void *)&thrs[0]);
2504 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 6, sizeof(float), (void *)&thrs[1]);
2505 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 7, sizeof(float), (void *)&thrs[2]);
2506 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 8, sizeof(float), (void *)&thrs[3]);
2507 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 9, sizeof(float), (void *)&boost[0]);
2508 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 10, sizeof(float),
2509 (void *)&boost[1]);
2510 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 11, sizeof(float),
2511 (void *)&boost[2]);
2512 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_synthesize, 12, sizeof(float),
2513 (void *)&boost[3]);
2515 if(err != CL_SUCCESS) goto error;
2516
2517 // swap buffers
2518 cl_mem dev_buf3 = dev_buf2;
2519 dev_buf2 = dev_buf1;
2520 dev_buf1 = dev_buf3;
2521 }
2522
2523 // copy output of last run of synthesize kernel to dev_tmp (if not already there)
2524 // note: we need to take swap of buffers into account, so current output lies in dev_buf1
2525 if(dev_buf1 != dev_tmp)
2526 {
2527 size_t origin[] = { 0, 0, 0 };
2528 size_t region[] = { width, height, 1 };
2529 err = dt_opencl_enqueue_copy_image(devid, dev_buf1, dev_tmp, origin, origin, region);
2530 if(err != CL_SUCCESS) goto error;
2531 }
2532
2533 if(!d->use_new_vst)
2534 {
2535 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform, 0, sizeof(cl_mem), (void *)&dev_tmp);
2536 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform, 1, sizeof(cl_mem), (void *)&dev_out);
2537 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform, 2, sizeof(int), (void *)&width);
2538 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform, 3, sizeof(int), (void *)&height);
2539 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform, 4, 4 * sizeof(float), (void *)&aa);
2540 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform, 5, 4 * sizeof(float), (void *)&sigma2);
2542 if(err != CL_SUCCESS) goto error;
2543 }
2544 else if(d->wavelet_color_mode == MODE_RGB)
2545 {
2546 const float bias = d->bias - 0.5 * logf(scale);
2547 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_v2, 0, sizeof(cl_mem), (void *)&dev_tmp);
2548 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_v2, 1, sizeof(cl_mem), (void *)&dev_out);
2549 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_v2, 2, sizeof(int), (void *)&width);
2550 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_v2, 3, sizeof(int), (void *)&height);
2551 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_v2, 4, 4 * sizeof(float), (void *)&aa);
2552 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_v2, 5, 4 * sizeof(float), (void *)&p);
2553 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_v2, 6, 4 * sizeof(float), (void *)&bb);
2554 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_v2, 7, sizeof(float), (void *)&bias);
2555 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_v2, 8, 4 * sizeof(float), (void *)&wb);
2557 if(err != CL_SUCCESS) goto error;
2558 }
2559 else
2560 {
2561 cl_mem dev_RGB = NULL;
2562 dev_RGB = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 9, toRGB);
2563 if(!IS_NULL_PTR(dev_RGB))
2564 {
2565 const float bias = d->bias - 0.5 * logf(scale);
2566 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_Y0U0V0, 0, sizeof(cl_mem), (void *)&dev_tmp);
2567 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_Y0U0V0, 1, sizeof(cl_mem), (void *)&dev_out);
2570 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_Y0U0V0, 4, 4 * sizeof(float), (void *)&aa);
2571 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_Y0U0V0, 5, 4 * sizeof(float), (void *)&p);
2572 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_Y0U0V0, 6, 4 * sizeof(float), (void *)&bb);
2573 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_Y0U0V0, 7, sizeof(float), (void *)&bias);
2574 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_Y0U0V0, 8, 4 * sizeof(float), (void *)&wb);
2575 dt_opencl_set_kernel_arg(devid, gd->kernel_denoiseprofile_backtransform_Y0U0V0, 9, sizeof(cl_mem), (void *)&dev_RGB);
2578 if(err != CL_SUCCESS) goto error;
2579 }
2580 else
2581 {
2583 goto error;
2584 }
2585 }
2586
2590 dt_opencl_release_mem_object(dev_filter);
2591 for(int k = 0; k < max_scale; k++)
2592 dt_opencl_release_mem_object(dev_detail[k]);
2593 dt_free(dev_detail);
2595 return TRUE;
2596
2597error:
2601 dt_opencl_release_mem_object(dev_filter);
2602 for(int k = 0; k < max_scale; k++)
2603 dt_opencl_release_mem_object(dev_detail[k]);
2604 dt_free(dev_detail);
2606 dt_print(DT_DEBUG_OPENCL, "[opencl_denoiseprofile] couldn't enqueue kernel! %d, devid %d\n", err, devid);
2607 return FALSE;
2608}
2609
2610int 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)
2611{
2612 const dt_iop_roi_t *const roi_in = &piece->roi_in;
2613 const dt_iop_roi_t *const roi_out = &piece->roi_out;
2615
2616 if(d->mode == MODE_NLMEANS || d->mode == MODE_NLMEANS_AUTO)
2617 {
2618 return process_nlmeans_cl(self, pipe, piece, dev_in, dev_out, roi_in, roi_out);
2619 }
2620 else if(d->mode == MODE_WAVELETS || d->mode == MODE_WAVELETS_AUTO)
2621 {
2622 return process_wavelets_cl(self, pipe, piece, dev_in, dev_out, roi_in, roi_out);
2623 }
2624 else
2625 {
2626 dt_print(DT_DEBUG_OPENCL, "[opencl_denoiseprofile] compute variance not yet supported by opencl code\n");
2627 return FALSE;
2628 }
2629}
2630#endif // HAVE_OPENCL
2631
2632int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
2633 void *const ovoid)
2634{
2635 const dt_iop_roi_t *const roi_in = &piece->roi_in;
2636 const dt_iop_roi_t *const roi_out = &piece->roi_out;
2638 int err = 0;
2639 if(d->mode == MODE_NLMEANS || d->mode == MODE_NLMEANS_AUTO)
2640 err = process_nlmeans(pipe, piece, ivoid, ovoid, roi_in, roi_out);
2641 else if(d->mode == MODE_WAVELETS || d->mode == MODE_WAVELETS_AUTO)
2642 err = process_wavelets(self, pipe, piece, ivoid, ovoid, roi_in, roi_out, eaw_dn_decompose, eaw_synthesize);
2643 else
2644 err = process_variance(self, pipe, piece, ivoid, ovoid, roi_in, roi_out);
2645 return err;
2646}
2647
2648static inline unsigned infer_radius_from_profile(const float a)
2649{
2650 return MIN((unsigned)(1.0f + a * 15000.0f + a * a * 300000.0f), 8);
2651}
2652
2653static inline float infer_scattering_from_profile(const float a)
2654{
2655 return MIN(3000.0f * a, 1.0f);
2656}
2657
2658static inline float infer_shadows_from_profile(const float a)
2659{
2660 return MIN(MAX(0.1f - 0.1 * logf(a), 0.7f), 1.8f);
2661}
2662
2663static inline float infer_bias_from_profile(const float a)
2664{
2665 return -MAX(5 + 0.5 * logf(a), 0.0);
2666}
2667
2669{
2670 dt_iop_default_init(module);
2671
2672 dt_iop_denoiseprofile_params_t *d = module->default_params;
2673
2674 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
2675 {
2676 for(int ch = 0; ch < DT_DENOISE_PROFILE_NONE; ch++)
2677 {
2678 d->x[ch][k] = k / (DT_IOP_DENOISE_PROFILE_BANDS - 1.f);
2679 }
2680 }
2681}
2682
2684// Per-image default noise profile, gui-free (mirrors get_auto_profile but keeps the historical
2685// a[0] = -1 sentinel that signals commit_params to re-autodetect at pipe time when a real per-ISO
2686// match/interpolation is found). Used by reload_defaults() so defaults are computed even on
2687// export/thumbnail devs that have no gui_data.
2689{
2690 GList *profiles = dt_noiseprofile_get_matching(&module->dev->image_storage);
2691 dt_noiseprofile_t interpolated = dt_noiseprofile_generic; // default to generic poissonian
2692
2693 const int iso = module->dev->image_storage.exif_iso;
2694 dt_noiseprofile_t *last = NULL;
2695 for(GList *iter = profiles; iter; iter = g_list_next(iter))
2696 {
2697 dt_noiseprofile_t *current = (dt_noiseprofile_t *)iter->data;
2698 if(current->iso == iso)
2699 {
2700 interpolated = *current;
2701 interpolated.a[0] = -1.0f; // signal later autodetection in commit_params
2702 break;
2703 }
2704 if(last && last->iso < iso && current->iso > iso)
2705 {
2706 interpolated.iso = iso;
2707 dt_noiseprofile_interpolate(last, current, &interpolated);
2708 interpolated.a[0] = -1.0f;
2709 break;
2710 }
2711 last = current;
2712 }
2713 g_list_free_full(profiles, dt_noiseprofile_free);
2714 return interpolated;
2715}
2716
2718{
2719 // Params only. The matching-profiles combobox, g->profiles/g->interpolated and the slider defaults
2720 // are set in gui_update() (GUI thread, widgets present): reload_defaults() also runs on
2721 // export/thumbnail devs with no gui_data, where the old `if(g)` guard left defaults uncomputed.
2722 const dt_noiseprofile_t interpolated = _default_noise_profile(module);
2723
2724 // all these formulas were "guessed" and are completely empirical
2725 const float a = interpolated.a[1];
2726 dt_iop_denoiseprofile_params_t *d = module->default_params;
2727
2729 d->scattering = infer_scattering_from_profile(a);
2730 d->shadows = infer_shadows_from_profile(a);
2731 d->bias = infer_bias_from_profile(a);
2732
2733 for(int k = 0; k < 3; k++)
2734 {
2735 d->a[k] = interpolated.a[k];
2736 d->b[k] = interpolated.b[k];
2737 }
2738}
2739
2741{
2742 const int program = 11; // denoiseprofile.cl, from programs.conf
2745 module->data = gd;
2746 gd->kernel_denoiseprofile_precondition = dt_opencl_create_kernel(program, "denoiseprofile_precondition");
2747 gd->kernel_denoiseprofile_precondition_v2 = dt_opencl_create_kernel(program, "denoiseprofile_precondition_v2");
2748 gd->kernel_denoiseprofile_precondition_Y0U0V0 = dt_opencl_create_kernel(program, "denoiseprofile_precondition_Y0U0V0");
2749 gd->kernel_denoiseprofile_init = dt_opencl_create_kernel(program, "denoiseprofile_init");
2750 gd->kernel_denoiseprofile_dist = dt_opencl_create_kernel(program, "denoiseprofile_dist");
2751 gd->kernel_denoiseprofile_horiz = dt_opencl_create_kernel(program, "denoiseprofile_horiz");
2752 gd->kernel_denoiseprofile_vert = dt_opencl_create_kernel(program, "denoiseprofile_vert");
2753 gd->kernel_denoiseprofile_accu = dt_opencl_create_kernel(program, "denoiseprofile_accu");
2754 gd->kernel_denoiseprofile_finish = dt_opencl_create_kernel(program, "denoiseprofile_finish");
2755 gd->kernel_denoiseprofile_finish_v2 = dt_opencl_create_kernel(program, "denoiseprofile_finish_v2");
2756 gd->kernel_denoiseprofile_backtransform = dt_opencl_create_kernel(program, "denoiseprofile_backtransform");
2757 gd->kernel_denoiseprofile_backtransform_v2 = dt_opencl_create_kernel(program, "denoiseprofile_backtransform_v2");
2758 gd->kernel_denoiseprofile_backtransform_Y0U0V0 = dt_opencl_create_kernel(program, "denoiseprofile_backtransform_Y0U0V0");
2759 gd->kernel_denoiseprofile_decompose = dt_opencl_create_kernel(program, "denoiseprofile_decompose");
2760 gd->kernel_denoiseprofile_synthesize = dt_opencl_create_kernel(program, "denoiseprofile_synthesize");
2761 gd->kernel_denoiseprofile_reduce_first = dt_opencl_create_kernel(program, "denoiseprofile_reduce_first");
2762 gd->kernel_denoiseprofile_reduce_second = dt_opencl_create_kernel(program, "denoiseprofile_reduce_second");
2763}
2764
2785
2787{
2788 GList *profiles = dt_noiseprofile_get_matching(&self->dev->image_storage);
2789 dt_noiseprofile_t interpolated = dt_noiseprofile_generic; // default to generic poissonian
2790
2791 const int iso = self->dev->image_storage.exif_iso;
2792 dt_noiseprofile_t *last = NULL;
2793 for(GList *iter = profiles; iter; iter = g_list_next(iter))
2794 {
2795 dt_noiseprofile_t *current = (dt_noiseprofile_t *)iter->data;
2796 if(current->iso == iso)
2797 {
2798 interpolated = *current;
2799 break;
2800 }
2801 if(last && last->iso < iso && current->iso > iso)
2802 {
2803 interpolated.iso = iso;
2804 dt_noiseprofile_interpolate(last, current, &interpolated);
2805 break;
2806 }
2807 last = current;
2808 }
2809 g_list_free_full(profiles, dt_noiseprofile_free);
2810 profiles = NULL;
2811 return interpolated;
2812}
2813
2817{
2820
2821 d->nbhood = p->nbhood;
2822 d->central_pixel_weight = p->central_pixel_weight;
2823 d->strength = p->strength;
2824 d->overshooting = p->overshooting;
2825 for(int i = 0; i < 3; i++)
2826 {
2827 d->a[i] = p->a[i];
2828 d->b[i] = p->b[i];
2829 }
2830 d->mode = p->mode;
2831 d->wavelet_color_mode = p->wavelet_color_mode;
2832
2833 // compare if a[0] in params is set to "magic value" -1.0 for autodetection
2834 if(p->a[0] == -1.0)
2835 {
2836 // autodetect matching profile again, the same way as detecting their names,
2837 // this is partially duplicated code and data because we are not allowed to access
2838 // gui_data here ..
2840 for(int k = 0; k < 3; k++)
2841 {
2842 d->a[k] = interpolated.a[k];
2843 d->b[k] = interpolated.b[k];
2844 }
2845 }
2846
2847 if((p->mode == MODE_NLMEANS_AUTO) || (p->mode == MODE_WAVELETS_AUTO))
2848 {
2849 const float gain = p->overshooting;
2850 d->radius = infer_radius_from_profile(d->a[1] * gain);
2851 d->scattering = infer_scattering_from_profile(d->a[1] * gain);
2852 d->shadows = infer_shadows_from_profile(d->a[1] * gain);
2853 d->bias = infer_bias_from_profile(d->a[1] * gain);
2854 }
2855 else
2856 {
2857 d->radius = p->radius;
2858 d->scattering = p->scattering;
2859 d->shadows = p->shadows;
2860 d->bias = p->bias;
2861 }
2862
2863 for(int ch = 0; ch < DT_DENOISE_PROFILE_NONE; ch++)
2864 {
2865 dt_draw_curve_set_point(d->curve[ch], 0, p->x[ch][DT_IOP_DENOISE_PROFILE_BANDS - 2] - 1.f, p->y[ch][0]);
2866 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
2867 dt_draw_curve_set_point(d->curve[ch], k, p->x[ch][k], p->y[ch][k]);
2868 dt_draw_curve_set_point(d->curve[ch], DT_IOP_DENOISE_PROFILE_BANDS + 1, p->x[ch][1] + 1.f,
2870 dt_draw_curve_calc_values(d->curve[ch], 0.0, 1.0, DT_IOP_DENOISE_PROFILE_BANDS, NULL, d->force[ch]);
2871 }
2872
2873 d->wb_adaptive_anscombe = p->wb_adaptive_anscombe;
2874 d->fix_anscombe_and_nlmeans_norm = p->fix_anscombe_and_nlmeans_norm;
2875 d->use_new_vst = p->use_new_vst;
2876 piece->cache_output_on_ram = TRUE;
2877}
2878
2880{
2883
2884 piece->data = (void *)d;
2885 piece->data_size = sizeof(dt_iop_denoiseprofile_data_t);
2886 // Checked AFTER both piece->data and piece->data_size are set, deliberately.
2887 // dt_iop_init_pipe() disables the node for us when it sees data_size > 0 with a NULL
2888 // data -- returning before data_size is assigned skips that and leaves the node enabled
2889 // with no storage. dt_calloc_align() returns NULL on failure and pipe nodes are built
2890 // exactly when memory is tightest (Sentry 134134395 faulted in atrous's init_pipe).
2891 if(IS_NULL_PTR(piece->data)) return;
2892 for(int ch = 0; ch < DT_DENOISE_PROFILE_NONE; ch++)
2893 {
2894 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, CATMULL_ROM);
2895 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
2896 (void)dt_draw_curve_add_point(d->curve[ch], default_params->x[ch][k], default_params->y[ch][k]);
2897 }
2898}
2899
2901{
2902 /* init_pipe() may have failed to allocate, and cleanup runs regardless. */
2903 if(IS_NULL_PTR(piece->data)) return;
2905 for(int ch = 0; ch < DT_DENOISE_PROFILE_NONE; ch++) dt_draw_curve_destroy(d->curve[ch]);
2906 dt_free_align(piece->data);
2907 piece->data = NULL;
2908}
2909
2911{
2912 int i = dt_bauhaus_combobox_get(w);
2915 const dt_noiseprofile_t *profile = &(g->interpolated);
2916 if(i > 0) profile = (dt_noiseprofile_t *)g_list_nth_data(g->profiles, i - 1);
2917 for(int k = 0; k < 3; k++)
2918 {
2919 p->a[k] = profile->a[k];
2920 p->b[k] = profile->b[k];
2921 }
2922 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2923}
2924
2926{
2929 const unsigned mode = dt_bauhaus_combobox_get(w);
2930 switch(mode)
2931 {
2932 case 0:
2933 p->mode = MODE_NLMEANS;
2934 gtk_widget_hide(g->box_wavelets);
2935 gtk_widget_hide(g->box_variance);
2936 gtk_widget_show_all(g->box_nlm);
2937 break;
2938 case 1:
2939 p->mode = MODE_NLMEANS_AUTO;
2940 gtk_widget_hide(g->box_wavelets);
2941 gtk_widget_hide(g->box_variance);
2942 gtk_widget_show_all(g->box_nlm);
2943 gtk_widget_set_visible(g->radius, FALSE);
2944 gtk_widget_set_visible(g->nbhood, FALSE);
2945 gtk_widget_set_visible(g->scattering, FALSE);
2946 break;
2947 case 2:
2948 p->mode = MODE_WAVELETS;
2949 gtk_widget_hide(g->box_nlm);
2950 gtk_widget_hide(g->box_variance);
2951 gtk_widget_show_all(g->box_wavelets);
2952 gtk_widget_set_visible(GTK_WIDGET(g->wavelet_color_mode), p->use_new_vst);
2953 gtk_widget_set_visible(GTK_WIDGET(g->channel_tabs), p->use_new_vst && (p->wavelet_color_mode == MODE_RGB));
2954 gtk_widget_set_visible(GTK_WIDGET(g->channel_tabs_Y0U0V0), p->use_new_vst && (p->wavelet_color_mode == MODE_Y0U0V0));
2955 break;
2956 case 3:
2957 p->mode = MODE_WAVELETS_AUTO;
2958 gtk_widget_hide(g->box_nlm);
2959 gtk_widget_hide(g->box_variance);
2960 gtk_widget_show_all(g->box_wavelets);
2961 gtk_widget_set_visible(GTK_WIDGET(g->wavelet_color_mode), p->use_new_vst);
2962 gtk_widget_set_visible(GTK_WIDGET(g->channel_tabs), p->use_new_vst && (p->wavelet_color_mode == MODE_RGB));
2963 gtk_widget_set_visible(GTK_WIDGET(g->channel_tabs_Y0U0V0), p->use_new_vst && (p->wavelet_color_mode == MODE_Y0U0V0));
2964 break;
2965 case 4:
2966 p->mode = MODE_VARIANCE;
2967 gtk_widget_hide(g->box_wavelets);
2968 gtk_widget_hide(g->box_nlm);
2969 gtk_widget_show_all(g->box_variance);
2970 break;
2971 }
2972 const gboolean auto_mode = (p->mode == MODE_NLMEANS_AUTO) || (p->mode == MODE_WAVELETS_AUTO);
2973 gtk_widget_set_visible(g->shadows, p->use_new_vst && !auto_mode);
2974 gtk_widget_set_visible(g->bias, p->use_new_vst && !auto_mode);
2975 gtk_widget_set_visible(g->overshooting, auto_mode);
2976 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2977}
2978
2979void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
2980{
2983
2984 if(w == g->wavelet_color_mode)
2985 {
2986 gtk_widget_set_visible(GTK_WIDGET(g->channel_tabs), (p->wavelet_color_mode == MODE_RGB));
2987 gtk_widget_set_visible(GTK_WIDGET(g->channel_tabs_Y0U0V0), (p->wavelet_color_mode == MODE_Y0U0V0));
2988 if(p->wavelet_color_mode == MODE_RGB)
2989 g->channel = DT_DENOISE_PROFILE_ALL;
2990 else
2991 g->channel = DT_DENOISE_PROFILE_Y0;
2992 }
2993 else if(w == g->overshooting)
2994 {
2995 const float gain = p->overshooting;
2996 float a = p->a[1];
2997 if(p->a[0] == -1.0)
2998 {
3000 a = interpolated.a[1];
3001 }
3002 // set the sliders as visible while we are setting their values
3003 // otherwise a log message appears
3004 if(p->mode == MODE_NLMEANS_AUTO)
3005 {
3006 gtk_widget_set_visible(g->radius, TRUE);
3007 gtk_widget_set_visible(g->scattering, TRUE);
3010 gtk_widget_set_visible(g->radius, FALSE);
3011 gtk_widget_set_visible(g->scattering, FALSE);
3012 }
3013 else
3014 {
3015 // we are in wavelets mode.
3016 // we need to show the box_nlm, setting the sliders to visible is not enough
3017 gtk_widget_show_all(g->box_nlm);
3020 gtk_widget_hide(g->box_nlm);
3021 }
3022 gtk_widget_set_visible(g->shadows, TRUE);
3023 gtk_widget_set_visible(g->bias, TRUE);
3026 gtk_widget_set_visible(g->shadows, FALSE);
3027 gtk_widget_set_visible(g->bias, FALSE);
3028 }
3029 else if(w == g->use_new_vst)
3030 {
3031 const gboolean auto_mode = (p->mode == MODE_NLMEANS_AUTO) || (p->mode == MODE_WAVELETS_AUTO);
3032 gtk_widget_set_visible(g->shadows, p->use_new_vst && !auto_mode);
3033 gtk_widget_set_visible(g->bias, p->use_new_vst && !auto_mode);
3034 gtk_widget_set_visible(g->wavelet_color_mode, p->use_new_vst);
3035 if(!p->use_new_vst && p->wavelet_color_mode == MODE_Y0U0V0)
3036 p->wavelet_color_mode = MODE_RGB;
3037 }
3038}
3039
3041{
3044
3045 // Matching noise profiles + the interpolated default profile and the profile combobox, formerly
3046 // built in reload_defaults(). They need live widgets and feed the profile picker below, so they
3047 // belong here on the GUI thread. Keep g->interpolated in sync (used by the profile callback).
3048 dt_bauhaus_combobox_clear(g->profile);
3049 char name[512];
3050 if(g->profiles)
3051 {
3052 g_list_free_full(g->profiles, dt_noiseprofile_free);
3053 g->profiles = NULL;
3054 }
3055 g->profiles = dt_noiseprofile_get_matching(&self->dev->image_storage);
3056 g->interpolated = dt_noiseprofile_generic; // default to generic poissonian
3057 g_strlcpy(name, _(g->interpolated.name), sizeof(name));
3058
3059 const int iso = self->dev->image_storage.exif_iso;
3060 dt_noiseprofile_t *last = NULL;
3061 for(GList *iter = g->profiles; iter; iter = g_list_next(iter))
3062 {
3063 dt_noiseprofile_t *current = (dt_noiseprofile_t *)iter->data;
3064 if(current->iso == iso)
3065 {
3066 g->interpolated = *current;
3067 g->interpolated.a[0] = -1.0f; // signal later autodetection in commit_params
3068 snprintf(name, sizeof(name), _("found match for ISO %d"), iso);
3069 break;
3070 }
3071 if(last && last->iso < iso && current->iso > iso)
3072 {
3073 g->interpolated.iso = iso;
3074 dt_noiseprofile_interpolate(last, current, &g->interpolated);
3075 g->interpolated.a[0] = -1.0f;
3076 snprintf(name, sizeof(name), _("interpolated from ISO %d and %d"), last->iso, current->iso);
3077 break;
3078 }
3079 last = current;
3080 }
3081 dt_bauhaus_combobox_add(g->profile, name);
3082 for(GList *iter = g->profiles; iter; iter = g_list_next(iter))
3083 {
3084 dt_noiseprofile_t *profile = (dt_noiseprofile_t *)iter->data;
3085 dt_bauhaus_combobox_add(g->profile, profile->name);
3086 }
3087
3088 // slider defaults from the (image-dependent) default params computed in reload_defaults()
3090 dt_bauhaus_slider_set_default(g->radius, d->radius);
3091 dt_bauhaus_slider_set_default(g->scattering, d->scattering);
3092 dt_bauhaus_slider_set_default(g->shadows, d->shadows);
3093 dt_bauhaus_slider_set_default(g->bias, d->bias);
3094
3095 dt_bauhaus_combobox_set(g->profile, -1);
3096 unsigned combobox_index = 0;
3097 switch (p->mode)
3098 {
3099 case MODE_NLMEANS:
3100 combobox_index = 0;
3101 gtk_widget_hide(g->box_wavelets);
3102 gtk_widget_hide(g->box_variance);
3103 gtk_widget_show_all(g->box_nlm);
3104 break;
3105 case MODE_NLMEANS_AUTO:
3106 combobox_index = 1;
3107 gtk_widget_hide(g->box_wavelets);
3108 gtk_widget_hide(g->box_variance);
3109 gtk_widget_show_all(g->box_nlm);
3110 gtk_widget_set_visible(g->radius, FALSE);
3111 gtk_widget_set_visible(g->nbhood, FALSE);
3112 gtk_widget_set_visible(g->scattering, FALSE);
3113 break;
3114 case MODE_WAVELETS:
3115 combobox_index = 2;
3116 gtk_widget_hide(g->box_nlm);
3117 gtk_widget_hide(g->box_variance);
3118 gtk_widget_show_all(g->box_wavelets);
3119 break;
3120 case MODE_WAVELETS_AUTO:
3121 combobox_index = 3;
3122 gtk_widget_hide(g->box_nlm);
3123 gtk_widget_hide(g->box_variance);
3124 gtk_widget_show_all(g->box_wavelets);
3125 break;
3126 case MODE_VARIANCE:
3127 combobox_index = 4;
3128 gtk_widget_hide(g->box_wavelets);
3129 gtk_widget_hide(g->box_nlm);
3130 gtk_widget_show_all(g->box_variance);
3131 if(dt_bauhaus_combobox_length(g->mode) == 4)
3132 {
3133 dt_bauhaus_combobox_add(g->mode, _("compute variance"));
3134 }
3135 break;
3136 }
3137 float a = p->a[1];
3138 if(p->a[0] == -1.0)
3139 {
3141 a = interpolated.a[1];
3142 }
3143 if((p->mode == MODE_NLMEANS_AUTO) || (p->mode == MODE_WAVELETS_AUTO))
3144 {
3145 const float gain = p->overshooting;
3150 }
3151 dt_bauhaus_combobox_set(g->mode, combobox_index);
3152 if(p->a[0] == -1.0)
3153 {
3154 dt_bauhaus_combobox_set(g->profile, 0);
3155 }
3156 else
3157 {
3158 int i = 1;
3159 for(GList *iter = g->profiles; iter; iter = g_list_next(iter), i++)
3160 {
3161 dt_noiseprofile_t *profile = (dt_noiseprofile_t *)iter->data;
3162 if(!memcmp(profile->a, p->a, sizeof(float) * 3)
3163 && !memcmp(profile->b, p->b, sizeof(float) * 3))
3164 {
3165 dt_bauhaus_combobox_set(g->profile, i);
3166 break;
3167 }
3168 }
3169 }
3170 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->wb_adaptive_anscombe), p->wb_adaptive_anscombe);
3171 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->fix_anscombe_and_nlmeans_norm), p->fix_anscombe_and_nlmeans_norm);
3172 gtk_widget_set_visible(g->fix_anscombe_and_nlmeans_norm, !p->fix_anscombe_and_nlmeans_norm);
3173 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->use_new_vst), p->use_new_vst);
3174 gtk_widget_set_visible(g->use_new_vst, !p->use_new_vst);
3175 const gboolean auto_mode = (p->mode == MODE_NLMEANS_AUTO) || (p->mode == MODE_WAVELETS_AUTO);
3176 const gboolean wavelet_mode = (p->mode == MODE_WAVELETS) || (p->mode == MODE_WAVELETS_AUTO);
3177 gtk_widget_set_visible(g->overshooting, auto_mode);
3178 gtk_widget_set_visible(g->wavelet_color_mode, p->use_new_vst && wavelet_mode);
3179 gtk_widget_set_visible(g->shadows, p->use_new_vst && !auto_mode);
3180 gtk_widget_set_visible(g->bias, p->use_new_vst && !auto_mode);
3181 gtk_widget_set_visible(GTK_WIDGET(g->channel_tabs), (p->wavelet_color_mode == MODE_RGB));
3182 gtk_widget_set_visible(GTK_WIDGET(g->channel_tabs_Y0U0V0), (p->wavelet_color_mode == MODE_Y0U0V0));
3183 if((p->wavelet_color_mode == MODE_Y0U0V0) && (g->channel < DT_DENOISE_PROFILE_Y0))
3184 {
3185 g->channel = DT_DENOISE_PROFILE_Y0;
3186 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->channel_tabs_Y0U0V0), g->channel - DT_DENOISE_PROFILE_Y0);
3187 }
3188 if((p->wavelet_color_mode == MODE_RGB) && (g->channel > DT_DENOISE_PROFILE_B))
3189 {
3190 g->channel = DT_DENOISE_PROFILE_ALL;
3191 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->channel_tabs), g->channel);
3192 }
3193}
3194
3196{
3199 if(p->wavelet_color_mode == MODE_Y0U0V0)
3200 {
3201 g->channel = DT_DENOISE_PROFILE_Y0;
3202 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->channel_tabs_Y0U0V0), g->channel - DT_DENOISE_PROFILE_Y0);
3203 }
3204 else
3205 {
3206 g->channel = DT_DENOISE_PROFILE_ALL;
3207 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->channel_tabs), g->channel);
3208 }
3209 gtk_widget_set_visible(g->fix_anscombe_and_nlmeans_norm, !p->fix_anscombe_and_nlmeans_norm);
3210 gtk_widget_set_visible(g->use_new_vst, !p->use_new_vst);
3211}
3212
3213static void dt_iop_denoiseprofile_get_params(dt_iop_denoiseprofile_params_t *p, const int ch, const double mouse_x,
3214 const double mouse_y, const float rad)
3215{
3216 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
3217 {
3218 const float f = expf(-(mouse_x - p->x[ch][k]) * (mouse_x - p->x[ch][k]) / (rad * rad));
3219 p->y[ch][k] = (1 - f) * p->y[ch][k] + f * mouse_y;
3220 }
3221}
3222
3223static gboolean denoiseprofile_draw_variance(GtkWidget *widget, cairo_t *crf, gpointer user_data)
3224{
3225 if(dt_gui_widgets_suppressed()) return FALSE;
3226
3227 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3229
3230 if(!isnan(c->variance_R))
3231 {
3232 gchar *str = g_strdup_printf("%.2f", c->variance_R);
3234 gtk_label_set_text(c->label_var_R, str);
3236 dt_free(str);
3237 }
3238 if(!isnan(c->variance_G))
3239 {
3240 gchar *str = g_strdup_printf("%.2f", c->variance_G);
3242 gtk_label_set_text(c->label_var_G, str);
3244 dt_free(str);
3245 }
3246 if(!isnan(c->variance_B))
3247 {
3248 gchar *str = g_strdup_printf("%.2f", c->variance_B);
3250 gtk_label_set_text(c->label_var_B, str);
3252 dt_free(str);
3253 }
3254 return FALSE;
3255}
3256
3257static gboolean denoiseprofile_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
3258{
3259 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3262
3263 int ch = (int)c->channel;
3264 dt_draw_curve_set_point(c->transition_curve, 0, p.x[ch][DT_IOP_DENOISE_PROFILE_BANDS - 2] - 1.f, p.y[ch][0]);
3265 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
3266 dt_draw_curve_set_point(c->transition_curve, k + 1, p.x[ch][k], p.y[ch][k]);
3267 dt_draw_curve_set_point(c->transition_curve, DT_IOP_DENOISE_PROFILE_BANDS + 1, p.x[ch][1] + 1.f,
3269
3270 const int inset = DT_IOP_DENOISE_PROFILE_INSET;
3271 GtkAllocation allocation;
3272 gtk_widget_get_allocation(widget, &allocation);
3273 int width = allocation.width, height = allocation.height;
3274 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
3275 cairo_t *cr = cairo_create(cst);
3276 cairo_set_source_rgb(cr, .2, .2, .2);
3277
3278 cairo_paint(cr);
3279
3280 cairo_translate(cr, inset, inset);
3281 width -= 2 * inset;
3282 height -= 2 * inset;
3283
3284 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
3285 cairo_set_source_rgb(cr, .1, .1, .1);
3286 cairo_rectangle(cr, 0, 0, width, height);
3287 cairo_stroke(cr);
3288
3289 cairo_set_source_rgb(cr, .3, .3, .3);
3290 cairo_rectangle(cr, 0, 0, width, height);
3291 cairo_fill(cr);
3292
3293 // draw grid
3294 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
3295 cairo_set_source_rgb(cr, .1, .1, .1);
3296 dt_draw_grid(cr, 8, 0, 0, width, height);
3297
3298 if(c->mouse_y > 0 || c->dragging)
3299 {
3300 // draw min/max curves:
3301 dt_iop_denoiseprofile_get_params(&p, c->channel, c->mouse_x, 1., c->mouse_radius);
3302 dt_draw_curve_set_point(c->transition_curve, 0, p.x[ch][DT_IOP_DENOISE_PROFILE_BANDS - 2] - 1.f, p.y[ch][0]);
3303 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
3304 dt_draw_curve_set_point(c->transition_curve, k + 1, p.x[ch][k], p.y[ch][k]);
3305 dt_draw_curve_set_point(c->transition_curve, DT_IOP_DENOISE_PROFILE_BANDS + 1, p.x[ch][1] + 1.f,
3307 dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_DENOISE_PROFILE_RES, c->draw_min_xs,
3308 c->draw_min_ys);
3309
3311 dt_iop_denoiseprofile_get_params(&p, c->channel, c->mouse_x, .0, c->mouse_radius);
3312 dt_draw_curve_set_point(c->transition_curve, 0, p.x[ch][DT_IOP_DENOISE_PROFILE_BANDS - 2] - 1.f, p.y[ch][0]);
3313 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
3314 dt_draw_curve_set_point(c->transition_curve, k + 1, p.x[ch][k], p.y[ch][k]);
3315 dt_draw_curve_set_point(c->transition_curve, DT_IOP_DENOISE_PROFILE_BANDS + 1, p.x[ch][1] + 1.f,
3317 dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_DENOISE_PROFILE_RES, c->draw_max_xs,
3318 c->draw_max_ys);
3319 }
3320
3321 cairo_save(cr);
3322
3323 // draw selected cursor
3324 cairo_translate(cr, 0, height);
3325
3326 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
3327 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
3328
3329 for(int i = 0; i < DT_DENOISE_PROFILE_NONE; i++)
3330 {
3331 // draw curves, selected last
3332 ch = ((int)c->channel + i + 1) % DT_DENOISE_PROFILE_NONE;
3333 float alpha = 0.3;
3334 if(i == DT_DENOISE_PROFILE_NONE - 1) alpha = 1.0;
3335 if(p.wavelet_color_mode == MODE_RGB)
3336 {
3337 switch(ch)
3338 {
3340 cairo_set_source_rgba(cr, .7, .7, .7, alpha);
3341 break;
3343 cairo_set_source_rgba(cr, .7, .1, .1, alpha);
3344 break;
3346 cairo_set_source_rgba(cr, .1, .7, .1, alpha);
3347 break;
3349 cairo_set_source_rgba(cr, .1, .1, .7, alpha);
3350 break;
3351 default:
3352 cairo_set_source_rgba(cr, 7, .7, .7, 0.0f);
3353 break;
3354 }
3355 }
3356 else
3357 {
3358 switch(ch)
3359 {
3361 cairo_set_source_rgba(cr, .7, .7, .7, alpha);
3362 break;
3364 cairo_set_source_rgba(cr, .8, .4, .0, alpha);
3365 break;
3366 default:
3367 cairo_set_source_rgba(cr, .7, .7, .7, 0.0f);
3368 break;
3369 }
3370 }
3371
3373 dt_draw_curve_set_point(c->transition_curve, 0, p.x[ch][DT_IOP_DENOISE_PROFILE_BANDS - 2] - 1.0f, p.y[ch][0]);
3374 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
3375 dt_draw_curve_set_point(c->transition_curve, k + 1, p.x[ch][k], p.y[ch][k]);
3376 dt_draw_curve_set_point(c->transition_curve, DT_IOP_DENOISE_PROFILE_BANDS + 1, p.x[ch][1] + 1.0f,
3378 dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_DENOISE_PROFILE_RES, c->draw_xs, c->draw_ys);
3379 cairo_move_to(cr, 0 * width / (float)(DT_IOP_DENOISE_PROFILE_RES - 1), -height * c->draw_ys[0]);
3380 for(int k = 1; k < DT_IOP_DENOISE_PROFILE_RES; k++)
3381 cairo_line_to(cr, k * width / (float)(DT_IOP_DENOISE_PROFILE_RES - 1), -height * c->draw_ys[k]);
3382 cairo_stroke(cr);
3383 }
3384
3385 ch = c->channel;
3386 // draw dots on knots
3387 cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
3388 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
3389 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
3390 {
3391 cairo_arc(cr, width * p.x[ch][k], -height * p.y[ch][k], DT_PIXEL_APPLY_DPI(3.0), 0.0, 2.0 * M_PI);
3392 if(c->x_move == k)
3393 cairo_fill(cr);
3394 else
3395 cairo_stroke(cr);
3396 }
3397
3398 if(c->mouse_y > 0 || c->dragging)
3399 {
3400 // draw min/max, if selected
3401 cairo_set_source_rgba(cr, .7, .7, .7, .6);
3402 cairo_move_to(cr, 0, -height * c->draw_min_ys[0]);
3403 for(int k = 1; k < DT_IOP_DENOISE_PROFILE_RES; k++)
3404 cairo_line_to(cr, k * width / (float)(DT_IOP_DENOISE_PROFILE_RES - 1), -height * c->draw_min_ys[k]);
3405 for(int k = DT_IOP_DENOISE_PROFILE_RES - 1; k >= 0; k--)
3406 cairo_line_to(cr, k * width / (float)(DT_IOP_DENOISE_PROFILE_RES - 1), -height * c->draw_max_ys[k]);
3407 cairo_close_path(cr);
3408 cairo_fill(cr);
3409 // draw mouse focus circle
3410 cairo_set_source_rgba(cr, .9, .9, .9, .5);
3411 const float pos = DT_IOP_DENOISE_PROFILE_RES * c->mouse_x;
3412 int k = (int)pos;
3413 const float f = k - pos;
3415 float ht = -height * (f * c->draw_ys[k] + (1 - f) * c->draw_ys[k + 1]);
3416 cairo_arc(cr, c->mouse_x * width, ht, c->mouse_radius * width, 0, 2. * M_PI);
3417 cairo_stroke(cr);
3418 }
3419
3420 cairo_restore(cr);
3421
3422 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
3423
3424 // draw labels:
3425 PangoLayout *layout;
3426 PangoRectangle ink;
3427 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
3428 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
3429 pango_font_description_set_absolute_size(desc, (.08 * height) * PANGO_SCALE);
3430 layout = pango_cairo_create_layout(cr);
3431 pango_layout_set_font_description(layout, desc);
3432 cairo_set_source_rgb(cr, .1, .1, .1);
3433
3434 pango_layout_set_text(layout, _("coarse"), -1);
3435 pango_layout_get_pixel_extents(layout, &ink, NULL);
3436 cairo_move_to(cr, .02 * width - ink.y, .5 * (height + ink.width));
3437 cairo_save(cr);
3438 cairo_rotate(cr, -M_PI * .5f);
3439 pango_cairo_show_layout(cr, layout);
3440 cairo_restore(cr);
3441
3442 pango_layout_set_text(layout, _("fine"), -1);
3443 pango_layout_get_pixel_extents(layout, &ink, NULL);
3444 cairo_move_to(cr, .98 * width - ink.height, .5 * (height + ink.width));
3445 cairo_save(cr);
3446 cairo_rotate(cr, -M_PI * .5f);
3447 pango_cairo_show_layout(cr, layout);
3448 cairo_restore(cr);
3449
3450
3451 pango_layout_set_text(layout, _("smooth"), -1);
3452 pango_layout_get_pixel_extents(layout, &ink, NULL);
3453 cairo_move_to(cr, .5 * (width - ink.width), .08 * height - ink.height);
3454 pango_cairo_show_layout(cr, layout);
3455
3456 pango_layout_set_text(layout, _("noisy"), -1);
3457 pango_layout_get_pixel_extents(layout, &ink, NULL);
3458 cairo_move_to(cr, .5 * (width - ink.width), .97 * height - ink.height);
3459 pango_cairo_show_layout(cr, layout);
3460
3461 pango_font_description_free(desc);
3462 g_object_unref(layout);
3463 cairo_destroy(cr);
3464 cairo_set_source_surface(crf, cst, 0, 0);
3465 cairo_paint(crf);
3466 cairo_surface_destroy(cst);
3467 return TRUE;
3468}
3469
3470static gboolean denoiseprofile_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
3471{
3472 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3475 const int inset = DT_IOP_DENOISE_PROFILE_INSET;
3476 GtkAllocation allocation;
3477 gtk_widget_get_allocation(widget, &allocation);
3478 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
3479 if(!c->dragging) c->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
3480 c->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
3481 if(c->dragging)
3482 {
3483 *p = c->drag_params;
3484 if(c->x_move < 0)
3485 {
3486 dt_iop_denoiseprofile_get_params(p, c->channel, c->mouse_x, c->mouse_y + c->mouse_pick, c->mouse_radius);
3487 }
3488 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
3489 }
3490 else
3491 {
3492 c->x_move = -1;
3493 }
3494 gtk_widget_queue_draw(widget);
3495 return TRUE;
3496}
3497
3498static gboolean denoiseprofile_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3499{
3500 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3502 const int ch = c->channel;
3503 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
3504 {
3505 // reset current curve
3508 /* dt_iop_denoiseprofile_gui_data_t *c = (dt_iop_denoiseprofile_gui_data_t *)self->gui_data; */
3509 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
3510 {
3511 p->x[ch][k] = d->x[ch][k];
3512 p->y[ch][k] = d->y[ch][k];
3513 }
3514 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
3515 gtk_widget_queue_draw(self->gui->widget);
3516 }
3517 else if(event->button == 1)
3518 {
3519 c->drag_params = *(dt_iop_denoiseprofile_params_t *)self->params;
3520 const int inset = DT_IOP_DENOISE_PROFILE_INSET;
3521 GtkAllocation allocation;
3522 gtk_widget_get_allocation(widget, &allocation);
3523 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
3524 c->mouse_pick
3525 = dt_draw_curve_calc_value(c->transition_curve, CLAMP(event->x - inset, 0, width) / (float)width);
3526 c->mouse_pick -= 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
3527 c->dragging = 1;
3528 return TRUE;
3529 }
3530 return FALSE;
3531}
3532
3533static gboolean denoiseprofile_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3534{
3535 if(event->button == 1)
3536 {
3537 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3539 c->dragging = 0;
3540 return TRUE;
3541 }
3542 return FALSE;
3543}
3544
3545static gboolean denoiseprofile_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
3546{
3547 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3549 if(!c->dragging) c->mouse_y = -1.0;
3550 gtk_widget_queue_draw(widget);
3551 return TRUE;
3552}
3553
3554static gboolean denoiseprofile_scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
3555{
3556 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3558
3559 int delta_y;
3560 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
3561 {
3562 c->mouse_radius = CLAMP(c->mouse_radius * (1.f + 0.1f * delta_y), 0.2f / DT_IOP_DENOISE_PROFILE_BANDS, 1.f);
3563 gtk_widget_queue_draw(widget);
3564 }
3565
3566 return TRUE;
3567}
3568
3569static void denoiseprofile_tab_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
3570{
3571 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3573 if(dt_gui_widgets_suppressed()) return;
3575 if(p->wavelet_color_mode == MODE_Y0U0V0)
3577 else
3578 c->channel = (dt_iop_denoiseprofile_channel_t)page_num;
3579 gtk_widget_queue_draw(self->gui->widget);
3580}
3581
3583{
3586
3587 g->profiles = NULL;
3588
3589 g->channel = 0;
3590
3591 // First build sub-level boxes
3592 g->box_nlm = self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3593
3594 g->radius = dt_bauhaus_slider_from_params(self, "radius");
3595 dt_bauhaus_slider_set_soft_range(g->radius, 0.0, 8.0);
3596 dt_bauhaus_slider_set_digits(g->radius, 0);
3597 g->nbhood = dt_bauhaus_slider_from_params(self, "nbhood");
3598 dt_bauhaus_slider_set_digits(g->nbhood, 0);
3599 g->scattering = dt_bauhaus_slider_from_params(self, "scattering");
3600 dt_bauhaus_slider_set_soft_max(g->scattering, 1.0f);
3601 g->central_pixel_weight = dt_bauhaus_slider_from_params(self, "central_pixel_weight");
3602 dt_bauhaus_slider_set_soft_max(g->central_pixel_weight, 1.0f);
3603
3604 g->box_wavelets = self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3605
3606 g->wavelet_color_mode = dt_bauhaus_combobox_from_params(self, "wavelet_color_mode");
3607
3608 g->channel_tabs = GTK_NOTEBOOK(gtk_notebook_new());
3609 dt_ui_notebook_page(g->channel_tabs, N_("all"), NULL);
3610 dt_ui_notebook_page(g->channel_tabs, N_("R"), NULL);
3611 dt_ui_notebook_page(g->channel_tabs, N_("G"), NULL);
3612 dt_ui_notebook_page(g->channel_tabs, N_("B"), NULL);
3613 g_signal_connect(G_OBJECT(g->channel_tabs), "switch_page", G_CALLBACK(denoiseprofile_tab_switch), self);
3614 gtk_box_pack_start(GTK_BOX(g->box_wavelets), GTK_WIDGET(g->channel_tabs), FALSE, FALSE, 0);
3615
3616 g->channel_tabs_Y0U0V0 = GTK_NOTEBOOK(gtk_notebook_new());
3617 dt_ui_notebook_page(g->channel_tabs_Y0U0V0, N_("Y0"), NULL);
3618 dt_ui_notebook_page(g->channel_tabs_Y0U0V0, N_("U0V0"), NULL);
3619 g_signal_connect(G_OBJECT(g->channel_tabs_Y0U0V0), "switch_page", G_CALLBACK(denoiseprofile_tab_switch), self);
3620 gtk_box_pack_start(GTK_BOX(g->box_wavelets), GTK_WIDGET(g->channel_tabs_Y0U0V0), FALSE, FALSE, 0);
3621
3622 const int ch = (int)g->channel;
3623 g->transition_curve = dt_draw_curve_new(0.0, 1.0, CATMULL_ROM);
3624 (void)dt_draw_curve_add_point(g->transition_curve, p->x[ch][DT_IOP_DENOISE_PROFILE_BANDS - 2] - 1.0f,
3626 for(int k = 0; k < DT_IOP_DENOISE_PROFILE_BANDS; k++)
3627 (void)dt_draw_curve_add_point(g->transition_curve, p->x[ch][k], p->y[ch][k]);
3628 (void)dt_draw_curve_add_point(g->transition_curve, p->x[ch][1] + 1.0f, p->y[ch][1]);
3629
3630 g->mouse_x = g->mouse_y = g->mouse_pick = -1.0;
3631 g->dragging = 0;
3632 g->x_move = -1;
3633 g->mouse_radius = 1.0f / (DT_IOP_DENOISE_PROFILE_BANDS * 2);
3634
3635 g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
3636 gtk_widget_set_hexpand(GTK_WIDGET(g->area), TRUE);
3637
3638 gtk_widget_add_events(GTK_WIDGET(g->area), GDK_POINTER_MOTION_MASK
3639 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
3640 | GDK_LEAVE_NOTIFY_MASK | dt_widget_scroll_mask());
3641 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(denoiseprofile_draw), self);
3642 g_signal_connect(G_OBJECT(g->area), "button-press-event", G_CALLBACK(denoiseprofile_button_press), self);
3643 g_signal_connect(G_OBJECT(g->area), "button-release-event", G_CALLBACK(denoiseprofile_button_release), self);
3644 g_signal_connect(G_OBJECT(g->area), "motion-notify-event", G_CALLBACK(denoiseprofile_motion_notify), self);
3645 g_signal_connect(G_OBJECT(g->area), "leave-notify-event", G_CALLBACK(denoiseprofile_leave_notify), self);
3646 g_signal_connect(G_OBJECT(g->area), "scroll-event", G_CALLBACK(denoiseprofile_scrolled), self);
3647 gtk_box_pack_start(GTK_BOX(g->box_wavelets),
3648 dt_ui_resizable_drawing_area(GTK_WIDGET(g->area),
3649 "plugins/darkroom/denoiseprofile/graphheight", 280, 100),
3650 FALSE, FALSE, 0);
3651
3652 g->box_variance = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3653
3654 g->label_var = GTK_LABEL(dt_ui_label_new(_("use only with a perfectly\n"
3655 "uniform image if you want to\n"
3656 "estimate the noise variance.")));
3657 gtk_box_pack_start(GTK_BOX(g->box_variance), GTK_WIDGET(g->label_var), TRUE, TRUE, 0);
3658
3659 GtkBox *hboxR = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
3660 GtkLabel *labelR = GTK_LABEL(dt_ui_label_new(_("variance red: ")));
3661 gtk_box_pack_start(GTK_BOX(hboxR), GTK_WIDGET(labelR), FALSE, FALSE, 0);
3662 g->label_var_R = GTK_LABEL(dt_ui_label_new("")); // This gets filled in by process
3663 gtk_widget_set_tooltip_text(GTK_WIDGET(g->label_var_R), _("variance computed on the red channel"));
3664 gtk_box_pack_start(GTK_BOX(hboxR), GTK_WIDGET(g->label_var_R), FALSE, FALSE, 0);
3665 gtk_box_pack_start(GTK_BOX(g->box_variance), GTK_WIDGET(hboxR), TRUE, TRUE, 0);
3666
3667 GtkBox *hboxG = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
3668 GtkLabel *labelG = GTK_LABEL(dt_ui_label_new(_("variance green: ")));
3669 gtk_box_pack_start(GTK_BOX(hboxG), GTK_WIDGET(labelG), FALSE, FALSE, 0);
3670 g->label_var_G = GTK_LABEL(dt_ui_label_new("")); // This gets filled in by process
3671 gtk_widget_set_tooltip_text(GTK_WIDGET(g->label_var_G), _("variance computed on the green channel"));
3672 gtk_box_pack_start(GTK_BOX(hboxG), GTK_WIDGET(g->label_var_G), FALSE, FALSE, 0);
3673 gtk_box_pack_start(GTK_BOX(g->box_variance), GTK_WIDGET(hboxG), TRUE, TRUE, 0);
3674
3675 GtkBox *hboxB = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
3676 GtkLabel *labelB = GTK_LABEL(dt_ui_label_new(_("variance blue: ")));
3677 gtk_box_pack_start(GTK_BOX(hboxB), GTK_WIDGET(labelB), FALSE, FALSE, 0);
3678 g->label_var_B = GTK_LABEL(dt_ui_label_new("")); // This gets filled in by process
3679 gtk_widget_set_tooltip_text(GTK_WIDGET(g->label_var_B), _("variance computed on the blue channel"));
3680 gtk_box_pack_start(GTK_BOX(hboxB), GTK_WIDGET(g->label_var_B), FALSE, FALSE, 0);
3681 gtk_box_pack_start(GTK_BOX(g->box_variance), GTK_WIDGET(hboxB), TRUE, TRUE, 0);
3682
3683 g_signal_connect(G_OBJECT(g->box_variance), "draw", G_CALLBACK(denoiseprofile_draw_variance), self);
3684
3685 // start building top level widget
3686 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3687
3689 dt_bauhaus_widget_set_label(g->profile, N_("profile"));
3690 g_signal_connect(G_OBJECT(g->profile), "value-changed", G_CALLBACK(profile_callback), self);
3691 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->profile, TRUE, TRUE, 0);
3692
3693 g->wb_adaptive_anscombe = dt_bauhaus_toggle_from_params(self, "wb_adaptive_anscombe");
3694
3696 dt_bauhaus_widget_set_label(g->mode, N_("mode"));
3697 dt_bauhaus_combobox_add(g->mode, _("non-local means"));
3698 dt_bauhaus_combobox_add(g->mode, _("non-local means auto"));
3699 dt_bauhaus_combobox_add(g->mode, _("wavelets"));
3700 dt_bauhaus_combobox_add(g->mode, _("wavelets auto"));
3701 const gboolean compute_variance = dt_conf_get_bool("plugins/darkroom/denoiseprofile/show_compute_variance_mode");
3702 if(compute_variance) dt_bauhaus_combobox_add(g->mode, _("compute variance"));
3703 g_signal_connect(G_OBJECT(g->mode), "value-changed", G_CALLBACK(mode_callback), self);
3704 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->mode, TRUE, TRUE, 0);
3705
3706 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->box_nlm, TRUE, TRUE, 0);
3707 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->box_wavelets, TRUE, TRUE, 0);
3708
3709 g->overshooting = dt_bauhaus_slider_from_params(self, "overshooting");
3710 g->strength = dt_bauhaus_slider_from_params(self, N_("strength"));
3711 dt_bauhaus_slider_set_soft_max(g->strength, 4.0f);
3712 dt_bauhaus_slider_set_digits(g->strength, 3);
3713 g->shadows = dt_bauhaus_slider_from_params(self, "shadows");
3714 g->bias = dt_bauhaus_slider_from_params(self, "bias");
3715 dt_bauhaus_slider_set_soft_range(g->bias, -10.0f, 10.0f);
3716
3717 // Set the overshooting soft range only once the sliders that its
3718 // value-changed handler (gui_changed) touches exist: changing a slider's
3719 // soft bound re-applies the value and emits "value-changed", which calls
3720 // gui_changed(overshooting) -> dt_bauhaus_slider_set(g->shadows / g->bias).
3721 // Those are created just above, so doing this earlier dereferenced NULL.
3722 dt_bauhaus_slider_set_soft_max(g->overshooting, 4.0f);
3723
3724 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->box_variance, TRUE, TRUE, 0);
3725
3726 g->fix_anscombe_and_nlmeans_norm = dt_bauhaus_toggle_from_params(self, "fix_anscombe_and_nlmeans_norm");
3727
3728 g->use_new_vst = dt_bauhaus_toggle_from_params(self, "use_new_vst");
3729
3730 gtk_widget_set_tooltip_text(g->wb_adaptive_anscombe, _("adapt denoising according to the\n"
3731 "white balance coefficients.\n"
3732 "should be enabled on a first instance\n"
3733 "for better denoising.\n"
3734 "should be disabled if an earlier instance\n"
3735 "has been used with a color blending mode."));
3736 gtk_widget_set_tooltip_text(g->fix_anscombe_and_nlmeans_norm, _("fix bugs in anscombe transform resulting\n"
3737 "in undersmoothing of the green channel in\n"
3738 "wavelets mode, combined with a bad handling\n"
3739 "of white balance coefficients, and a bug in\n"
3740 "non local means normalization resulting in\n"
3741 "undersmoothing when patch size was increased.\n"
3742 "enabling this option will change the denoising\n"
3743 "you get. once enabled, you won't be able to\n"
3744 "return back to old algorithm."));
3745 gtk_widget_set_tooltip_text(g->profile, _("profile used for variance stabilization"));
3746 gtk_widget_set_tooltip_text(g->mode, _("method used in the denoising core.\n"
3747 "non-local means works best for `lightness' blending,\n"
3748 "wavelets work best for `color' blending"));
3749 gtk_widget_set_tooltip_text(g->wavelet_color_mode, _("color representation used within the algorithm.\n"
3750 "RGB keeps the RGB channels separated,\n"
3751 "while Y0U0V0 combine the channels to\n"
3752 "denoise chroma and luma separately."));
3753 gtk_widget_set_tooltip_text(g->radius, _("radius of the patches to match.\n"
3754 "increase for more sharpness on strong edges, and better denoising of smooth areas.\n"
3755 "if details are oversmoothed, reduce this value or increase the central pixel weight slider."));
3756 gtk_widget_set_tooltip_text(g->nbhood, _("emergency use only: radius of the neighbourhood to search patches in. "
3757 "increase for better denoising performance, but watch the long runtimes! "
3758 "large radii can be very slow. you have been warned"));
3759 gtk_widget_set_tooltip_text(g->scattering, _("scattering of the neighbourhood to search patches in.\n"
3760 "increase for better coarse-grain noise reduction.\n"
3761 "does not affect execution time."));
3762 gtk_widget_set_tooltip_text(g->central_pixel_weight, _("increase the weight of the central pixel\n"
3763 "of the patch in the patch comparison.\n"
3764 "useful to recover details when patch size\n"
3765 "is quite big."));
3766 gtk_widget_set_tooltip_text(g->strength, _("finetune denoising strength"));
3767 gtk_widget_set_tooltip_text(g->overshooting, _("controls the way parameters are autoset\n"
3768 "increase if shadows are not denoised enough\n"
3769 "or if chroma noise remains.\n"
3770 "this can happen if your picture is underexposed."));
3771 gtk_widget_set_tooltip_text(g->shadows, _("finetune shadows denoising.\n"
3772 "decrease to denoise more aggressively\n"
3773 "dark areas of the image."));
3774 gtk_widget_set_tooltip_text(g->bias, _("correct color cast in shadows.\n"
3775 "decrease if shadows are too purple.\n"
3776 "increase if shadows are too green."));
3777 gtk_widget_set_tooltip_text(g->use_new_vst, _("upgrade the variance stabilizing algorithm.\n"
3778 "new algorithm extends the current one.\n"
3779 "it is more flexible but could give small\n"
3780 "differences in the images already processed."));
3781
3782}
3783
3785{
3787 g_list_free_full(g->profiles, dt_noiseprofile_free);
3788 g->profiles = NULL;
3789 dt_draw_curve_destroy(g->transition_curve);
3790 // nothing else necessary, gtk will clean up the slider.
3791
3793}
3794
3795// clang-format off
3796// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3797// vim: shiftwidth=2 expandtab tabstop=2 cindent
3798// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3799// clang-format on
const double thrs
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
void dt_bauhaus_slider_set_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_combobox_clear(GtkWidget *widget)
Definition bauhaus.c:1997
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1491
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1474
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:1963
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RGB
static const float x
const float f
const int t
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static dt_aligned_pixel_t rgb
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
const float delta
int dt_conf_get_bool(const char *name)
#define P(V, params)
#define CATMULL_ROM
Definition curve_tools.h:35
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
static int bucket_next(unsigned int *state, unsigned int max)
#define DT_IOP_DENOISE_PROFILE_BANDS
dt_iop_denoiseprofile_channel_t
@ DT_DENOISE_PROFILE_G
@ DT_DENOISE_PROFILE_R
@ DT_DENOISE_PROFILE_NONE
@ DT_DENOISE_PROFILE_ALL
@ DT_DENOISE_PROFILE_U0V0
@ DT_DENOISE_PROFILE_B
@ DT_DENOISE_PROFILE_Y0
void init(dt_iop_module_t *module)
#define NUM_BUCKETS
static __DT_CLONE_TARGETS__ void set_up_conversion_matrices(dt_colormatrix_t toY0U0V0, dt_colormatrix_t toRGB, const dt_aligned_pixel_t wb)
const char ** description(struct dt_iop_module_t *self)
int default_group()
static float infer_bias_from_profile(const float a)
void gui_reset(dt_iop_module_t *self)
#define DT_DENOISE_PROFILE_NONE_V9
static void profile_callback(GtkWidget *w, dt_iop_module_t *self)
void reload_defaults(dt_iop_module_t *module)
static __DT_CLONE_TARGETS__ void compute_wb_factors(dt_aligned_pixel_t wb, const dt_iop_denoiseprofile_data_t *const d, const dt_dev_pixelpipe_iop_t *const piece, const dt_aligned_pixel_t weights)
static __DT_CLONE_TARGETS__ void backtransform_v2(float *const buf, const int wd, const int ht, const float a, const dt_aligned_pixel_t p, const float b, const float bias, const dt_aligned_pixel_t wb)
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)
static int process_nlmeans_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, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
static void dt_iop_denoiseprofile_get_params(dt_iop_denoiseprofile_params_t *p, const int ch, const double mouse_x, const double mouse_y, const float rad)
#define REDUCESIZE
void gui_update(dt_iop_module_t *self)
static __DT_CLONE_TARGETS__ void backtransform(float *const buf, const int wd, const int ht, const dt_aligned_pixel_t a, const dt_aligned_pixel_t b)
static int process_wavelets_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, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static dt_noiseprofile_t dt_iop_denoiseprofile_get_auto_profile(dt_iop_module_t *self)
#define debug_dump_PFM(p, n, b, w, h, s)
static gboolean denoiseprofile_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
const char * name()
#define DT_IOP_DENOISE_PROFILE_INSET
#define MAX_MAX_SCALE
static float nlmeans_precondition_cl(const dt_iop_denoiseprofile_data_t *const d, const dt_dev_pixelpipe_iop_t *const piece, dt_aligned_pixel_t wb, float scale, dt_aligned_pixel_t aa, dt_aligned_pixel_t bb, dt_aligned_pixel_t p)
void gui_init(dt_iop_module_t *self)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
#define DT_IOP_DENOISE_PROFILE_RES
dt_iop_denoiseprofile_wavelet_mode_t
@ MODE_RGB
@ MODE_Y0U0V0
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)
static __DT_CLONE_TARGETS__ void backtransform_Y0U0V0(float *const buf, const int wd, const int ht, const float a, const dt_aligned_pixel_t p, const float b, const float bias, const dt_aligned_pixel_t wb, const dt_colormatrix_t toRGB)
void gui_cleanup(dt_iop_module_t *self)
static gboolean denoiseprofile_draw_variance(GtkWidget *widget, cairo_t *crf, gpointer user_data)
static float infer_scattering_from_profile(const float a)
void cleanup_global(dt_iop_module_so_t *module)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
static float infer_shadows_from_profile(const float a)
static __DT_CLONE_TARGETS__ int process_wavelets(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const eaw_dn_decompose_t decompose, const eaw_synthesize_t synthesize)
#define DT_IOP_DENOISE_PROFILE_V8_BANDS
int flags()
static gboolean denoiseprofile_scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
static void denoiseprofile_tab_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
dt_iop_denoiseprofile_mode_t
@ MODE_WAVELETS
@ MODE_NLMEANS_AUTO
@ MODE_WAVELETS_AUTO
@ MODE_NLMEANS
@ MODE_VARIANCE
#define DT_IOP_DENOISE_PROFILE_P_FULCRUM
static __DT_CLONE_TARGETS__ void precondition_v2(const float *const in, float *const buf, const int wd, const int ht, const float a, const dt_aligned_pixel_t p, const float b, const dt_aligned_pixel_t wb)
void init_presets(dt_iop_module_so_t *self)
static void mode_callback(GtkWidget *w, dt_iop_module_t *self)
int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
static __DT_CLONE_TARGETS__ void precondition_Y0U0V0(const float *const in, float *const buf, const int wd, const int ht, const float a, const dt_aligned_pixel_t p, const float b, const dt_colormatrix_t toY0U0V0)
static __DT_CLONE_TARGETS__ void sum_rec(const size_t npixels, const float *in, float *out)
static __DT_CLONE_TARGETS__ void variance_rec(const size_t npixels, const float *in, float *out, const dt_aligned_pixel_t mean)
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static gboolean denoiseprofile_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
static gboolean denoiseprofile_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
static dt_noiseprofile_t _default_noise_profile(dt_iop_module_t *module)
static gboolean denoiseprofile_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
void init_global(dt_iop_module_so_t *module)
static gboolean denoiseprofile_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
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)
static __DT_CLONE_TARGETS__ void precondition(const float *const in, float *const buf, const int wd, const int ht, const dt_aligned_pixel_t a, const dt_aligned_pixel_t b)
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
static unsigned infer_radius_from_profile(const float a)
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:402
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
static void dt_draw_curve_calc_values(dt_draw_curve_t *c, const float min, const float max, const int res, float *x, float *y)
Definition draw.h:324
static void dt_draw_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:158
static float dt_draw_curve_calc_value(dt_draw_curve_t *c, const float x)
Definition draw.h:360
static void dt_draw_curve_destroy(dt_draw_curve_t *c)
Definition draw.h:297
static void dt_draw_curve_set_point(dt_draw_curve_t *c, const int num, const float x, const float y)
Definition draw.h:303
static int dt_draw_curve_add_point(dt_draw_curve_t *c, const float x, const float y)
Definition draw.h:379
static dt_draw_curve_t * dt_draw_curve_new(const float min, const float max, unsigned int type)
Definition draw.h:281
void eaw_dn_decompose(float *const restrict out, const float *const restrict in, float *const restrict detail, dt_aligned_pixel_t sum_squared, const int scale, const float inv_sigma2, const int32_t width, const int32_t height)
Definition eaw.c:242
void eaw_synthesize(float *const out, const float *const in, const float *const restrict detail, const float *const restrict threshold, const float *const restrict boost, const int32_t width, const int32_t height)
Definition eaw.c:157
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)
#define DT_GUI_MODULE(x)
int dt_iop_alloc_image_buffers(struct dt_iop_module_t *const module, const struct dt_iop_roi_t *const roi_in, const struct dt_iop_roi_t *const roi_out,...)
Definition imagebuf.c:35
void dt_iop_copy_image_roi(float *const __restrict__ out, const float *const __restrict__ in, const size_t ch, const dt_iop_roi_t *const __restrict__ roi_in, const dt_iop_roi_t *const __restrict__ roi_out, const int zero_pad)
Definition imagebuf.c:163
#define DT_IMGSZ_INPUT
Definition imagebuf.h:59
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:308
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
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_GROUP_REPAIR
Definition imageop.h:159
GtkWidget * dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
@ 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
float *const restrict const size_t const size_t ch
#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 M_PI
Definition math.h:47
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:34
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define DT_CACHELINE_BYTES
Definition mem_alloc.h:66
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.
__DT_CLONE_TARGETS__ void nlmeans_denoise(const float *const inbuf, float *const outbuf, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_nlmeans_param_t *const params)
int nlmeans_denoiseprofile_cl(const dt_nlmeans_param_t *const params, const int devid, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in)
const dt_noiseprofile_t dt_noiseprofile_generic
void dt_noiseprofile_interpolate(const dt_noiseprofile_t *const p1, const dt_noiseprofile_t *const p2, dt_noiseprofile_t *out)
void dt_noiseprofile_free(gpointer data)
GList * dt_noiseprofile_get_matching(const dt_image_t *cimg)
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition notebook.c:88
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_buffer(const int devid, const size_t size)
Definition opencl.c:2970
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_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2750
int dt_opencl_enqueue_copy_image(const int devid, cl_mem src, cl_mem dst, size_t *orig_src, size_t *orig_dst, size_t *region)
Definition opencl.c:2679
int dt_opencl_read_buffer_from_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2727
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 __OMP_SIMD__(...)
Definition openmp.h:99
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
@ DT_DEV_PIXELPIPE_THUMBNAIL
Definition pixelpipe.h:45
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define dt_pixelpipe_cache_alloc_align(size, pipe)
#define dt_pixelpipe_cache_free_align(mem)
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#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
static const dt_aligned_pixel_simd_t sign
Definition simd.h:118
const float uint32_t state[4]
const float sigma
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
dt_dev_pixelpipe_type_t type
dt_image_t image_storage
Definition develop.h:225
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
float exif_iso
Definition image.h:370
dt_aligned_pixel_t coeffs
Definition format.h:110
unsigned int channels
Definition format.h:83
dt_aligned_pixel_t processed_maximum
Definition format.h:114
struct dt_iop_buffer_dsc_t::@56 temperature
dt_draw_curve_t * curve[DT_DENOISE_PROFILE_NONE]
dt_iop_denoiseprofile_wavelet_mode_t wavelet_color_mode
dt_iop_denoiseprofile_channel_t channel
float force[DT_DENOISE_PROFILE_NONE][7]
dt_iop_denoiseprofile_mode_t mode
dt_iop_denoiseprofile_params_t drag_params
dt_iop_denoiseprofile_channel_t channel
float x[DT_DENOISE_PROFILE_NONE][7]
dt_iop_denoiseprofile_mode_t mode
float y[DT_DENOISE_PROFILE_NONE][7]
dt_iop_denoiseprofile_wavelet_mode_t wavelet_color_mode
dt_iop_denoiseprofile_mode_t mode
float x[DT_DENOISE_PROFILE_NONE][7]
float y[DT_DENOISE_PROFILE_NONE][7]
dt_iop_denoiseprofile_wavelet_mode_t wavelet_color_mode
dt_iop_denoiseprofile_mode_t mode
dt_iop_denoiseprofile_mode_t mode
dt_iop_denoiseprofile_mode_t mode
dt_iop_denoiseprofile_mode_t mode
dt_iop_denoiseprofile_mode_t mode
dt_iop_denoiseprofile_mode_t mode
dt_iop_denoiseprofile_mode_t mode
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
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:337
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
dt_aligned_pixel_t a
dt_aligned_pixel_t b
GtkWidget * notebook
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
GdkEventMask dt_widget_scroll_mask(void)
gboolean dt_gui_widgets_suppressed(void)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)