1 /*
2 This file is part of darktable,
3 Copyright (C) 2011-2021 darktable developers.
4
5
6 darktable is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 darktable is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with darktable. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 #include "bauhaus/bauhaus.h"
23 #include "common/darktable.h"
24 #include "common/imagebuf.h"
25 #include "common/dwt.h"
26 #include "control/control.h"
27 #include "develop/imageop.h"
28 #include "develop/imageop_math.h"
29 #include "develop/imageop_gui.h"
30 #include "develop/openmp_maths.h"
31 #include "dtgtk/drawingarea.h"
32 #include "gui/accelerators.h"
33 #include "gui/gtk.h"
34 #include "iop/iop_api.h"
35
36 #include <gtk/gtk.h>
37 #include <stdlib.h>
38 #include <strings.h>
39
40 DT_MODULE_INTROSPECTION(2, dt_iop_rawdenoise_params_t)
41
42 #define DT_IOP_RAWDENOISE_INSET DT_PIXEL_APPLY_DPI(5)
43 #define DT_IOP_RAWDENOISE_RES 64
44 #define DT_IOP_RAWDENOISE_BANDS 5
45
46 typedef enum dt_iop_rawdenoise_channel_t
47 {
48 DT_RAWDENOISE_ALL = 0,
49 DT_RAWDENOISE_R = 1,
50 DT_RAWDENOISE_G = 2,
51 DT_RAWDENOISE_B = 3,
52 DT_RAWDENOISE_NONE = 4
53 } dt_iop_rawdenoise_channel_t;
54
55 typedef struct dt_iop_rawdenoise_params_t
56 {
57 float threshold; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.01 $DESCRIPTION: "noise threshold"
58 float x[DT_RAWDENOISE_NONE][DT_IOP_RAWDENOISE_BANDS];
59 float y[DT_RAWDENOISE_NONE][DT_IOP_RAWDENOISE_BANDS]; // $DEFAULT: 0.5
60 } dt_iop_rawdenoise_params_t;
61
62 typedef struct dt_iop_rawdenoise_gui_data_t
63 {
64 dt_draw_curve_t *transition_curve; // curve for gui to draw
65
66 GtkWidget *threshold;
67 GtkDrawingArea *area;
68 GtkNotebook *channel_tabs;
69 double mouse_x, mouse_y, mouse_pick;
70 float mouse_radius;
71 dt_iop_rawdenoise_params_t drag_params;
72 int dragging;
73 int x_move;
74 dt_iop_rawdenoise_channel_t channel;
75 float draw_xs[DT_IOP_RAWDENOISE_RES], draw_ys[DT_IOP_RAWDENOISE_RES];
76 float draw_min_xs[DT_IOP_RAWDENOISE_RES], draw_min_ys[DT_IOP_RAWDENOISE_RES];
77 float draw_max_xs[DT_IOP_RAWDENOISE_RES], draw_max_ys[DT_IOP_RAWDENOISE_RES];
78 } dt_iop_rawdenoise_gui_data_t;
79
80 typedef struct dt_iop_rawdenoise_data_t
81 {
82 float threshold;
83 dt_draw_curve_t *curve[DT_RAWDENOISE_NONE];
84 dt_iop_rawdenoise_channel_t channel;
85 float force[DT_RAWDENOISE_NONE][DT_IOP_RAWDENOISE_BANDS];
86 } dt_iop_rawdenoise_data_t;
87
88 typedef struct dt_iop_rawdenoise_global_data_t
89 {
90 } dt_iop_rawdenoise_global_data_t;
91
legacy_params(dt_iop_module_t * self,const void * const old_params,const int old_version,void * new_params,const int new_version)92 int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
93 const int new_version)
94 {
95 if(old_version == 1 && new_version == 2)
96 {
97 // Since first version, the dt_iop_params_t struct have new members
98 // at the end of the struct.
99 // Yet, the beginning of the struct is exactly the same:
100 // threshold is still the first member of the struct.
101 // This allows to define the variable o with dt_iop_rawdenoise_params_t
102 // as long as we don't try to access new members on o.
103 // In other words, o can be seen as a dt_iop_rawdenoise_params_t
104 // with no allocated space for the new member.
105 dt_iop_rawdenoise_params_t *o = (dt_iop_rawdenoise_params_t *)old_params;
106 dt_iop_rawdenoise_params_t *n = (dt_iop_rawdenoise_params_t *)new_params;
107 n->threshold = o->threshold;
108 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
109 {
110 for(int ch = 0; ch < DT_RAWDENOISE_NONE; ch++)
111 {
112 n->x[ch][k] = k / (DT_IOP_RAWDENOISE_BANDS - 1.0);
113 n->y[ch][k] = 0.5f;
114 }
115 }
116 return 0;
117 }
118 return 1;
119 }
120
121
name()122 const char *name()
123 {
124 return _("raw denoise");
125 }
126
description(struct dt_iop_module_t * self)127 const char *description(struct dt_iop_module_t *self)
128 {
129 return dt_iop_set_description(self, _("denoise the raw picture early in the pipeline"),
130 _("corrective"),
131 _("linear, raw, scene-referred"),
132 _("linear, raw"),
133 _("linear, raw, scene-referred"));
134 }
135
flags()136 int flags()
137 {
138 return IOP_FLAGS_SUPPORTS_BLENDING;
139 }
140
default_group()141 int default_group()
142 {
143 return IOP_GROUP_CORRECT | IOP_GROUP_TECHNICAL;
144 }
145
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)146 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
147 {
148 return iop_cs_RAW;
149 }
150
151 #define BIT16 65536.0
152
compute_channel_noise(float * const noise,int color,const dt_iop_rawdenoise_data_t * const data)153 static void compute_channel_noise(float *const noise, int color, const dt_iop_rawdenoise_data_t *const data)
154 {
155 // note that these constants are the same for X-Trans and Bayer, as they are proportional to image detail on
156 // each channel, not the sensor pattern
157 static const float noise_all[] = { 0.8002, 0.2735, 0.1202, 0.0585, 0.0291, 0.0152, 0.0080, 0.0044 };
158 for(int i = 0; i < DT_IOP_RAWDENOISE_BANDS; i++)
159 {
160 // scale the value from [0,1] to [0,16],
161 // and makes the "0.5" neutral value become 1
162 float chan_threshold_exp_4;
163 switch(color)
164 {
165 case 0:
166 chan_threshold_exp_4 = data->force[DT_RAWDENOISE_R][DT_IOP_RAWDENOISE_BANDS - i - 1];
167 break;
168 case 2:
169 chan_threshold_exp_4 = data->force[DT_RAWDENOISE_B][DT_IOP_RAWDENOISE_BANDS - i - 1];
170 break;
171 default:
172 chan_threshold_exp_4 = data->force[DT_RAWDENOISE_G][DT_IOP_RAWDENOISE_BANDS - i - 1];
173 break;
174 }
175 chan_threshold_exp_4 *= chan_threshold_exp_4;
176 chan_threshold_exp_4 *= chan_threshold_exp_4;
177 // repeat for the overall all-channels thresholds
178 float all_threshold_exp_4 = data->force[DT_RAWDENOISE_ALL][DT_IOP_RAWDENOISE_BANDS - i - 1];
179 all_threshold_exp_4 *= all_threshold_exp_4;
180 all_threshold_exp_4 *= all_threshold_exp_4;
181 noise[i] = noise_all[i] * all_threshold_exp_4 * chan_threshold_exp_4 * 16.0f * 16.0f;
182 // the following multiplication needs to stay separate from the above line, because merging the two changes
183 // the results on the integration test!
184 noise[i] *= data->threshold;
185 }
186 }
187
wavelet_denoise(const float * const restrict in,float * const restrict out,const dt_iop_roi_t * const roi,const dt_iop_rawdenoise_data_t * const data,const uint32_t filters)188 static void wavelet_denoise(const float *const restrict in, float *const restrict out, const dt_iop_roi_t *const roi,
189 const dt_iop_rawdenoise_data_t * const data, const uint32_t filters)
190 {
191 const size_t size = (size_t)(roi->width / 2 + 1) * (roi->height / 2 + 1);
192 float *const restrict fimg = dt_alloc_align_float(size);
193 if (!fimg)
194 return;
195
196 const int nc = 4;
197 for(int c = 0; c < nc; c++) /* denoise R,G1,B,G3 individually */
198 {
199 const int color = FC(c % 2, c / 2, filters);
200 float noise[DT_IOP_RAWDENOISE_BANDS];
201 compute_channel_noise(noise,color,data);
202
203 // adjust for odd width and height
204 const int halfwidth = roi->width / 2 + (roi->width & (~(c >> 1)) & 1);
205 const int halfheight = roi->height / 2 + (roi->height & (~c) & 1);
206
207 // collect one of the R/G1/G2/B channels into a monochrome image, applying sqrt() to the values as a
208 // variance-stabilizing transform
209 #ifdef _OPENMP
210 #pragma omp parallel for default(none) \
211 dt_omp_firstprivate(in, fimg, roi, halfwidth) \
212 shared(c) \
213 schedule(static)
214 #endif
215 for(int row = c & 1; row < roi->height; row += 2)
216 {
217 float *const restrict fimgp = fimg + (size_t)row / 2 * halfwidth;
218 const int offset = (c & 2) >> 1;
219 const float *const restrict inp = in + (size_t)row * roi->width + offset;
220 const int senselwidth = (roi->width-offset+1)/2;
221 for(int col = 0; col < senselwidth; col++)
222 fimgp[col] = sqrtf(MAX(0.0f, inp[2*col]));
223 }
224
225 // perform the wavelet decomposition and denoising
226 dwt_denoise(fimg,halfwidth,halfheight,DT_IOP_RAWDENOISE_BANDS,noise);
227
228 // distribute the denoised data back out to the original R/G1/G2/B channel, squaring the resulting values to
229 // undo the original transform
230 #ifdef _OPENMP
231 #pragma omp parallel for default(none) \
232 dt_omp_firstprivate(fimg, halfwidth, out, roi, size) \
233 shared(c) \
234 schedule(static)
235 #endif
236 for(int row = c & 1; row < roi->height; row += 2)
237 {
238 const float *const restrict fimgp = fimg + (size_t)row / 2 * halfwidth;
239 const int offset = (c & 2) >> 1;
240 float *const restrict outp = out + (size_t)row * roi->width + offset;
241 const int senselwidth = (roi->width-offset+1)/2;
242 for(int col = 0; col < senselwidth; col++)
243 {
244 float d = fimgp[col];
245 outp[2*col] = d * d;
246 }
247 }
248 }
249 #if 0
250 /* FIXME: Haven't ported this part yet */
251 float maximum = 1.0; /* FIXME */
252 float black = 0.0; /* FIXME */
253 maximum *= BIT16;
254 black *= BIT16;
255 for (c=0; c<4; c++)
256 cblack[c] *= BIT16;
257 if (filters && colors == 3) /* pull G1 and G3 closer together */
258 {
259 float *window[4];
260 int wlast, blk[2];
261 float mul[2];
262 float thold = threshold/512;
263 for (row=0; row < 2; row++)
264 {
265 mul[row] = 0.125 * pre_mul[FC(row+1,0) | 1] / pre_mul[FC(row,0) | 1];
266 blk[row] = cblack[FC(row,0) | 1];
267 }
268 for (i=0; i < 4; i++)
269 window[i] = fimg + width*i;
270 for (wlast=-1, row=1; row < height-1; row++)
271 {
272 while (wlast < row+1)
273 {
274 for (wlast++, i=0; i < 4; i++)
275 window[(i+3) & 3] = window[i];
276 for (col = FC(wlast,1) & 1; col < width; col+=2)
277 window[2][col] = BAYER(wlast,col);
278 }
279 for (col = (FC(row,0) & 1)+1; col < width-1; col+=2)
280 {
281 float avg = ( window[0][col-1] + window[0][col+1] +
282 window[2][col-1] + window[2][col+1] - blk[~row & 1]*4 )
283 * mul[row & 1] + (window[1][col] + blk[row & 1]) * 0.5;
284 avg = avg > 0 ? sqrtf(avg) : 0;
285 float diff = sqrtf(BAYER(row,col)) - avg;
286 if (diff < -thold) diff += thold;
287 else if (diff > thold) diff -= thold;
288 else diff = 0;
289 BAYER(row,col) = SQR(avg+diff);
290 }
291 }
292 }
293 #endif
294 dt_free_align(fimg);
295 }
296
vstransform(const float value)297 static inline float vstransform(const float value)
298 {
299 return sqrtf(MAX(0.0f, value));
300 }
301
wavelet_denoise_xtrans(const float * const restrict in,float * const restrict out,const dt_iop_roi_t * const restrict roi,const dt_iop_rawdenoise_data_t * const data,const uint8_t (* const xtrans)[6])302 static void wavelet_denoise_xtrans(const float *const restrict in, float *const restrict out,
303 const dt_iop_roi_t *const restrict roi,
304 const dt_iop_rawdenoise_data_t *const data, const uint8_t (*const xtrans)[6])
305 {
306 const int width = roi->width;
307 const int height = roi->height;
308 const size_t size = (size_t)width * height;
309 // allocate a buffer for the particular color channel to be denoise; we add two rows to simplify the
310 // channel-extraction code (no special case for top/bottom row)
311 float *const img = dt_alloc_align_float((size_t)width * (height+2));
312 if (!img)
313 {
314 // we ran out of memory, so just pass through the image without denoising
315 memcpy(out, in, sizeof(float) * size);
316 return;
317 }
318 float *const fimg = img + width; // point at the actual color channel contents in the buffer
319
320 for(int c = 0; c < 3; c++)
321 {
322 float noise[DT_IOP_RAWDENOISE_BANDS];
323 compute_channel_noise(noise, c, data);
324
325 // ensure a defined value for every pixel in the top and bottom rows, even if they are more than
326 // one pixel away from the nearest neighbor of the same color and thus the simple interpolation
327 // used in the following loop does not set them
328 for (size_t col = 0; col < width; col++)
329 {
330 fimg[col] = 0.5f;
331 fimg[(height-1)*width + col] = 0.5f;
332 }
333 const size_t nthreads = darktable.num_openmp_threads; // go direct, dt_get_num_threads() always returns numprocs
334 const size_t chunksize = (height + nthreads - 1) / nthreads;
335 #ifdef _OPENMP
336 #pragma omp parallel for default(none) \
337 dt_omp_firstprivate(fimg, height, in, roi, size, width, xtrans, nthreads, chunksize) \
338 shared(c) num_threads(nthreads) \
339 schedule(static)
340 #endif
341 for(size_t chunk = 0; chunk < nthreads; chunk++)
342 {
343 const size_t start = chunk * chunksize;
344 const size_t pastend = MIN(start + chunksize,height);
345 for(size_t row = start; row < pastend; row++)
346 {
347 const float *const restrict inp = in + row * width;
348 float *const restrict fimgp = fimg + row * width;
349 // handle red/blue pixel in first column
350 if (c != 1 && FCxtrans(row, 0, roi, xtrans) == c)
351 {
352 // copy to neighbors above and right
353 const float d = vstransform(inp[0]);
354 fimgp[0] = fimgp[-width] = fimgp[-width+1] = d;
355 }
356 for(size_t col = (c != 1); col < width-1; col++)
357 {
358 if (FCxtrans(row, col, roi, xtrans) == c)
359 {
360 // the pixel at the current location has the desired color, so apply sqrt() as a variance-stablizing
361 // transform, and then do cheap nearest-neighbor interpolation by copying it to appropriate neighbors
362 const float d = vstransform(inp[col]);
363 fimgp[col] = d;
364 if (c == 1) // green pixel
365 {
366 // Copy to the right and down. The X-Trans color layout is such that copying to those two neighbors
367 // results in all positions being filled except in the left-most and right-most columns and sometimes
368 // the topmost and bottom-most rows (depending on how the ROI aligns with the CFA).
369 fimgp[col+1] = fimgp[col+width] = d;
370 }
371 else // red or blue pixel
372 {
373 // Copy value to all eight neighbors; it's OK to copy to the row above even when we're in row 0 (or
374 // the row below when in the last row) because the destination is sandwiched between other buffers
375 // that will be overwritten afterwards anyway. We need to copy to all adjacent positions because
376 // there may be two green pixels between nearest red/red or blue/blue, so each will cover one of the
377 // greens.
378 fimgp[col-width-1] = fimgp[col-width] = fimgp[col-width+1] = d; // row above
379 fimgp[col-1] = fimgp[col+1] = d; // left and right
380 if (row < pastend-1)
381 fimgp[col+width-1] = fimgp[col+width] = fimgp[col+width+1] = d; // row below
382 }
383 }
384 }
385 // leftmost and rightmost pixel in the row may still need to be filled in from a neighbor
386 if (FCxtrans(row, 0, roi, xtrans) != c)
387 {
388 int src = 0; // fallback is current sensel even if it has the wrong color
389 if (row > 1 && FCxtrans(row-1, 0, roi, xtrans) == c)
390 src = -width;
391 else if (FCxtrans(row, 1, roi, xtrans) == c)
392 src = 1;
393 else if (row > 1 && FCxtrans(row-1, 1, roi, xtrans) == c)
394 src = -width + 1;
395 fimgp[0] = vstransform(inp[src]);
396 }
397 // check the right-most pixel; if it's the desired color and not green, copy it to the neighbors
398 if (c != 1 && FCxtrans(row, width-1, roi, xtrans) == c)
399 {
400 // copy to neighbors above and left
401 const float d = vstransform(inp[width-1]);
402 fimgp[width-2] = fimgp[width-1] = fimgp[-1] = d;
403 }
404 else if (FCxtrans(row, width-1, roi, xtrans) != c)
405 {
406 int src = width-1; // fallback is current sensel even if it has the wrong color
407 if (FCxtrans(row, width-2, roi, xtrans) == c)
408 src = width-2;
409 else if (row > 1 && FCxtrans(row-1, width-1, roi, xtrans) == c)
410 src = -1;
411 else if (row > 1 && FCxtrans(row-1, width-2, roi, xtrans) == c)
412 src = -2;
413 fimgp[width-1] = vstransform(inp[src]);
414 }
415 }
416 if (pastend < height)
417 {
418 // Another slice follows us, and by updating the last row of our slice, we've clobbered values that
419 // were previously written by the other thread. Restore them.
420 const float *const restrict inp = in + pastend * width;
421 float *const restrict fimgp = fimg + pastend * width;
422 for (size_t col = 0; col < width-1; col++)
423 {
424 if (FCxtrans(pastend, col, roi, xtrans) == c)
425 {
426 const float d = vstransform(inp[col]);
427 if (c == 1) // green pixel
428 {
429 if (FCxtrans(pastend, col+1, roi, xtrans) != c)
430 fimgp[col] = fimgp[col+1] = d; // copy to the right
431 }
432 else // red/blue pixel
433 {
434 // copy the pixel's adjusted value to the prior row and left and right (if not at edge)
435 fimgp[col-width] = fimgp[col-width+1] = d;
436 if (col > 0) fimgp[col-width-1] = d;
437 }
438 }
439 // some red and blue values may need to be restored from the row TWO past the end of our slice
440 if (c != 1 && pastend+1 < height && FCxtrans(pastend+1, col, roi, xtrans) == c)
441 {
442 const float d = vstransform(inp[col+width]);
443 fimgp[col] = fimgp[col+1] = d;
444 if (col > 0) fimgp[col-1] = d;
445 }
446 }
447 }
448 }
449
450 // perform the wavelet decomposition and denoising
451 dwt_denoise(fimg,width,height,DT_IOP_RAWDENOISE_BANDS,noise);
452
453 // distribute the denoised data back out to the original R/G/B channel, squaring the resulting values to
454 // undo the original transform
455 #ifdef _OPENMP
456 #pragma omp parallel for default(none) \
457 dt_omp_firstprivate(height, fimg, roi, width, xtrans, c) \
458 dt_omp_sharedconst(out) \
459 schedule(static)
460 #endif
461 for(int row = 0; row < height; row++)
462 {
463 const float *const restrict fimgp = fimg + (size_t)row * width;
464 float *const restrict outp = out + (size_t)row * width;
465 for(int col = 0; col < width; col++)
466 if(FCxtrans(row, col, roi, xtrans) == c)
467 {
468 float d = fimgp[col];
469 outp[col] = d * d;
470 }
471 }
472 }
473
474 dt_free_align(img);
475 }
476
process(struct dt_iop_module_t * self,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)477 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
478 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
479 {
480 const dt_iop_rawdenoise_data_t *const restrict d = (dt_iop_rawdenoise_data_t *)piece->data;
481
482 if(!(d->threshold > 0.0f))
483 {
484 dt_iop_image_copy_by_size(ovoid, ivoid, roi_in->width, roi_in->height, piece->colors);
485 }
486 else
487 {
488 const uint32_t filters = piece->pipe->dsc.filters;
489 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->pipe->dsc.xtrans;
490 if (filters != 9u)
491 wavelet_denoise(ivoid, ovoid, roi_in, d, filters);
492 else
493 wavelet_denoise_xtrans(ivoid, ovoid, roi_in, d, xtrans);
494 }
495 }
496
init(dt_iop_module_t * module)497 void init(dt_iop_module_t *module)
498 {
499 dt_iop_default_init(module);
500
501 dt_iop_rawdenoise_params_t *d = module->default_params;
502
503 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
504 {
505 for(int ch = 0; ch < DT_RAWDENOISE_NONE; ch++)
506 {
507 d->x[ch][k] = k / (DT_IOP_RAWDENOISE_BANDS - 1.f);
508 }
509 }
510 }
511
reload_defaults(dt_iop_module_t * module)512 void reload_defaults(dt_iop_module_t *module)
513 {
514 // can't be switched on for non-raw images:
515 module->hide_enable_button = !dt_image_is_raw(&module->dev->image_storage);
516
517 if(module->widget)
518 {
519 gtk_stack_set_visible_child_name(GTK_STACK(module->widget), module->hide_enable_button ? "non_raw" : "raw");
520 }
521
522 module->default_enabled = 0;
523 }
524
commit_params(struct dt_iop_module_t * self,dt_iop_params_t * params,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)525 void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe,
526 dt_dev_pixelpipe_iop_t *piece)
527 {
528 dt_iop_rawdenoise_params_t *p = (dt_iop_rawdenoise_params_t *)params;
529 dt_iop_rawdenoise_data_t *d = (dt_iop_rawdenoise_data_t *)piece->data;
530
531 d->threshold = p->threshold;
532
533 for(int ch = 0; ch < DT_RAWDENOISE_NONE; ch++)
534 {
535 dt_draw_curve_set_point(d->curve[ch], 0, p->x[ch][DT_IOP_RAWDENOISE_BANDS - 2] - 1.0, p->y[ch][0]);
536 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
537 dt_draw_curve_set_point(d->curve[ch], k, p->x[ch][k], p->y[ch][k]);
538 dt_draw_curve_set_point(d->curve[ch], DT_IOP_RAWDENOISE_BANDS + 1, p->x[ch][1] + 1.0,
539 p->y[ch][DT_IOP_RAWDENOISE_BANDS - 1]);
540 dt_draw_curve_calc_values(d->curve[ch], 0.0, 1.0, DT_IOP_RAWDENOISE_BANDS, NULL, d->force[ch]);
541 }
542
543 if (!(dt_image_is_raw(&pipe->image)))
544 piece->enabled = 0;
545 }
546
init_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)547 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
548 {
549 dt_iop_rawdenoise_data_t *d = (dt_iop_rawdenoise_data_t *)malloc(sizeof(dt_iop_rawdenoise_data_t));
550 dt_iop_rawdenoise_params_t *default_params = (dt_iop_rawdenoise_params_t *)self->default_params;
551
552 piece->data = (void *)d;
553 for(int ch = 0; ch < DT_RAWDENOISE_NONE; ch++)
554 {
555 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, CATMULL_ROM);
556 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
557 (void)dt_draw_curve_add_point(d->curve[ch], default_params->x[ch][k], default_params->y[ch][k]);
558 }
559 }
560
cleanup_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)561 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
562 {
563 dt_iop_rawdenoise_data_t *d = (dt_iop_rawdenoise_data_t *)(piece->data);
564 for(int ch = 0; ch < DT_RAWDENOISE_NONE; ch++) dt_draw_curve_destroy(d->curve[ch]);
565 free(piece->data);
566 piece->data = NULL;
567 }
568
gui_update(dt_iop_module_t * self)569 void gui_update(dt_iop_module_t *self)
570 {
571 dt_iop_rawdenoise_gui_data_t *g = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
572 dt_iop_rawdenoise_params_t *p = (dt_iop_rawdenoise_params_t *)self->params;
573 dt_iop_cancel_history_update(self);
574 dt_bauhaus_slider_set_soft(g->threshold, p->threshold);
575 gtk_widget_queue_draw(self->widget);
576 }
577
dt_iop_rawdenoise_get_params(dt_iop_rawdenoise_params_t * p,const int ch,const double mouse_x,const double mouse_y,const float rad)578 static void dt_iop_rawdenoise_get_params(dt_iop_rawdenoise_params_t *p, const int ch, const double mouse_x,
579 const double mouse_y, const float rad)
580 {
581 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
582 {
583 const float f = expf(-(mouse_x - p->x[ch][k]) * (mouse_x - p->x[ch][k]) / (rad * rad));
584 p->y[ch][k] = (1 - f) * p->y[ch][k] + f * mouse_y;
585 }
586 }
587
rawdenoise_draw(GtkWidget * widget,cairo_t * crf,gpointer user_data)588 static gboolean rawdenoise_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
589 {
590 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
591 dt_iop_rawdenoise_gui_data_t *c = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
592 dt_iop_rawdenoise_params_t p = *(dt_iop_rawdenoise_params_t *)self->params;
593
594 int ch = (int)c->channel;
595 dt_draw_curve_set_point(c->transition_curve, 0, p.x[ch][DT_IOP_RAWDENOISE_BANDS - 2] - 1.0, p.y[ch][0]);
596 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
597 dt_draw_curve_set_point(c->transition_curve, k + 1, p.x[ch][k], p.y[ch][k]);
598 dt_draw_curve_set_point(c->transition_curve, DT_IOP_RAWDENOISE_BANDS + 1, p.x[ch][1] + 1.0,
599 p.y[ch][DT_IOP_RAWDENOISE_BANDS - 1]);
600
601 const int inset = DT_IOP_RAWDENOISE_INSET;
602 GtkAllocation allocation;
603 gtk_widget_get_allocation(widget, &allocation);
604 int width = allocation.width, height = allocation.height;
605 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
606 cairo_t *cr = cairo_create(cst);
607 cairo_set_source_rgb(cr, .2, .2, .2);
608
609 cairo_paint(cr);
610
611 cairo_translate(cr, inset, inset);
612 width -= 2 * inset;
613 height -= 2 * inset;
614
615 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
616 cairo_set_source_rgb(cr, .1, .1, .1);
617 cairo_rectangle(cr, 0, 0, width, height);
618 cairo_stroke(cr);
619
620 cairo_set_source_rgb(cr, .3, .3, .3);
621 cairo_rectangle(cr, 0, 0, width, height);
622 cairo_fill(cr);
623
624 // draw grid
625 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
626 cairo_set_source_rgb(cr, .1, .1, .1);
627 dt_draw_grid(cr, 8, 0, 0, width, height);
628
629 if(c->mouse_y > 0 || c->dragging)
630 {
631 // draw min/max curves:
632 dt_iop_rawdenoise_get_params(&p, c->channel, c->mouse_x, 1., c->mouse_radius);
633 dt_draw_curve_set_point(c->transition_curve, 0, p.x[ch][DT_IOP_RAWDENOISE_BANDS - 2] - 1.0, p.y[ch][0]);
634 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
635 dt_draw_curve_set_point(c->transition_curve, k + 1, p.x[ch][k], p.y[ch][k]);
636 dt_draw_curve_set_point(c->transition_curve, DT_IOP_RAWDENOISE_BANDS + 1, p.x[ch][1] + 1.0,
637 p.y[ch][DT_IOP_RAWDENOISE_BANDS - 1]);
638 dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_RAWDENOISE_RES, c->draw_min_xs, c->draw_min_ys);
639
640 p = *(dt_iop_rawdenoise_params_t *)self->params;
641 dt_iop_rawdenoise_get_params(&p, c->channel, c->mouse_x, .0, c->mouse_radius);
642 dt_draw_curve_set_point(c->transition_curve, 0, p.x[ch][DT_IOP_RAWDENOISE_BANDS - 2] - 1.0, p.y[ch][0]);
643 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
644 dt_draw_curve_set_point(c->transition_curve, k + 1, p.x[ch][k], p.y[ch][k]);
645 dt_draw_curve_set_point(c->transition_curve, DT_IOP_RAWDENOISE_BANDS + 1, p.x[ch][1] + 1.0,
646 p.y[ch][DT_IOP_RAWDENOISE_BANDS - 1]);
647 dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_RAWDENOISE_RES, c->draw_max_xs, c->draw_max_ys);
648 }
649
650 cairo_save(cr);
651
652 // draw selected cursor
653 cairo_translate(cr, 0, height);
654
655 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
656 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
657
658 for(int i = 0; i < DT_RAWDENOISE_NONE; i++)
659 {
660 // draw curves, selected last
661 ch = ((int)c->channel + i + 1) % DT_RAWDENOISE_NONE;
662 float alpha = 0.3;
663 if(i == DT_RAWDENOISE_NONE - 1) alpha = 1.0;
664 switch(ch)
665 {
666 case 0:
667 cairo_set_source_rgba(cr, .7, .7, .7, alpha);
668 break;
669 case 1:
670 cairo_set_source_rgba(cr, .7, .1, .1, alpha);
671 break;
672 case 2:
673 cairo_set_source_rgba(cr, .1, .7, .1, alpha);
674 break;
675 case 3:
676 cairo_set_source_rgba(cr, .1, .1, .7, alpha);
677 break;
678 }
679
680 p = *(dt_iop_rawdenoise_params_t *)self->params;
681 dt_draw_curve_set_point(c->transition_curve, 0, p.x[ch][DT_IOP_RAWDENOISE_BANDS - 2] - 1.0, p.y[ch][0]);
682 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
683 dt_draw_curve_set_point(c->transition_curve, k + 1, p.x[ch][k], p.y[ch][k]);
684 dt_draw_curve_set_point(c->transition_curve, DT_IOP_RAWDENOISE_BANDS + 1, p.x[ch][1] + 1.0,
685 p.y[ch][DT_IOP_RAWDENOISE_BANDS - 1]);
686 dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_RAWDENOISE_RES, c->draw_xs, c->draw_ys);
687 cairo_move_to(cr, 0 * width / (float)(DT_IOP_RAWDENOISE_RES - 1), -height * c->draw_ys[0]);
688 for(int k = 1; k < DT_IOP_RAWDENOISE_RES; k++)
689 cairo_line_to(cr, k * width / (float)(DT_IOP_RAWDENOISE_RES - 1), -height * c->draw_ys[k]);
690 cairo_stroke(cr);
691 }
692
693 ch = c->channel;
694 // draw dots on knots
695 cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
696 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
697 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
698 {
699 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);
700 if(c->x_move == k)
701 cairo_fill(cr);
702 else
703 cairo_stroke(cr);
704 }
705
706 if(c->mouse_y > 0 || c->dragging)
707 {
708 // draw min/max, if selected
709 cairo_set_source_rgba(cr, .7, .7, .7, .6);
710 cairo_move_to(cr, 0, -height * c->draw_min_ys[0]);
711 for(int k = 1; k < DT_IOP_RAWDENOISE_RES; k++)
712 cairo_line_to(cr, k * width / (float)(DT_IOP_RAWDENOISE_RES - 1), -height * c->draw_min_ys[k]);
713 for(int k = DT_IOP_RAWDENOISE_RES - 1; k >= 0; k--)
714 cairo_line_to(cr, k * width / (float)(DT_IOP_RAWDENOISE_RES - 1), -height * c->draw_max_ys[k]);
715 cairo_close_path(cr);
716 cairo_fill(cr);
717 // draw mouse focus circle
718 cairo_set_source_rgba(cr, .9, .9, .9, .5);
719 const float pos = DT_IOP_RAWDENOISE_RES * c->mouse_x;
720 int k = (int)pos;
721 const float f = k - pos;
722 if(k >= DT_IOP_RAWDENOISE_RES - 1) k = DT_IOP_RAWDENOISE_RES - 2;
723 float ht = -height * (f * c->draw_ys[k] + (1 - f) * c->draw_ys[k + 1]);
724 cairo_arc(cr, c->mouse_x * width, ht, c->mouse_radius * width, 0, 2. * M_PI);
725 cairo_stroke(cr);
726 }
727
728 cairo_restore(cr);
729
730 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
731
732 // draw labels:
733 PangoLayout *layout;
734 PangoRectangle ink;
735 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
736 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
737 pango_font_description_set_absolute_size(desc, (.08 * height) * PANGO_SCALE);
738 layout = pango_cairo_create_layout(cr);
739 pango_layout_set_font_description(layout, desc);
740 cairo_set_source_rgb(cr, .1, .1, .1);
741
742 pango_layout_set_text(layout, _("coarse"), -1);
743 pango_layout_get_pixel_extents(layout, &ink, NULL);
744 cairo_move_to(cr, .02 * width - ink.y, .5 * (height + ink.width));
745 cairo_save(cr);
746 cairo_rotate(cr, -M_PI * .5f);
747 pango_cairo_show_layout(cr, layout);
748 cairo_restore(cr);
749
750 pango_layout_set_text(layout, _("fine"), -1);
751 pango_layout_get_pixel_extents(layout, &ink, NULL);
752 cairo_move_to(cr, .98 * width - ink.height, .5 * (height + ink.width));
753 cairo_save(cr);
754 cairo_rotate(cr, -M_PI * .5f);
755 pango_cairo_show_layout(cr, layout);
756 cairo_restore(cr);
757
758
759 pango_layout_set_text(layout, _("smooth"), -1);
760 pango_layout_get_pixel_extents(layout, &ink, NULL);
761 cairo_move_to(cr, .5 * (width - ink.width), .08 * height - ink.height);
762 pango_cairo_show_layout(cr, layout);
763
764 pango_layout_set_text(layout, _("noisy"), -1);
765 pango_layout_get_pixel_extents(layout, &ink, NULL);
766 cairo_move_to(cr, .5 * (width - ink.width), .97 * height - ink.height);
767 pango_cairo_show_layout(cr, layout);
768
769 pango_font_description_free(desc);
770 g_object_unref(layout);
771 cairo_destroy(cr);
772 cairo_set_source_surface(crf, cst, 0, 0);
773 cairo_paint(crf);
774 cairo_surface_destroy(cst);
775 return TRUE;
776 }
777
rawdenoise_motion_notify(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)778 static gboolean rawdenoise_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
779 {
780 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
781 dt_iop_rawdenoise_gui_data_t *c = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
782 dt_iop_rawdenoise_params_t *p = (dt_iop_rawdenoise_params_t *)self->params;
783 const int inset = DT_IOP_RAWDENOISE_INSET;
784 GtkAllocation allocation;
785 gtk_widget_get_allocation(widget, &allocation);
786 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
787 if(!c->dragging) c->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
788 c->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
789 if(c->dragging)
790 {
791 *p = c->drag_params;
792 if(c->x_move < 0)
793 {
794 dt_iop_rawdenoise_get_params(p, c->channel, c->mouse_x, c->mouse_y + c->mouse_pick, c->mouse_radius);
795 }
796 gtk_widget_queue_draw(widget);
797 dt_iop_queue_history_update(self, FALSE);
798 }
799 else
800 {
801 c->x_move = -1;
802 gtk_widget_queue_draw(widget);
803 }
804 return TRUE;
805 }
806
rawdenoise_button_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)807 static gboolean rawdenoise_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
808 {
809 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
810 dt_iop_rawdenoise_gui_data_t *c = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
811 const int ch = c->channel;
812 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
813 {
814 // reset current curve
815 dt_iop_rawdenoise_params_t *p = (dt_iop_rawdenoise_params_t *)self->params;
816 dt_iop_rawdenoise_params_t *d = (dt_iop_rawdenoise_params_t *)self->default_params;
817 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
818 {
819 p->x[ch][k] = d->x[ch][k];
820 p->y[ch][k] = d->y[ch][k];
821 }
822 dt_dev_add_history_item(darktable.develop, self, TRUE);
823 gtk_widget_queue_draw(self->widget);
824 }
825 else if(event->button == 1)
826 {
827 c->drag_params = *(dt_iop_rawdenoise_params_t *)self->params;
828 const int inset = DT_IOP_RAWDENOISE_INSET;
829 GtkAllocation allocation;
830 gtk_widget_get_allocation(widget, &allocation);
831 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
832 c->mouse_pick
833 = dt_draw_curve_calc_value(c->transition_curve, CLAMP(event->x - inset, 0, width) / (float)width);
834 c->mouse_pick -= 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
835 c->dragging = 1;
836 return TRUE;
837 }
838 return FALSE;
839 }
840
rawdenoise_button_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)841 static gboolean rawdenoise_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
842 {
843 if(event->button == 1)
844 {
845 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
846 dt_iop_rawdenoise_gui_data_t *c = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
847 c->dragging = 0;
848 return TRUE;
849 }
850 return FALSE;
851 }
852
rawdenoise_leave_notify(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)853 static gboolean rawdenoise_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
854 {
855 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
856 dt_iop_rawdenoise_gui_data_t *c = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
857 if(!c->dragging) c->mouse_y = -1.0;
858 gtk_widget_queue_draw(widget);
859 return TRUE;
860 }
861
rawdenoise_scrolled(GtkWidget * widget,GdkEventScroll * event,gpointer user_data)862 static gboolean rawdenoise_scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
863 {
864 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
865 dt_iop_rawdenoise_gui_data_t *c = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
866
867 if(dt_gui_ignore_scroll(event)) return FALSE;
868
869 int delta_y;
870 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
871 {
872 if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
873 {
874 //adjust aspect
875 const int aspect = dt_conf_get_int("plugins/darkroom/rawdenoise/aspect_percent");
876 dt_conf_set_int("plugins/darkroom/rawdenoise/aspect_percent", aspect + delta_y);
877 dtgtk_drawing_area_set_aspect_ratio(widget, aspect / 100.0);
878 }
879 else
880 {
881 c->mouse_radius = CLAMP(c->mouse_radius * (1.0 + 0.1 * delta_y), 0.2 / DT_IOP_RAWDENOISE_BANDS, 1.0);
882 gtk_widget_queue_draw(widget);
883 }
884 }
885
886 return TRUE;
887 }
888
rawdenoise_tab_switch(GtkNotebook * notebook,GtkWidget * page,guint page_num,gpointer user_data)889 static void rawdenoise_tab_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
890 {
891 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
892 if(darktable.gui->reset) return;
893 dt_iop_rawdenoise_gui_data_t *c = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
894 c->channel = (dt_iop_rawdenoise_channel_t)page_num;
895 gtk_widget_queue_draw(self->widget);
896 }
897
gui_init(dt_iop_module_t * self)898 void gui_init(dt_iop_module_t *self)
899 {
900 dt_iop_rawdenoise_gui_data_t *c = IOP_GUI_ALLOC(rawdenoise);
901 dt_iop_rawdenoise_params_t *p = (dt_iop_rawdenoise_params_t *)self->default_params;
902
903 c->channel = dt_conf_get_int("plugins/darkroom/rawdenoise/gui_channel");
904 c->channel_tabs = GTK_NOTEBOOK(gtk_notebook_new());
905
906 dt_ui_notebook_page(c->channel_tabs, _("all"), NULL);
907 dt_ui_notebook_page(c->channel_tabs, _("R"), NULL);
908 dt_ui_notebook_page(c->channel_tabs, _("G"), NULL);
909 dt_ui_notebook_page(c->channel_tabs, _("B"), NULL);
910
911 gtk_widget_show(gtk_notebook_get_nth_page(c->channel_tabs, c->channel));
912 gtk_notebook_set_current_page(c->channel_tabs, c->channel);
913 g_signal_connect(G_OBJECT(c->channel_tabs), "switch_page", G_CALLBACK(rawdenoise_tab_switch), self);
914
915 const int ch = (int)c->channel;
916 c->transition_curve = dt_draw_curve_new(0.0, 1.0, CATMULL_ROM);
917 (void)dt_draw_curve_add_point(c->transition_curve, p->x[ch][DT_IOP_RAWDENOISE_BANDS - 2] - 1.0,
918 p->y[ch][DT_IOP_RAWDENOISE_BANDS - 2]);
919 for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
920 (void)dt_draw_curve_add_point(c->transition_curve, p->x[ch][k], p->y[ch][k]);
921 (void)dt_draw_curve_add_point(c->transition_curve, p->x[ch][1] + 1.0, p->y[ch][1]);
922
923 c->mouse_x = c->mouse_y = c->mouse_pick = -1.0;
924 c->dragging = 0;
925 c->x_move = -1;
926 self->timeout_handle = 0;
927 c->mouse_radius = 1.0 / (DT_IOP_RAWDENOISE_BANDS * 2);
928
929 GtkWidget *box_raw = self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
930
931 const float aspect = dt_conf_get_int("plugins/darkroom/rawdenoise/aspect_percent") / 100.0;
932 c->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(aspect));
933
934 gtk_box_pack_start(GTK_BOX(box_raw), GTK_WIDGET(c->channel_tabs), FALSE, FALSE, 0);
935 gtk_box_pack_start(GTK_BOX(box_raw), GTK_WIDGET(c->area), FALSE, FALSE, 0);
936
937 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK
938 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
939 | GDK_LEAVE_NOTIFY_MASK | darktable.gui->scroll_mask);
940 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(rawdenoise_draw), self);
941 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(rawdenoise_button_press), self);
942 g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(rawdenoise_button_release), self);
943 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(rawdenoise_motion_notify), self);
944 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(rawdenoise_leave_notify), self);
945 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(rawdenoise_scrolled), self);
946
947 c->threshold = dt_bauhaus_slider_from_params(self, "threshold");
948 dt_bauhaus_slider_set_soft_max(c->threshold, 0.1);
949 dt_bauhaus_slider_set_digits(c->threshold, 3);
950
951 // start building top level widget
952 self->widget = gtk_stack_new();
953 gtk_stack_set_homogeneous(GTK_STACK(self->widget), FALSE);
954
955 GtkWidget *label_non_raw = dt_ui_label_new(_("raw denoising\nonly works for raw images."));
956
957 gtk_stack_add_named(GTK_STACK(self->widget), label_non_raw, "non_raw");
958 gtk_stack_add_named(GTK_STACK(self->widget), box_raw, "raw");
959 }
960
gui_cleanup(dt_iop_module_t * self)961 void gui_cleanup(dt_iop_module_t *self)
962 {
963 dt_iop_rawdenoise_gui_data_t *c = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
964 dt_conf_set_int("plugins/darkroom/rawdenoise/gui_channel", c->channel);
965 dt_draw_curve_destroy(c->transition_curve);
966 dt_iop_cancel_history_update(self);
967
968 IOP_GUI_FREE;
969 }
970 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
971 // vim: shiftwidth=2 expandtab tabstop=2 cindent
972 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
973