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