1 /*
2   This file is part of darktable,
3   Copyright (C) 2010-2021 darktable developers.
4 
5   darktable is free software: you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation, either version 3 of the License, or
8   (at your option) any later version.
9 
10   darktable is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 #include "bauhaus/bauhaus.h"
22 #include "common/colorspaces.h"
23 #include "common/debug.h"
24 #include "common/math.h"
25 #include "common/opencl.h"
26 #include "control/control.h"
27 #include "develop/develop.h"
28 #include "develop/imageop.h"
29 #include "develop/imageop_math.h"
30 #include "develop/openmp_maths.h"
31 #include "gui/accelerators.h"
32 #include "gui/gtk.h"
33 #include "gui/presets.h"
34 #include "iop/iop_api.h"
35 
36 #include <assert.h>
37 #include <gtk/gtk.h>
38 #include <inttypes.h>
39 #include <math.h>
40 #include <stdlib.h>
41 #include <string.h>
42 
43 /** Crazy presets b&w ...
44   Film Type     R   G   B           R G B
45   AGFA 200X   18    41    41    Ilford Pan F    33  36  31
46   Agfapan 25    25    39    36    Ilford SFX    36  31  33
47   Agfapan 100   21    40    39    Ilford XP2 Super  21  42  37
48   Agfapan 400   20    41    39    Kodak T-Max 100 24  37  39
49   Ilford Delta 100  21    42    37    Kodak T-Max 400 27  36  37
50   Ilford Delta 400  22    42    36    Kodak Tri-X 400 25  35  40
51   Ilford Delta 3200 31    36    33    Normal Contrast 43  33  30
52   Ilford FP4    28    41    31    High Contrast   40  34  60
53   Ilford HP5    23    37    40    Generic B/W   24  68  8
54 */
55 
56 DT_MODULE_INTROSPECTION(2, dt_iop_channelmixer_params_t)
57 
58 typedef enum _channelmixer_output_t
59 {
60   /** mixes into hue channel */
61   CHANNEL_HUE = 0,
62   /** mixes into lightness channel */
63   CHANNEL_SATURATION,
64   /** mixes into lightness channel */
65   CHANNEL_LIGHTNESS,
66   /** mixes into red channel of image */
67   CHANNEL_RED,
68   /** mixes into green channel of image */
69   CHANNEL_GREEN,
70   /** mixes into blue channel of image */
71   CHANNEL_BLUE,
72   /** mixes into gray channel of image = monochrome*/
73   CHANNEL_GRAY,
74 
75   CHANNEL_SIZE
76 } _channelmixer_output_t;
77 
78 typedef enum _channelmixer_algorithm_t
79 {
80    CHANNEL_MIXER_VERSION_1 = 0,
81    CHANNEL_MIXER_VERSION_2 = 1,
82 } _channelmixer_algorithm_t;
83 
84 typedef struct dt_iop_channelmixer_params_t
85 {
86   /** amount of red to mix value */
87   float red[CHANNEL_SIZE]; // $MIN: -1.0 $MAX: 1.0
88   /** amount of green to mix value */
89   float green[CHANNEL_SIZE]; // $MIN: -1.0 $MAX: 1.0
90   /** amount of blue to mix value */
91   float blue[CHANNEL_SIZE]; // $MIN: -1.0 $MAX: 1.0
92   /** algorithm version */
93   _channelmixer_algorithm_t algorithm_version;
94 } dt_iop_channelmixer_params_t;
95 
96 typedef struct dt_iop_channelmixer_gui_data_t
97 {
98   GtkBox *vbox;
99   GtkWidget *output_channel;                          // Output channel
100   GtkWidget *scale_red, *scale_green, *scale_blue;    // red, green, blue
101 } dt_iop_channelmixer_gui_data_t;
102 
103 typedef enum _channelmixer_operation_mode_t
104 {
105   OPERATION_MODE_RGB = 0,
106   OPERATION_MODE_GRAY = 1,
107   OPERATION_MODE_HSL_V1 = 2,
108   OPERATION_MODE_HSL_V2 = 3,
109 } _channelmixer_operation_mode_t;
110 
111 typedef struct dt_iop_channelmixer_data_t
112 {
113   float hsl_matrix[9];
114   float rgb_matrix[9];
115   _channelmixer_operation_mode_t operation_mode;
116 } dt_iop_channelmixer_data_t;
117 
118 typedef struct dt_iop_channelmixer_global_data_t
119 {
120   int kernel_channelmixer;
121 } dt_iop_channelmixer_global_data_t;
122 
123 
name()124 const char *name()
125 {
126   return _("channel mixer");
127 }
128 
deprecated_msg()129 const char *deprecated_msg()
130 {
131   return _("this module is deprecated. please use the color calibration module instead.");
132 }
133 
description(struct dt_iop_module_t * self)134 const char *description(struct dt_iop_module_t *self)
135 {
136   return dt_iop_set_description(self, _("perform color space corrections\n"
137                                         "such as white balance, channels mixing\n"
138                                         "and conversions to monochrome emulating film"),
139                                       _("corrective or creative"),
140                                       _("linear, RGB, display-referred"),
141                                       _("linear, RGB"),
142                                       _("linear, RGB, display-referred"));
143 }
144 
145 
flags()146 int flags()
147 {
148   return IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_SUPPORTS_BLENDING | IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_DEPRECATED;
149 }
150 
default_group()151 int default_group()
152 {
153   return IOP_GROUP_COLOR | IOP_GROUP_GRADING;
154 }
155 
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)156 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
157 {
158   return iop_cs_rgb;
159 }
160 
legacy_params(dt_iop_module_t * self,const void * const old_params,const int old_version,void * new_params,const int new_version)161 int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
162                   const int new_version)
163 {
164   if(old_version == 1 && new_version == 2)
165   {
166     typedef struct dt_iop_channelmixer_params_v1_t
167     {
168       float red[7];
169       float green[7];
170       float blue[7];
171     } dt_iop_channelmixer_params_v1_t;
172 
173     const dt_iop_channelmixer_params_v1_t *old = (dt_iop_channelmixer_params_v1_t *)old_params;
174     dt_iop_channelmixer_params_t *new = (dt_iop_channelmixer_params_t *)new_params;
175     dt_iop_channelmixer_params_t *defaults = (dt_iop_channelmixer_params_t *)self->default_params;
176 
177     *new = *defaults; // start with a fresh copy of default parameters
178     new->algorithm_version = CHANNEL_MIXER_VERSION_1;
179 
180     // copy gray mixing parameters
181     new->red[CHANNEL_GRAY] = old->red[6];
182     new->green[CHANNEL_GRAY] = old->green[6];
183     new->blue[CHANNEL_GRAY] = old->blue[6];
184 
185     // version 1 does not use RGB mixing when gray is enabled
186     if(new->red[CHANNEL_GRAY] == 0.0f && new->green[CHANNEL_GRAY] == 0.0f && new->blue[CHANNEL_GRAY] == 0.0f)
187     {
188       for(int i = 0; i < 3; i++)
189       {
190         new->red[CHANNEL_RED + i] = old->red[3 + i];
191         new->green[CHANNEL_RED + i] = old->green[3 + i];
192         new->blue[CHANNEL_RED + i] = old->blue[3 + i];
193       }
194     }
195 
196     // copy HSL mixing parameters
197     for(int i = 0; i < 3; i++)
198     {
199       new->red[i] = old->red[i];
200       new->green[i] = old->green[i];
201       new->blue[i] = old->blue[i];
202     }
203     return 0;
204   }
205   return 1;
206 }
207 
process_hsl_v1(dt_dev_pixelpipe_iop_t * piece,const float * const restrict in,float * const restrict out,const dt_iop_roi_t * const roi_out)208 static void process_hsl_v1(dt_dev_pixelpipe_iop_t *piece, const float *const restrict in,
209                            float *const restrict out, const dt_iop_roi_t *const roi_out)
210 {
211   const dt_iop_channelmixer_data_t *data = (dt_iop_channelmixer_data_t *)piece->data;
212   const float *const restrict hsl_matrix = data->hsl_matrix;
213   const float *const restrict rgb_matrix = data->rgb_matrix;
214   const int ch = piece->colors;
215   const size_t pixel_count = (size_t)ch * roi_out->width * roi_out->height;
216 
217 #ifdef _OPENMP
218 #pragma omp parallel for default(none) \
219   dt_omp_firstprivate(ch, pixel_count, hsl_matrix, rgb_matrix, in, out) \
220   schedule(static)
221 #endif
222   for(size_t k = 0; k < pixel_count; k += ch)
223   {
224     float h, s, l, hmix, smix, lmix;
225     float rgb[3];
226 
227     // Calculate the HSL mix
228     hmix = clamp_simd(in[k + 0] * hsl_matrix[0]) + (in[k + 1] * hsl_matrix[1]) + (in[k + 2] * hsl_matrix[2]);
229     smix = clamp_simd(in[k + 0] * hsl_matrix[3]) + (in[k + 1] * hsl_matrix[4]) + (in[k + 2] * hsl_matrix[5]);
230     lmix = clamp_simd(in[k + 0] * hsl_matrix[6]) + (in[k + 1] * hsl_matrix[7]) + (in[k + 2] * hsl_matrix[8]);
231 
232     // If HSL mix is used apply to out[]
233     if(hmix != 0.0f || smix != 0.0f || lmix != 0.0f)
234     {
235       // mix into HSL output channels
236       rgb2hsl(&(in[k]), &h, &s, &l);
237       h = (hmix != 0.0f) ? hmix : h;
238       s = (smix != 0.0f) ? smix : s;
239       l = (lmix != 0.0f) ? lmix : l;
240       hsl2rgb(rgb, h, s, l);
241     }
242     else // no HSL copy in[] to out[]
243     {
244       for(int c = 0; c < 3; c++) rgb[c] = in[k + c];
245     }
246 
247     // Calculate RGB mix
248     for(int i = 0, j = 0; i < 3; i++, j += 3)
249     {
250       out[k + i] = clamp_simd(rgb_matrix[j + 0] * rgb[0]
251                               + rgb_matrix[j + 1] * rgb[1]
252                               + rgb_matrix[j + 2] * rgb[2]);
253     }
254   }
255 }
256 
process_hsl_v2(dt_dev_pixelpipe_iop_t * piece,const float * const restrict in,float * const restrict out,const dt_iop_roi_t * const roi_out)257 static void process_hsl_v2(dt_dev_pixelpipe_iop_t *piece, const float *const restrict in,
258                            float *const restrict out, const dt_iop_roi_t *const roi_out)
259 {
260   const dt_iop_channelmixer_data_t *data = (dt_iop_channelmixer_data_t *)piece->data;
261   const float *const restrict hsl_matrix = data->hsl_matrix;
262   const float *const restrict rgb_matrix = data->rgb_matrix;
263   const int ch = piece->colors;
264   const size_t pixel_count = (size_t)ch * roi_out->width * roi_out->height;
265 
266 #ifdef _OPENMP
267 #pragma omp parallel for default(none) \
268   dt_omp_firstprivate(ch, pixel_count, hsl_matrix, rgb_matrix, in, out) \
269   schedule(static)
270 #endif
271   for(size_t k = 0; k < pixel_count; k += ch)
272   {
273     float rgb[3] = { in[k], in[k + 1], in[k + 2] };
274 
275     float hsl_mix[3];
276     for(int i = 0, j = 0; i < 3; i++, j += 3)
277     {
278       hsl_mix[i] = clamp_simd(hsl_matrix[j + 0] * rgb[0]
279                                + hsl_matrix[j + 1] * rgb[1]
280                                + hsl_matrix[j + 2] * rgb[2]);
281     }
282 
283     // If HSL mix is used apply to out[]
284     if(hsl_mix[0] != 0.0 || hsl_mix[1] != 0.0 || hsl_mix[2] != 0.0)
285     {
286       float hsl[3];
287       // rgb2hsl expects all values to be clipped
288       for(int i = 0; i < 3; i++)
289       {
290         rgb[i] = clamp_simd(rgb[i]);
291       }
292       // mix into HSL output channels
293       rgb2hsl(rgb, &hsl[0], &hsl[1], &hsl[2]);
294       for(int i = 0; i < 3; i++)
295       {
296         hsl[i] = (hsl_mix[i] != 0.0f) ? hsl_mix[i] : hsl[i];
297       }
298       hsl2rgb(rgb, hsl[0], hsl[1], hsl[2]);
299     }
300 
301     // Calculate RGB mix
302     for(int i = 0, j = 0; i < 3; i++, j += 3)
303     {
304       out[k + i] = fmaxf(rgb_matrix[j + 0] * rgb[0]
305                          + rgb_matrix[j + 1] * rgb[1]
306                          + rgb_matrix[j + 2] * rgb[2], 0.0f);
307     }
308   }
309 }
310 
process_rgb(dt_dev_pixelpipe_iop_t * piece,const float * const restrict in,float * const restrict out,const dt_iop_roi_t * const roi_out)311 static void process_rgb(dt_dev_pixelpipe_iop_t *piece, const float *const restrict in,
312                         float *const restrict out, const dt_iop_roi_t *const roi_out)
313 {
314   const dt_iop_channelmixer_data_t *data = (dt_iop_channelmixer_data_t *)piece->data;
315   const float *const restrict rgb_matrix = data->rgb_matrix;
316   const int ch = piece->colors;
317   const size_t pixel_count = (size_t)ch * roi_out->width * roi_out->height;
318 
319 #ifdef _OPENMP
320 #pragma omp parallel for default(none) \
321   dt_omp_firstprivate(ch, pixel_count, rgb_matrix, in, out) \
322   schedule(static)
323 #endif
324   for(size_t k = 0; k < pixel_count; k += ch)
325   {
326     for(int i = 0, j = 0; i < 3; i++, j += 3)
327     {
328       out[k + i] = fmaxf(rgb_matrix[j + 0] * in[k + 0]
329                          + rgb_matrix[j + 1] * in[k + 1]
330                          + rgb_matrix[j + 2] * in[k + 2], 0.0f);
331     }
332   }
333 }
334 
process_gray(dt_dev_pixelpipe_iop_t * piece,const float * const restrict in,float * const restrict out,const dt_iop_roi_t * const roi_out)335 static void process_gray(dt_dev_pixelpipe_iop_t *piece, const float *const restrict in,
336                          float *const restrict out, const dt_iop_roi_t *const roi_out)
337 {
338   const dt_iop_channelmixer_data_t *data = (dt_iop_channelmixer_data_t *)piece->data;
339   const float *const restrict rgb_matrix = data->rgb_matrix;
340   const int ch = piece->colors;
341   const size_t pixel_count = (size_t)ch * roi_out->width * roi_out->height;
342 
343 #ifdef _OPENMP
344 #pragma omp parallel for default(none) \
345   dt_omp_firstprivate(ch, pixel_count, rgb_matrix, in, out) \
346   schedule(static)
347 #endif
348   for(size_t k = 0; k < pixel_count; k += ch)
349   {
350     float gray = fmaxf(rgb_matrix[0] * in[k + 0]
351                        + rgb_matrix[1] * in[k + 1]
352                        + rgb_matrix[2] * in[k + 2], 0.0f);
353     out[k + 0] = gray;
354     out[k + 1] = gray;
355     out[k + 2] = gray;
356   }
357 }
358 
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)359 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
360              void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
361 {
362   const dt_iop_channelmixer_data_t *data = (dt_iop_channelmixer_data_t *)piece->data;
363   switch (data->operation_mode)
364   {
365     case OPERATION_MODE_RGB:
366       process_rgb(piece, (const float *const restrict)ivoid, (float *const restrict)ovoid, roi_out);
367       break;
368     case OPERATION_MODE_GRAY:
369       process_gray(piece, (const float *const restrict)ivoid, (float *const restrict)ovoid, roi_out);
370       break;
371     case OPERATION_MODE_HSL_V1:
372       process_hsl_v1(piece, (const float *const restrict)ivoid, (float *const restrict)ovoid, roi_out);
373       break;
374     case OPERATION_MODE_HSL_V2:
375       process_hsl_v2(piece, (const float *const restrict)ivoid, (float *const restrict)ovoid, roi_out);
376       break;
377     default:
378       break;
379   }
380   if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
381 }
382 
383 #ifdef HAVE_OPENCL
process_cl(struct dt_iop_module_t * self,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)384 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
385                const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
386 {
387   dt_iop_channelmixer_data_t *data = (dt_iop_channelmixer_data_t *)piece->data;
388   dt_iop_channelmixer_global_data_t *gd = (dt_iop_channelmixer_global_data_t *)self->global_data;
389 
390   cl_mem dev_hsl_matrix = NULL;
391   cl_mem dev_rgb_matrix = NULL;
392 
393   cl_int err = -999;
394 
395   const int devid = piece->pipe->devid;
396   const int width = roi_in->width;
397   const int height = roi_in->height;
398 
399   const _channelmixer_operation_mode_t operation_mode = data->operation_mode;
400 
401   size_t sizes[] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
402 
403   dev_hsl_matrix = dt_opencl_copy_host_to_device_constant(devid, sizeof(data->hsl_matrix), data->hsl_matrix);
404   if(dev_hsl_matrix == NULL) goto error;
405   dev_rgb_matrix = dt_opencl_copy_host_to_device_constant(devid, sizeof(data->rgb_matrix), data->rgb_matrix);
406   if(dev_rgb_matrix == NULL) goto error;
407 
408   dt_opencl_set_kernel_arg(devid, gd->kernel_channelmixer, 0, sizeof(cl_mem), (void *)&dev_in);
409   dt_opencl_set_kernel_arg(devid, gd->kernel_channelmixer, 1, sizeof(cl_mem), (void *)&dev_out);
410   dt_opencl_set_kernel_arg(devid, gd->kernel_channelmixer, 2, sizeof(int), (void *)&width);
411   dt_opencl_set_kernel_arg(devid, gd->kernel_channelmixer, 3, sizeof(int), (void *)&height);
412   dt_opencl_set_kernel_arg(devid, gd->kernel_channelmixer, 4, sizeof(int), (void *)&operation_mode);
413   dt_opencl_set_kernel_arg(devid, gd->kernel_channelmixer, 5, sizeof(cl_mem), (void *)&dev_hsl_matrix);
414   dt_opencl_set_kernel_arg(devid, gd->kernel_channelmixer, 6, sizeof(cl_mem), (void *)&dev_rgb_matrix);
415   err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_channelmixer, sizes);
416   if(err != CL_SUCCESS) goto error;
417 
418   dt_opencl_release_mem_object(dev_hsl_matrix);
419   dt_opencl_release_mem_object(dev_rgb_matrix);
420 
421   return TRUE;
422 
423 error:
424   dt_opencl_release_mem_object(dev_hsl_matrix);
425   dt_opencl_release_mem_object(dev_rgb_matrix);
426   dt_print(DT_DEBUG_OPENCL, "[opencl_channelmixer] couldn't enqueue kernel! %d\n", err);
427   return FALSE;
428 }
429 #endif
430 
init_global(dt_iop_module_so_t * module)431 void init_global(dt_iop_module_so_t *module)
432 {
433   const int program = 8; // extended.cl, from programs.conf
434   dt_iop_channelmixer_global_data_t *gd
435       = (dt_iop_channelmixer_global_data_t *)malloc(sizeof(dt_iop_channelmixer_global_data_t));
436   module->data = gd;
437   gd->kernel_channelmixer = dt_opencl_create_kernel(program, "channelmixer");
438 }
439 
cleanup_global(dt_iop_module_so_t * module)440 void cleanup_global(dt_iop_module_so_t *module)
441 {
442   dt_iop_channelmixer_global_data_t *gd = (dt_iop_channelmixer_global_data_t *)module->data;
443   dt_opencl_free_kernel(gd->kernel_channelmixer);
444   free(module->data);
445   module->data = NULL;
446 }
447 
red_callback(GtkWidget * slider,gpointer user_data)448 static void red_callback(GtkWidget *slider, gpointer user_data)
449 {
450   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
451   if(darktable.gui->reset) return;
452   dt_iop_channelmixer_params_t *p = (dt_iop_channelmixer_params_t *)self->params;
453   dt_iop_channelmixer_gui_data_t *g = (dt_iop_channelmixer_gui_data_t *)self->gui_data;
454   const int output_channel_index = dt_bauhaus_combobox_get(g->output_channel);
455   const float value = dt_bauhaus_slider_get(slider);
456   if(output_channel_index >= 0 && value != p->red[output_channel_index])
457   {
458     p->red[output_channel_index] = value;
459     dt_dev_add_history_item(darktable.develop, self, TRUE);
460   }
461 }
462 
green_callback(GtkWidget * slider,gpointer user_data)463 static void green_callback(GtkWidget *slider, gpointer user_data)
464 {
465   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
466   if(darktable.gui->reset) return;
467   dt_iop_channelmixer_params_t *p = (dt_iop_channelmixer_params_t *)self->params;
468   dt_iop_channelmixer_gui_data_t *g = (dt_iop_channelmixer_gui_data_t *)self->gui_data;
469   const int output_channel_index = dt_bauhaus_combobox_get(g->output_channel);
470   const float value = dt_bauhaus_slider_get(slider);
471   if(output_channel_index >= 0 && value != p->green[output_channel_index])
472   {
473     p->green[output_channel_index] = value;
474     dt_dev_add_history_item(darktable.develop, self, TRUE);
475   }
476 }
477 
blue_callback(GtkWidget * slider,gpointer user_data)478 static void blue_callback(GtkWidget *slider, gpointer user_data)
479 {
480   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
481   if(darktable.gui->reset) return;
482   dt_iop_channelmixer_params_t *p = (dt_iop_channelmixer_params_t *)self->params;
483   dt_iop_channelmixer_gui_data_t *g = (dt_iop_channelmixer_gui_data_t *)self->gui_data;
484   const int output_channel_index = dt_bauhaus_combobox_get(g->output_channel);
485   const float value = dt_bauhaus_slider_get(slider);
486   if(output_channel_index >= 0 && value != p->blue[output_channel_index])
487   {
488     p->blue[output_channel_index] = value;
489     dt_dev_add_history_item(darktable.develop, self, TRUE);
490   }
491 }
492 
output_callback(GtkComboBox * combo,gpointer user_data)493 static void output_callback(GtkComboBox *combo, gpointer user_data)
494 {
495   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
496   if(darktable.gui->reset) return;
497   dt_iop_channelmixer_params_t *p = (dt_iop_channelmixer_params_t *)self->params;
498   dt_iop_channelmixer_gui_data_t *g = (dt_iop_channelmixer_gui_data_t *)self->gui_data;
499 
500   const int output_channel_index = dt_bauhaus_combobox_get(g->output_channel);
501   if(output_channel_index >= 0)
502   {
503     dt_bauhaus_slider_set(g->scale_red, p->red[output_channel_index]);
504     dt_bauhaus_slider_set_default(g->scale_red, output_channel_index == CHANNEL_RED ? 1.0 : 0.0);
505     dt_bauhaus_slider_set(g->scale_green, p->green[output_channel_index]);
506     dt_bauhaus_slider_set_default(g->scale_green, output_channel_index == CHANNEL_GREEN ? 1.0 : 0.0);
507     dt_bauhaus_slider_set(g->scale_blue, p->blue[output_channel_index]);
508     dt_bauhaus_slider_set_default(g->scale_blue, output_channel_index == CHANNEL_BLUE ? 1.0 : 0.0);
509   }
510 }
511 
commit_params(struct dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)512 void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
513                    dt_dev_pixelpipe_iop_t *piece)
514 {
515   dt_iop_channelmixer_params_t *p = (dt_iop_channelmixer_params_t *)p1;
516   dt_iop_channelmixer_data_t *d = (dt_iop_channelmixer_data_t *)piece->data;
517 
518   // HSL mixer matrix
519   gboolean hsl_mix_mode = FALSE;
520   for(int i = CHANNEL_HUE, k = 0; i <= CHANNEL_LIGHTNESS; i++, k += 3)
521   {
522     d->hsl_matrix[k + 0] = p->red[i];
523     d->hsl_matrix[k + 1] = p->green[i];
524     d->hsl_matrix[k + 2] = p->blue[i];
525     hsl_mix_mode |= p->red[i] != 0.0f || p->green[i] != 0.0f || p->blue[i] != 0.0f;
526   }
527 
528   // RGB mixer matrix
529   for(int i = CHANNEL_RED, k = 0; i <= CHANNEL_BLUE; i++, k += 3)
530   {
531     d->rgb_matrix[k + 0] = p->red[i];
532     d->rgb_matrix[k + 1] = p->green[i];
533     d->rgb_matrix[k + 2] = p->blue[i];
534   }
535 
536   // Gray
537   float graymix[3] = { p->red[CHANNEL_GRAY], p->green[CHANNEL_GRAY], p->blue[CHANNEL_GRAY] };
538   const gboolean gray_mix_mode = (graymix[0] != 0.0f || graymix[1] != 0.0f || graymix[2] != 0.0f) ? TRUE : FALSE;
539 
540   // Recompute the 3x3 RGB matrix
541   if(gray_mix_mode)
542   {
543     float mixed_gray[3];
544     for(int i = 0; i < 3; i++)
545     {
546       mixed_gray[i] = (graymix[0] * d->rgb_matrix[i]
547                        + graymix[1] * d->rgb_matrix[i + 3]
548                        + graymix[2] * d->rgb_matrix[i + 6]);
549     }
550     for(int i = 0; i < 9; i += 3)
551     {
552       for(int j = 0; j < 3; j++)
553       {
554         d->rgb_matrix[i + j] = mixed_gray[j];
555       }
556     }
557   }
558 
559   if(p->algorithm_version == CHANNEL_MIXER_VERSION_1)
560   {
561     d->operation_mode = OPERATION_MODE_HSL_V1;
562   }
563   else if(hsl_mix_mode)
564   {
565     d->operation_mode = OPERATION_MODE_HSL_V2;
566   }
567   else if(gray_mix_mode)
568   {
569     d->operation_mode = OPERATION_MODE_GRAY;
570   }
571   else
572   {
573     d->operation_mode = OPERATION_MODE_RGB;
574   }
575 }
576 
init_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)577 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
578 {
579   piece->data = calloc(1, sizeof(dt_iop_channelmixer_data_t));
580 }
581 
cleanup_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)582 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
583 {
584   free(piece->data);
585   piece->data = NULL;
586 }
587 
gui_update(struct dt_iop_module_t * self)588 void gui_update(struct dt_iop_module_t *self)
589 {
590   dt_iop_channelmixer_gui_data_t *g = (dt_iop_channelmixer_gui_data_t *)self->gui_data;
591   dt_iop_channelmixer_params_t *p = (dt_iop_channelmixer_params_t *)self->params;
592 
593   const int output_channel_index = dt_bauhaus_combobox_get(g->output_channel);
594   if(output_channel_index >= 0)
595   {
596     dt_bauhaus_slider_set(g->scale_red, p->red[output_channel_index]);
597     dt_bauhaus_slider_set(g->scale_green, p->green[output_channel_index]);
598     dt_bauhaus_slider_set(g->scale_blue, p->blue[output_channel_index]);
599   }
600 }
601 
init(dt_iop_module_t * module)602 void init(dt_iop_module_t *module)
603 {
604   dt_iop_default_init(module);
605 
606   dt_iop_channelmixer_params_t *d = module->default_params;
607 
608   d->algorithm_version = CHANNEL_MIXER_VERSION_2;
609   d->red[CHANNEL_RED] = d->green[CHANNEL_GREEN] = d->blue[CHANNEL_BLUE] = 1.0;
610 }
611 
gui_init(struct dt_iop_module_t * self)612 void gui_init(struct dt_iop_module_t *self)
613 {
614   dt_iop_channelmixer_gui_data_t *g = IOP_GUI_ALLOC(channelmixer);
615   dt_iop_channelmixer_params_t *p = (dt_iop_channelmixer_params_t *)self->default_params;
616 
617   self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
618 
619   /* output */
620   g->output_channel = dt_bauhaus_combobox_new(self);
621   dt_bauhaus_widget_set_label(g->output_channel, NULL, N_("destination"));
622   dt_bauhaus_combobox_add(g->output_channel, _("hue"));
623   dt_bauhaus_combobox_add(g->output_channel, _("saturation"));
624   dt_bauhaus_combobox_add(g->output_channel, _("lightness"));
625   dt_bauhaus_combobox_add(g->output_channel, _("red"));
626   dt_bauhaus_combobox_add(g->output_channel, _("green"));
627   dt_bauhaus_combobox_add(g->output_channel, _("blue"));
628   dt_bauhaus_combobox_add(g->output_channel, C_("channelmixer", "gray"));
629   dt_bauhaus_combobox_set(g->output_channel, CHANNEL_RED);
630   g_signal_connect(G_OBJECT(g->output_channel), "value-changed", G_CALLBACK(output_callback), self);
631 
632   /* red */
633   g->scale_red = dt_bauhaus_slider_new_with_range(self, -2.0, 2.0, 0.005, p->red[CHANNEL_RED], 3);
634   gtk_widget_set_tooltip_text(g->scale_red, _("amount of red channel in the output channel"));
635   dt_bauhaus_widget_set_label(g->scale_red, NULL, N_("red"));
636   g_signal_connect(G_OBJECT(g->scale_red), "value-changed", G_CALLBACK(red_callback), self);
637 
638   /* green */
639   g->scale_green = dt_bauhaus_slider_new_with_range(self, -2.0, 2.0, 0.005, p->green[CHANNEL_RED], 3);
640   gtk_widget_set_tooltip_text(g->scale_green, _("amount of green channel in the output channel"));
641   dt_bauhaus_widget_set_label(g->scale_green, NULL, N_("green"));
642   g_signal_connect(G_OBJECT(g->scale_green), "value-changed", G_CALLBACK(green_callback), self);
643 
644   /* blue */
645   g->scale_blue = dt_bauhaus_slider_new_with_range(self, -2.0, 2.0, 0.005, p->blue[CHANNEL_RED], 3);
646   gtk_widget_set_tooltip_text(g->scale_blue, _("amount of blue channel in the output channel"));
647   dt_bauhaus_widget_set_label(g->scale_blue, NULL, N_("blue"));
648   g_signal_connect(G_OBJECT(g->scale_blue), "value-changed", G_CALLBACK(blue_callback), self);
649 
650 
651   gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->output_channel), TRUE, TRUE, 0);
652 
653   gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->scale_red), TRUE, TRUE, 0);
654   gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->scale_green), TRUE, TRUE, 0);
655   gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->scale_blue), TRUE, TRUE, 0);
656 }
657 
init_presets(dt_iop_module_so_t * self)658 void init_presets(dt_iop_module_so_t *self)
659 {
660   DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "BEGIN", NULL, NULL, NULL);
661 
662   dt_gui_presets_add_generic(_("swap R and B"), self->op, self->version(),
663                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 0, 0, 1, 0 },
664                                                               { 0, 0, 0, 0, 1, 0, 0 },
665                                                               { 0, 0, 0, 1, 0, 0, 0 },
666                                                               CHANNEL_MIXER_VERSION_2 },
667                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
668   dt_gui_presets_add_generic(_("swap G and B"), self->op, self->version(),
669                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0 },
670                                                               { 0, 0, 0, 0, 0, 1, 0 },
671                                                               { 0, 0, 0, 0, 1, 0, 0 },
672                                                               CHANNEL_MIXER_VERSION_2 },
673                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
674   dt_gui_presets_add_generic(_("color contrast boost"), self->op, self->version(),
675                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0.8, 1, 0, 0, 0 },
676                                                               { 0, 0, 0.1, 0, 1, 0, 0 },
677                                                               { 0, 0, 0.1, 0, 0, 1, 0 },
678                                                               CHANNEL_MIXER_VERSION_2 },
679                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
680   dt_gui_presets_add_generic(_("color details boost"), self->op, self->version(),
681                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0.1, 1, 0, 0, 0 },
682                                                               { 0, 0, 0.8, 0, 1, 0, 0 },
683                                                               { 0, 0, 0.1, 0, 0, 1, 0 },
684                                                               CHANNEL_MIXER_VERSION_2 },
685                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
686   dt_gui_presets_add_generic(_("color artifacts boost"), self->op, self->version(),
687                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0.1, 1, 0, 0, 0 },
688                                                               { 0, 0, 0.1, 0, 1, 0, 0 },
689                                                               { 0, 0, 0.8, 0, 0, 1, 0 },
690                                                               CHANNEL_MIXER_VERSION_2 },
691                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
692   dt_gui_presets_add_generic(_("B/W luminance-based"), self->op, self->version(),
693                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.21 },
694                                                               { 0, 0, 0, 0, 1, 0, 0.72 },
695                                                               { 0, 0, 0, 0, 0, 1, 0.07 },
696                                                               CHANNEL_MIXER_VERSION_2 },
697                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
698   dt_gui_presets_add_generic(_("B/W artifacts boost"), self->op, self->version(),
699                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, -0.275 },
700                                                               { 0, 0, 0, 0, 1, 0, -0.275 },
701                                                               { 0, 0, 0, 0, 0, 1, 1.275 },
702                                                               CHANNEL_MIXER_VERSION_2 },
703                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
704   dt_gui_presets_add_generic(_("B/W smooth skin"), self->op, self->version(),
705                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 1.0 },
706                                                               { 0, 0, 0, 0, 1, 0, 0.325 },
707                                                               { 0, 0, 0, 0, 0, 1, -0.4 },
708                                                               CHANNEL_MIXER_VERSION_2 },
709                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
710   dt_gui_presets_add_generic(_("B/W blue artifacts reduce"), self->op, self->version(),
711                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.4 },
712                                                               { 0, 0, 0, 0, 1, 0, 0.750 },
713                                                               { 0, 0, 0, 0, 0, 1, -0.15 },
714                                                               CHANNEL_MIXER_VERSION_2 },
715                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
716 
717   dt_gui_presets_add_generic(_("B/W Ilford Delta 100-400"), self->op, self->version(),
718                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.21 },
719                                                               { 0, 0, 0, 0, 1, 0, 0.42 },
720                                                               { 0, 0, 0, 0, 0, 1, 0.37 },
721                                                               CHANNEL_MIXER_VERSION_2 },
722                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
723 
724   dt_gui_presets_add_generic(_("B/W Ilford Delta 3200"), self->op, self->version(),
725                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.31 },
726                                                               { 0, 0, 0, 0, 1, 0, 0.36 },
727                                                               { 0, 0, 0, 0, 0, 1, 0.33 },
728                                                               CHANNEL_MIXER_VERSION_2 },
729                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
730 
731   dt_gui_presets_add_generic(_("B/W Ilford FP4"), self->op, self->version(),
732                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.28 },
733                                                               { 0, 0, 0, 0, 1, 0, 0.41 },
734                                                               { 0, 0, 0, 0, 0, 1, 0.31 },
735                                                               CHANNEL_MIXER_VERSION_2 },
736                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
737 
738   dt_gui_presets_add_generic(_("B/W Ilford HP5"), self->op, self->version(),
739                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.23 },
740                                                               { 0, 0, 0, 0, 1, 0, 0.37 },
741                                                               { 0, 0, 0, 0, 0, 1, 0.40 },
742                                                               CHANNEL_MIXER_VERSION_2 },
743                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
744 
745   dt_gui_presets_add_generic(_("B/W Ilford SFX"), self->op, self->version(),
746                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.36 },
747                                                               { 0, 0, 0, 0, 1, 0, 0.31 },
748                                                               { 0, 0, 0, 0, 0, 1, 0.33 },
749                                                               CHANNEL_MIXER_VERSION_2 },
750                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
751 
752   dt_gui_presets_add_generic(_("B/W Kodak T-Max 100"), self->op, self->version(),
753                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.24 },
754                                                               { 0, 0, 0, 0, 1, 0, 0.37 },
755                                                               { 0, 0, 0, 0, 0, 1, 0.39 },
756                                                               CHANNEL_MIXER_VERSION_2 },
757                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
758 
759   dt_gui_presets_add_generic(_("B/W Kodak T-max 400"), self->op, self->version(),
760                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.27 },
761                                                               { 0, 0, 0, 0, 1, 0, 0.36 },
762                                                               { 0, 0, 0, 0, 0, 1, 0.37 },
763                                                               CHANNEL_MIXER_VERSION_2 },
764                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
765 
766   dt_gui_presets_add_generic(_("B/W Kodak Tri-X 400"), self->op, self->version(),
767                              &(dt_iop_channelmixer_params_t){ { 0, 0, 0, 1, 0, 0, 0.25 },
768                                                               { 0, 0, 0, 0, 1, 0, 0.35 },
769                                                               { 0, 0, 0, 0, 0, 1, 0.40 },
770                                                               CHANNEL_MIXER_VERSION_2 },
771                              sizeof(dt_iop_channelmixer_params_t), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
772 
773 
774   DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "COMMIT", NULL, NULL, NULL);
775 }
776 
777 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
778 // vim: shiftwidth=2 expandtab tabstop=2 cindent
779 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
780