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