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[(size_t)(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   dt_action_define_iop(self, NULL, N_("channel"), GTK_WIDGET(c->channel_tabs), &dt_action_def_tabs_all_rgb);
906 
907   dt_ui_notebook_page(c->channel_tabs, N_("all"), NULL);
908   dt_ui_notebook_page(c->channel_tabs, N_("R"), NULL);
909   dt_ui_notebook_page(c->channel_tabs, N_("G"), NULL);
910   dt_ui_notebook_page(c->channel_tabs, N_("B"), NULL);
911 
912   gtk_widget_show(gtk_notebook_get_nth_page(c->channel_tabs, c->channel));
913   gtk_notebook_set_current_page(c->channel_tabs, c->channel);
914   g_signal_connect(G_OBJECT(c->channel_tabs), "switch_page", G_CALLBACK(rawdenoise_tab_switch), self);
915 
916   const int ch = (int)c->channel;
917   c->transition_curve = dt_draw_curve_new(0.0, 1.0, CATMULL_ROM);
918   (void)dt_draw_curve_add_point(c->transition_curve, p->x[ch][DT_IOP_RAWDENOISE_BANDS - 2] - 1.0,
919                                 p->y[ch][DT_IOP_RAWDENOISE_BANDS - 2]);
920   for(int k = 0; k < DT_IOP_RAWDENOISE_BANDS; k++)
921     (void)dt_draw_curve_add_point(c->transition_curve, p->x[ch][k], p->y[ch][k]);
922   (void)dt_draw_curve_add_point(c->transition_curve, p->x[ch][1] + 1.0, p->y[ch][1]);
923 
924   c->mouse_x = c->mouse_y = c->mouse_pick = -1.0;
925   c->dragging = 0;
926   c->x_move = -1;
927   self->timeout_handle = 0;
928   c->mouse_radius = 1.0 / (DT_IOP_RAWDENOISE_BANDS * 2);
929 
930   GtkWidget *box_raw = self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
931 
932   const float aspect = dt_conf_get_int("plugins/darkroom/rawdenoise/aspect_percent") / 100.0;
933   c->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(aspect));
934   g_object_set_data(G_OBJECT(c->area), "iop-instance", self);
935   dt_action_define_iop(self, NULL, N_("graph"), GTK_WIDGET(c->area), NULL);
936 
937   gtk_box_pack_start(GTK_BOX(box_raw), GTK_WIDGET(c->channel_tabs), FALSE, FALSE, 0);
938   gtk_box_pack_start(GTK_BOX(box_raw), GTK_WIDGET(c->area), FALSE, FALSE, 0);
939 
940   gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask
941                                            | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
942                                            | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
943   g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(rawdenoise_draw), self);
944   g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(rawdenoise_button_press), self);
945   g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(rawdenoise_button_release), self);
946   g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(rawdenoise_motion_notify), self);
947   g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(rawdenoise_leave_notify), self);
948   g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(rawdenoise_scrolled), self);
949 
950   c->threshold = dt_bauhaus_slider_from_params(self, "threshold");
951   dt_bauhaus_slider_set_soft_max(c->threshold, 0.1);
952   dt_bauhaus_slider_set_digits(c->threshold, 3);
953 
954   // start building top level widget
955   self->widget = gtk_stack_new();
956   gtk_stack_set_homogeneous(GTK_STACK(self->widget), FALSE);
957 
958   GtkWidget *label_non_raw = dt_ui_label_new(_("raw denoising\nonly works for raw images."));
959 
960   gtk_stack_add_named(GTK_STACK(self->widget), label_non_raw, "non_raw");
961   gtk_stack_add_named(GTK_STACK(self->widget), box_raw, "raw");
962 }
963 
gui_cleanup(dt_iop_module_t * self)964 void gui_cleanup(dt_iop_module_t *self)
965 {
966   dt_iop_rawdenoise_gui_data_t *c = (dt_iop_rawdenoise_gui_data_t *)self->gui_data;
967   dt_conf_set_int("plugins/darkroom/rawdenoise/gui_channel", c->channel);
968   dt_draw_curve_destroy(c->transition_curve);
969   dt_iop_cancel_history_update(self);
970 
971   IOP_GUI_FREE;
972 }
973 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
974 // vim: shiftwidth=2 expandtab tabstop=2 cindent
975 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
976