1 /*
2 This file is part of darktable,
3 Copyright (C) 2020 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
22 #include "bauhaus/bauhaus.h"
23 #include "common/darktable.h"
24 #include "common/opencl.h"
25 #include "control/control.h"
26 #include "develop/develop.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/button.h"
32 #include "dtgtk/resetlabel.h"
33 #include "gui/accelerators.h"
34 #include "gui/gtk.h"
35 #include "gui/presets.h"
36 #include "gui/color_picker_proxy.h"
37 #include "iop/iop_api.h"
38
39 #include <glib.h>
40 #include <math.h>
41 #include <stdlib.h>
42
43 #if defined(__GNUC__)
44 #pragma GCC optimize ("unroll-loops", "tree-loop-if-convert", \
45 "tree-loop-distribution", "no-strict-aliasing", \
46 "loop-interchange", "tree-loop-im", \
47 "unswitch-loops", "tree-loop-ivcanon", "ira-loop-pressure", \
48 "split-ivs-in-unroller", "variable-expansion-in-unroller", \
49 "split-loops", "ivopts", "predictive-commoning",\
50 \
51 "finite-math-only", "fp-contract=fast", "fast-math")
52 #endif
53
54 /** DOCUMENTATION
55 *
56 * This module allows to invert scanned negatives and simulate their print on paper, based on Kodak Cineon
57 * densitometry algorithm. It is better than the old invert module because it takes into account the Dmax of the film
58 * and allows white balance adjustments, as well as paper grade (gamma) simulation. It also allows density correction
59 * in log space, to account for the exposure settings of the scanner. Finally, it is applied after input colour profiling,
60 * which means the inversion happens after the scanner or the camera got color-corrected, while the old invert module
61 * invert the RAW, non-demosaiced, file before any colour correction.
62 *
63 * References :
64 *
65 * - https://www.kodak.com/uploadedfiles/motion/US_plugins_acrobat_en_motion_education_sensitometry_workbook.pdf
66 * - http://www.digital-intermediate.co.uk/film/pdf/Cineon.pdf
67 * - https://lists.gnu.org/archive/html/openexr-devel/2005-03/msg00009.html
68 **/
69
70 #define THRESHOLD 2.3283064365386963e-10f // -32 EV
71
72
73 DT_MODULE_INTROSPECTION(2, dt_iop_negadoctor_params_t)
74
75
76 typedef enum dt_iop_negadoctor_filmstock_t
77 {
78 // What kind of emulsion are we working on ?
79 DT_FILMSTOCK_NB = 0, // $DESCRIPTION: "black and white film"
80 DT_FILMSTOCK_COLOR = 1 // $DESCRIPTION: "color film"
81 } dt_iop_negadoctor_filmstock_t;
82
83
84 typedef struct dt_iop_negadoctor_params_t
85 {
86 dt_iop_negadoctor_filmstock_t film_stock; /* $DEFAULT: DT_FILMSTOCK_COLOR $DESCRIPTION: "film stock" */
87 float Dmin[4]; /* color of film substrate
88 $MIN: 0.00001 $MAX: 1.5 $DEFAULT: 1.0 */
89 float wb_high[4]; /* white balance RGB coeffs (illuminant)
90 $MIN: 0.25 $MAX: 2 $DEFAULT: 1.0 */
91 float wb_low[4]; /* white balance RGB offsets (base light)
92 $MIN: 0.25 $MAX: 2 $DEFAULT: 1.0 */
93 float D_max; /* max density of film
94 $MIN: 0.1 $MAX: 6 $DEFAULT: 2.046 */
95 float offset; /* inversion offset
96 $MIN: -1.0 $MAX: 1.0 $DEFAULT: -0.05 $DESCRIPTION: "scan exposure bias" */
97 float black; /* display black level
98 $MIN: -0.5 $MAX: 0.5 $DEFAULT: 0.0755 $DESCRIPTION: "paper black (density correction)" */
99 float gamma; /* display gamma
100 $MIN: 1.0 $MAX: 8.0 $DEFAULT: 4.0 $DESCRIPTION: "paper grade (gamma)" */
101 float soft_clip; /* highlights roll-off
102 $MIN: 0.0001 $MAX: 1.0 $DEFAULT: 0.75 $DESCRIPTION: "paper gloss (specular highlights)" */
103 float exposure; /* extra exposure
104 $MIN: 0.5 $MAX: 2.0 $DEFAULT: 0.9245 $DESCRIPTION: "print exposure adjustment" */
105 } dt_iop_negadoctor_params_t;
106
107
108 typedef struct dt_iop_negadoctor_data_t
109 {
110 float DT_ALIGNED_PIXEL Dmin[4]; // color of film substrate
111 float DT_ALIGNED_PIXEL wb_high[4]; // white balance RGB coeffs / Dmax
112 float DT_ALIGNED_PIXEL offset[4]; // inversion offset
113 float black; // display black level
114 float gamma; // display gamma
115 float soft_clip; // highlights roll-off
116 float soft_clip_comp; // 1 - softclip, complement to 1
117 float exposure; // extra exposure
118 } dt_iop_negadoctor_data_t;
119
120
121 typedef struct dt_iop_negadoctor_gui_data_t
122 {
123 GtkNotebook *notebook;
124 GtkWidget *film_stock;
125 GtkWidget *Dmin_R, *Dmin_G, *Dmin_B;
126 GtkWidget *wb_high_R, *wb_high_G, *wb_high_B;
127 GtkWidget *wb_low_R, *wb_low_G, *wb_low_B;
128 GtkWidget *D_max;
129 GtkWidget *offset;
130 GtkWidget *black, *gamma, *soft_clip, *exposure;
131 GtkWidget *Dmin_picker, *Dmin_sampler;
132 GtkWidget *WB_high_picker, *WB_high_sampler;
133 GtkWidget *WB_low_picker, *WB_low_sampler;
134 } dt_iop_negadoctor_gui_data_t;
135
136
137 typedef struct dt_iop_negadoctor_global_data_t
138 {
139 int kernel_negadoctor;
140 } dt_iop_negadoctor_global_data_t;
141
142
name()143 const char *name()
144 {
145 return _("negadoctor");
146 }
147
aliases()148 const char *aliases()
149 {
150 return _("film|invert|negative|scan");
151 }
152
description(struct dt_iop_module_t * self)153 const char *description(struct dt_iop_module_t *self)
154 {
155 return dt_iop_set_description(self, _("invert film negative scans and simulate printing on paper"),
156 _("corrective and creative"),
157 _("linear, RGB, display-referred"),
158 _("non-linear, RGB"),
159 _("non-linear, RGB, display-referred"));
160 }
161
flags()162 int flags()
163 {
164 return IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_ONE_INSTANCE;
165 }
166
167
default_group()168 int default_group()
169 {
170 return IOP_GROUP_BASIC | IOP_GROUP_TECHNICAL;
171 }
172
173
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)174 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
175 {
176 return iop_cs_rgb;
177 }
178
legacy_params(dt_iop_module_t * self,const void * const old_params,const int old_version,void * new_params,const int new_version)179 int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
180 void *new_params, const int new_version)
181 {
182 if(old_version == 1 && new_version == 2)
183 {
184 typedef struct dt_iop_negadoctor_params_v1_t
185 {
186 dt_iop_negadoctor_filmstock_t film_stock;
187 float DT_ALIGNED_PIXEL Dmin[4]; // color of film substrate
188 float DT_ALIGNED_PIXEL wb_high[4]; // white balance RGB coeffs (illuminant)
189 float DT_ALIGNED_PIXEL wb_low[4]; // white balance RGB offsets (base light)
190 float D_max; // max density of film
191 float offset; // inversion offset
192 float black; // display black level
193 float gamma; // display gamma
194 float soft_clip; // highlights roll-off
195 float exposure; // extra exposure
196 } dt_iop_negadoctor_params_v1_t;
197
198 dt_iop_negadoctor_params_v1_t *o = (dt_iop_negadoctor_params_v1_t *)old_params;
199 dt_iop_negadoctor_params_t *n = (dt_iop_negadoctor_params_t *)new_params;
200 dt_iop_negadoctor_params_t *d = (dt_iop_negadoctor_params_t *)self->default_params;
201
202 *n = *d; // start with a fresh copy of default parameters
203
204 // WARNING: when copying the arrays in a for loop, gcc wrongly assumed
205 // that n and o were aligned and used AVX instructions for me,
206 // which segfaulted. let's hope this doesn't get optimized too much.
207 n->film_stock = o->film_stock;
208 n->Dmin[0] = o->Dmin[0];
209 n->Dmin[1] = o->Dmin[1];
210 n->Dmin[2] = o->Dmin[2];
211 n->Dmin[3] = o->Dmin[3];
212 n->wb_high[0] = o->wb_high[0];
213 n->wb_high[1] = o->wb_high[1];
214 n->wb_high[2] = o->wb_high[2];
215 n->wb_high[3] = o->wb_high[3];
216 n->wb_low[0] = o->wb_low[0];
217 n->wb_low[1] = o->wb_low[1];
218 n->wb_low[2] = o->wb_low[2];
219 n->wb_low[3] = o->wb_low[3];
220 n->D_max = o->D_max;
221 n->offset = o->offset;
222 n->black = o->black;
223 n->gamma = o->gamma;
224 n->soft_clip = o->soft_clip;
225 n->exposure = o->exposure;
226
227 return 0;
228 }
229 return 1;
230 }
231
commit_params(dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)232 void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
233 dt_dev_pixelpipe_iop_t *piece)
234 {
235 const dt_iop_negadoctor_params_t *const p = (dt_iop_negadoctor_params_t *)p1;
236 dt_iop_negadoctor_data_t *const d = (dt_iop_negadoctor_data_t *)piece->data;
237
238 // keep WB_high even in B&W mode to apply sepia or warm tone look
239 // but premultiply it aheard with Dmax to spare one div per pixel
240 for(size_t c = 0; c < 4; c++) d->wb_high[c] = p->wb_high[c] / p->D_max;
241
242 for(size_t c = 0; c < 4; c++) d->offset[c] = p->wb_high[c] * p->offset * p->wb_low[c];
243
244 // ensure we use a monochrome Dmin for B&W film
245 if(p->film_stock == DT_FILMSTOCK_COLOR)
246 for(size_t c = 0; c < 4; c++) d->Dmin[c] = p->Dmin[c];
247 else if(p->film_stock == DT_FILMSTOCK_NB)
248 for(size_t c = 0; c < 4; c++) d->Dmin[c] = p->Dmin[0];
249
250 // arithmetic trick allowing to rewrite the pixel inversion as FMA
251 d->black = -p->exposure * (1.0f + p->black);
252
253 // highlights soft clip
254 d->soft_clip = p->soft_clip;
255 d->soft_clip_comp = 1.0f - p->soft_clip;
256
257 // copy
258 d->exposure = p->exposure;
259 d->gamma = p->gamma;
260 }
261
262
process(struct dt_iop_module_t * const self,dt_dev_pixelpipe_iop_t * const piece,const void * const restrict ivoid,void * const restrict ovoid,const dt_iop_roi_t * const restrict roi_in,const dt_iop_roi_t * const restrict roi_out)263 void process(struct dt_iop_module_t *const self, dt_dev_pixelpipe_iop_t *const piece,
264 const void *const restrict ivoid, void *const restrict ovoid,
265 const dt_iop_roi_t *const restrict roi_in, const dt_iop_roi_t *const restrict roi_out)
266 {
267 const dt_iop_negadoctor_data_t *const d = piece->data;
268 assert(piece->colors = 4);
269
270 const float *const restrict in = (float *)ivoid;
271 float *const restrict out = (float *)ovoid;
272
273
274 #ifdef _OPENMP
275 #pragma omp parallel for simd default(none) \
276 dt_omp_firstprivate(d, in, out, roi_out) \
277 aligned(in, out:64) collapse(2)
278 #endif
279 for(size_t k = 0; k < (size_t)roi_out->height * roi_out->width * 4; k += 4)
280 {
281 for(size_t c = 0; c < 4; c++)
282 {
283 // Unpack vectors one by one with extra pragmas to be sure the compiler understands they can be vectorized
284 const float *const restrict pix_in = in + k;
285 float *const restrict pix_out = out + k;
286 const float *const restrict Dmin = __builtin_assume_aligned(d->Dmin, 16);
287 const float *const restrict wb_high = __builtin_assume_aligned(d->wb_high, 16);
288 const float *const restrict offset = __builtin_assume_aligned(d->offset, 16);
289
290 // Convert transmission to density using Dmin as a fulcrum
291 const float density = - log10f(Dmin[c] / fmaxf(pix_in[c], THRESHOLD)); // threshold to -32 EV
292
293 // Correct density in log space
294 const float corrected_de = wb_high[c] * density + offset[c];
295
296 // Print density on paper : ((1 - 10^corrected_de + black) * exposure)^gamma rewritten for FMA
297 const float print_linear = -(d->exposure * fast_exp10f(corrected_de) + d->black);
298 const float print_gamma = powf(fmaxf(print_linear, 0.0f), d->gamma); // note : this is always > 0
299
300 // Compress highlights. from https://lists.gnu.org/archive/html/openexr-devel/2005-03/msg00009.html
301 pix_out[c] = (print_gamma > d->soft_clip) ? d->soft_clip + (1.0f - fast_expf(-(print_gamma - d->soft_clip) / d->soft_clip_comp)) * d->soft_clip_comp
302 : print_gamma;
303 }
304 }
305
306 if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK)
307 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
308 }
309
310
311 #ifdef HAVE_OPENCL
process_cl(struct dt_iop_module_t * const self,dt_dev_pixelpipe_iop_t * const piece,cl_mem dev_in,cl_mem dev_out,const dt_iop_roi_t * const restrict roi_in,const dt_iop_roi_t * const restrict roi_out)312 int process_cl(struct dt_iop_module_t *const self, dt_dev_pixelpipe_iop_t *const piece, cl_mem dev_in, cl_mem dev_out,
313 const dt_iop_roi_t *const restrict roi_in, const dt_iop_roi_t *const restrict roi_out)
314 {
315 const dt_iop_negadoctor_data_t *const d = (dt_iop_negadoctor_data_t *)piece->data;
316 const dt_iop_negadoctor_global_data_t *const gd = (dt_iop_negadoctor_global_data_t *)self->global_data;
317
318 cl_int err = -999;
319
320 const int devid = piece->pipe->devid;
321 const int width = roi_in->width;
322 const int height = roi_in->height;
323
324 size_t sizes[] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
325
326 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 0, sizeof(cl_mem), (void *)&dev_in);
327 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 1, sizeof(cl_mem), (void *)&dev_out);
328 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 2, sizeof(int), (void *)&width);
329 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 3, sizeof(int), (void *)&height);
330 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 4, 4 * sizeof(float), (void *)&d->Dmin);
331 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 5, 4 * sizeof(float), (void *)&d->wb_high);
332 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 6, 4 * sizeof(float), (void *)&d->offset);
333 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 7, sizeof(float), (void *)&d->exposure);
334 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 8, sizeof(float), (void *)&d->black);
335 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 9, sizeof(float), (void *)&d->gamma);
336 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 10, sizeof(float), (void *)&d->soft_clip);
337 dt_opencl_set_kernel_arg(devid, gd->kernel_negadoctor, 11, sizeof(float), (void *)&d->soft_clip_comp);
338
339 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_negadoctor, sizes);
340 if(err != CL_SUCCESS) goto error;
341 return TRUE;
342
343 error:
344 dt_print(DT_DEBUG_OPENCL, "[opencl_negadoctor] couldn't enqueue kernel! %d\n", err);
345 return FALSE;
346 }
347 #endif
348
349
init(dt_iop_module_t * module)350 void init(dt_iop_module_t *module)
351 {
352 dt_iop_default_init(module);
353
354 dt_iop_negadoctor_params_t *d = module->default_params;
355
356 d->Dmin[0] = 1.00f;
357 d->Dmin[1] = 0.45f;
358 d->Dmin[2] = 0.25f;
359 }
360
init_presets(dt_iop_module_so_t * self)361 void init_presets(dt_iop_module_so_t *self)
362 {
363 dt_iop_negadoctor_params_t tmp = (dt_iop_negadoctor_params_t){ .film_stock = DT_FILMSTOCK_COLOR,
364 .Dmin = { 1.13f, 0.49f, 0.27f, 0.0f},
365 .wb_high = { 1.0f, 1.0f, 1.0f, 0.0f },
366 .wb_low = { 1.0f, 1.0f, 1.0f, 0.0f },
367 .D_max = 1.6f,
368 .offset = -0.05f,
369 .gamma = 4.0f,
370 .soft_clip = 0.75f,
371 .exposure = 0.9245f,
372 .black = 0.0755f };
373
374
375 dt_gui_presets_add_generic(_("color film"), self->op,
376 self->version(), &tmp, sizeof(tmp), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
377
378 dt_iop_negadoctor_params_t tmq = (dt_iop_negadoctor_params_t){ .film_stock = DT_FILMSTOCK_NB,
379 .Dmin = { 1.0f, 1.0f, 1.0f, 0.0f},
380 .wb_high = { 1.0f, 1.0f, 1.0f, 0.0f },
381 .wb_low = { 1.0f, 1.0f, 1.0f, 0.0f },
382 .D_max = 2.2f,
383 .offset = -0.05f,
384 .gamma = 5.0f,
385 .soft_clip = 0.75f,
386 .exposure = 1.f,
387 .black = 0.0755f };
388
389
390 dt_gui_presets_add_generic(_("black and white film"), self->op,
391 self->version(), &tmq, sizeof(tmq), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
392 }
393
init_global(dt_iop_module_so_t * module)394 void init_global(dt_iop_module_so_t *module)
395 {
396 dt_iop_negadoctor_global_data_t *gd
397 = (dt_iop_negadoctor_global_data_t *)malloc(sizeof(dt_iop_negadoctor_global_data_t));
398
399 module->data = gd;
400 const int program = 30; // negadoctor.cl, from programs.conf
401 gd->kernel_negadoctor = dt_opencl_create_kernel(program, "negadoctor");
402 }
403
cleanup_global(dt_iop_module_so_t * module)404 void cleanup_global(dt_iop_module_so_t *module)
405 {
406 dt_iop_negadoctor_global_data_t *gd = module->data;
407 dt_opencl_free_kernel(gd->kernel_negadoctor);
408 free(module->data);
409 module->data = NULL;
410 }
411
init_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)412 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
413 {
414 piece->data = g_malloc0(sizeof(dt_iop_negadoctor_data_t));
415 }
416
cleanup_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)417 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
418 {
419 g_free(piece->data);
420 piece->data = NULL;
421 }
422
423
424 /* Global GUI stuff */
425
setup_color_variables(dt_iop_negadoctor_gui_data_t * const g,const gint state)426 static void setup_color_variables(dt_iop_negadoctor_gui_data_t *const g, const gint state)
427 {
428 gtk_widget_set_visible(g->Dmin_G, state);
429 gtk_widget_set_visible(g->Dmin_B, state);
430 }
431
432
toggle_stock_controls(dt_iop_module_t * const self)433 static void toggle_stock_controls(dt_iop_module_t *const self)
434 {
435 dt_iop_negadoctor_gui_data_t *const g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
436 const dt_iop_negadoctor_params_t *const p = (dt_iop_negadoctor_params_t *)self->params;
437
438 if(p->film_stock == DT_FILMSTOCK_NB)
439 {
440 // Hide color controls
441 setup_color_variables(g, FALSE);
442 dt_bauhaus_widget_set_label(g->Dmin_R, NULL, N_("D min"));
443 }
444 else if(p->film_stock == DT_FILMSTOCK_COLOR)
445 {
446 // Show color controls
447 setup_color_variables(g, TRUE);
448 dt_bauhaus_widget_set_label(g->Dmin_R, NULL, N_("D min red component"));
449 }
450 else
451 {
452 // We shouldn't be there
453 fprintf(stderr, "negadoctor film stock: undefined behaviour\n");
454 }
455 }
456
457
Dmin_picker_update(dt_iop_module_t * self)458 static void Dmin_picker_update(dt_iop_module_t *self)
459 {
460 dt_iop_negadoctor_gui_data_t *const g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
461 const dt_iop_negadoctor_params_t *const p = (dt_iop_negadoctor_params_t *)self->params;
462
463 GdkRGBA color;
464 color.alpha = 1.0f;
465
466 if(p->film_stock == DT_FILMSTOCK_COLOR)
467 {
468 color.red = p->Dmin[0];
469 color.green = p->Dmin[1];
470 color.blue = p->Dmin[2];
471 }
472 else if(p->film_stock == DT_FILMSTOCK_NB)
473 {
474 color.red = color.green = color.blue = p->Dmin[0];
475 }
476
477 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->Dmin_picker), &color);
478 }
479
Dmin_picker_callback(GtkColorButton * widget,dt_iop_module_t * self)480 static void Dmin_picker_callback(GtkColorButton *widget, dt_iop_module_t *self)
481 {
482 if(darktable.gui->reset) return;
483 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
484 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
485
486 dt_iop_color_picker_reset(self, TRUE);
487
488 GdkRGBA c;
489 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &c);
490 p->Dmin[0] = c.red;
491 p->Dmin[1] = c.green;
492 p->Dmin[2] = c.blue;
493
494 ++darktable.gui->reset;
495 dt_bauhaus_slider_set(g->Dmin_R, p->Dmin[0]);
496 dt_bauhaus_slider_set(g->Dmin_G, p->Dmin[1]);
497 dt_bauhaus_slider_set(g->Dmin_B, p->Dmin[2]);
498 --darktable.gui->reset;
499
500 Dmin_picker_update(self);
501 dt_iop_color_picker_reset(self, TRUE);
502 dt_dev_add_history_item(darktable.develop, self, TRUE);
503 }
504
WB_low_picker_update(dt_iop_module_t * self)505 static void WB_low_picker_update(dt_iop_module_t *self)
506 {
507 dt_iop_negadoctor_gui_data_t *const g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
508 const dt_iop_negadoctor_params_t *const p = (dt_iop_negadoctor_params_t *)self->params;
509
510 GdkRGBA color;
511 color.alpha = 1.0f;
512
513 float WB_low_invert[3];
514 for(size_t c = 0; c < 3; ++c) WB_low_invert[c] = 2.0f - p->wb_low[c];
515 const float WB_low_max = v_maxf(WB_low_invert);
516 for(size_t c = 0; c < 3; ++c) WB_low_invert[c] /= WB_low_max;
517
518 color.red = WB_low_invert[0];
519 color.green = WB_low_invert[1];
520 color.blue = WB_low_invert[2];
521
522 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->WB_low_picker), &color);
523 }
524
WB_low_picker_callback(GtkColorButton * widget,dt_iop_module_t * self)525 static void WB_low_picker_callback(GtkColorButton *widget, dt_iop_module_t *self)
526 {
527 if(darktable.gui->reset) return;
528 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
529 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
530
531 dt_iop_color_picker_reset(self, TRUE);
532
533 GdkRGBA c;
534 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &c);
535
536 float RGB[3];
537 RGB[0] = 2.0f - c.red;
538 RGB[1] = 2.0f - c.green;
539 RGB[2] = 2.0f - c.blue;
540
541 float RGB_min = v_minf(RGB);
542 for(size_t k = 0; k < 3; k++) p->wb_low[k] = RGB[k] / RGB_min;
543
544 ++darktable.gui->reset;
545 dt_bauhaus_slider_set(g->wb_low_R, p->wb_low[0]);
546 dt_bauhaus_slider_set(g->wb_low_G, p->wb_low[1]);
547 dt_bauhaus_slider_set(g->wb_low_B, p->wb_low[2]);
548 --darktable.gui->reset;
549
550 WB_low_picker_update(self);
551 dt_iop_color_picker_reset(self, TRUE);
552 dt_dev_add_history_item(darktable.develop, self, TRUE);
553 }
554
555
WB_high_picker_update(dt_iop_module_t * self)556 static void WB_high_picker_update(dt_iop_module_t *self)
557 {
558 dt_iop_negadoctor_gui_data_t *const g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
559 const dt_iop_negadoctor_params_t *const p = (dt_iop_negadoctor_params_t *)self->params;
560
561 GdkRGBA color;
562 color.alpha = 1.0f;
563
564 float WB_high_invert[3];
565 for(size_t c = 0; c < 3; ++c) WB_high_invert[c] = 2.0f - p->wb_high[c];
566 const float WB_high_max = v_maxf(WB_high_invert);
567 for(size_t c = 0; c < 3; ++c) WB_high_invert[c] /= WB_high_max;
568
569 color.red = WB_high_invert[0];
570 color.green = WB_high_invert[1];
571 color.blue = WB_high_invert[2];
572
573 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->WB_high_picker), &color);
574 }
575
WB_high_picker_callback(GtkColorButton * widget,dt_iop_module_t * self)576 static void WB_high_picker_callback(GtkColorButton *widget, dt_iop_module_t *self)
577 {
578 if(darktable.gui->reset) return;
579 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
580 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
581
582 dt_iop_color_picker_reset(self, TRUE);
583
584 GdkRGBA c;
585 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &c);
586
587 float RGB[3];
588 RGB[0] = 2.0f - c.red;
589 RGB[1] = 2.0f - c.green;
590 RGB[2] = 2.0f - c.blue;
591
592 float RGB_min = v_minf(RGB);
593 for(size_t k = 0; k < 3; k++) p->wb_high[k] = RGB[k] / RGB_min;
594
595 ++darktable.gui->reset;
596 dt_bauhaus_slider_set(g->wb_high_R, p->wb_high[0]);
597 dt_bauhaus_slider_set(g->wb_high_G, p->wb_high[1]);
598 dt_bauhaus_slider_set(g->wb_high_B, p->wb_high[2]);
599 --darktable.gui->reset;
600
601 WB_high_picker_update(self);
602 dt_iop_color_picker_reset(self, TRUE);
603 dt_dev_add_history_item(darktable.develop, self, TRUE);
604 }
605
606
607 /* Color pickers auto-tuners */
608
609 // measure Dmin from the film edges first
apply_auto_Dmin(dt_iop_module_t * self)610 static void apply_auto_Dmin(dt_iop_module_t *self)
611 {
612 if(darktable.gui->reset) return;
613 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
614 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
615
616 for(int k = 0; k < 4; k++) p->Dmin[k] = self->picked_color[k];
617
618 ++darktable.gui->reset;
619 dt_bauhaus_slider_set(g->Dmin_R, p->Dmin[0]);
620 dt_bauhaus_slider_set(g->Dmin_G, p->Dmin[1]);
621 dt_bauhaus_slider_set(g->Dmin_B, p->Dmin[2]);
622 --darktable.gui->reset;
623
624 Dmin_picker_update(self);
625 dt_control_queue_redraw_widget(self->widget);
626 dt_dev_add_history_item(darktable.develop, self, TRUE);
627 }
628
629 // from Dmin, find out the range of density values of the film and compute Dmax
apply_auto_Dmax(dt_iop_module_t * self)630 static void apply_auto_Dmax(dt_iop_module_t *self)
631 {
632 if(darktable.gui->reset) return;
633 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
634 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
635
636 float RGB[3];
637 for(int c = 0; c < 3; c++)
638 {
639 RGB[c] = log10f(p->Dmin[c] / fmaxf(self->picked_color_min[c], THRESHOLD));
640 }
641
642 // Take the max(RGB) for safety. Big values unclip whites
643 p->D_max = v_maxf(RGB);
644
645 ++darktable.gui->reset;
646 dt_bauhaus_slider_set(g->D_max, p->D_max);
647 --darktable.gui->reset;
648
649 dt_control_queue_redraw_widget(self->widget);
650 dt_dev_add_history_item(darktable.develop, self, TRUE);
651 }
652
653 // from Dmax, compute the offset so the range of density is rescaled between [0; 1]
apply_auto_offset(dt_iop_module_t * self)654 static void apply_auto_offset(dt_iop_module_t *self)
655 {
656 if(darktable.gui->reset) return;
657 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
658 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
659
660 float RGB[3];
661 for(int c = 0; c < 3; c++)
662 RGB[c] = log10f(p->Dmin[c] / fmaxf(self->picked_color_max[c], THRESHOLD)) / p->D_max;
663
664 // Take the min(RGB) for safety. Negative values unclip blacks
665 p->offset = v_minf(RGB);
666
667 ++darktable.gui->reset;
668 dt_bauhaus_slider_set(g->offset, p->offset);
669 --darktable.gui->reset;
670
671 dt_control_queue_redraw_widget(self->widget);
672 dt_dev_add_history_item(darktable.develop, self, TRUE);
673 }
674
675 // from Dmax and offset, compute the white balance correction as multipliers of the offset
676 // such that offset × wb[c] make black monochrome
apply_auto_WB_low(dt_iop_module_t * self)677 static void apply_auto_WB_low(dt_iop_module_t *self)
678 {
679 if(darktable.gui->reset) return;
680 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
681 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
682
683 float RGB_min[3];
684 for(int c = 0; c < 3; c++)
685 RGB_min[c] = log10f(p->Dmin[c] / fmaxf(self->picked_color[c], THRESHOLD)) / p->D_max;
686
687 const float RGB_v_min = v_minf(RGB_min); // warning: can be negative
688 for(int c = 0; c < 3; c++) p->wb_low[c] = RGB_v_min / RGB_min[c];
689
690 ++darktable.gui->reset;
691 dt_bauhaus_slider_set(g->wb_low_R, p->wb_low[0]);
692 dt_bauhaus_slider_set(g->wb_low_G, p->wb_low[1]);
693 dt_bauhaus_slider_set(g->wb_low_B, p->wb_low[2]);
694 --darktable.gui->reset;
695
696 WB_low_picker_update(self);
697 dt_control_queue_redraw_widget(self->widget);
698 dt_dev_add_history_item(darktable.develop, self, TRUE);
699 }
700
701 // from Dmax, offset and white balance multipliers, compute the white balance of the illuminant as multipliers of 1/Dmax
702 // such that WB[c] / Dmax make white monochrome
apply_auto_WB_high(dt_iop_module_t * self)703 static void apply_auto_WB_high(dt_iop_module_t *self)
704 {
705 if(darktable.gui->reset) return;
706 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
707 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
708
709 float RGB_min[3];
710 for(int c = 0; c < 3; c++)
711 RGB_min[c] = fabsf(-1.0f / (p->offset * p->wb_low[c] - log10f(p->Dmin[c] / fmaxf(self->picked_color[c], THRESHOLD)) / p->D_max));
712
713 const float RGB_v_min = v_minf(RGB_min); // warning : must be positive
714 for(int c = 0; c < 3; c++) p->wb_high[c] = RGB_min[c] / RGB_v_min;
715
716 ++darktable.gui->reset;
717 dt_bauhaus_slider_set(g->wb_high_R, p->wb_high[0]);
718 dt_bauhaus_slider_set(g->wb_high_G, p->wb_high[1]);
719 dt_bauhaus_slider_set(g->wb_high_B, p->wb_high[2]);
720 --darktable.gui->reset;
721
722 WB_high_picker_update(self);
723 dt_control_queue_redraw_widget(self->widget);
724 dt_dev_add_history_item(darktable.develop, self, TRUE);
725 }
726
727 // from Dmax, offset and both white balances, compute the print black adjustment
728 // such that the printed values range from 0 to + infinity
apply_auto_black(dt_iop_module_t * self)729 static void apply_auto_black(dt_iop_module_t *self)
730 {
731 if(darktable.gui->reset) return;
732 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
733 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
734
735 float RGB[3];
736 for(int c = 0; c < 3; c++)
737 {
738 RGB[c] = -log10f(p->Dmin[c] / fmaxf(self->picked_color_max[c], THRESHOLD));
739 RGB[c] *= p->wb_high[c] / p->D_max;
740 RGB[c] += p->wb_low[c] * p->offset * p->wb_high[c];
741 RGB[c] = 0.1f - (1.0f - fast_exp10f(RGB[c])); // actually, remap between -3.32 EV and infinity for safety because gamma comes later
742 }
743 p->black = v_maxf(RGB);
744
745 ++darktable.gui->reset;
746 dt_bauhaus_slider_set(g->black, p->black);
747 --darktable.gui->reset;
748
749 dt_control_queue_redraw_widget(self->widget);
750 dt_dev_add_history_item(darktable.develop, self, TRUE);
751 }
752
753 // from Dmax, offset, both white balances, and printblack, compute the print exposure adjustment as a scaling factor
754 // such that the printed values range from 0 to 1
apply_auto_exposure(dt_iop_module_t * self)755 static void apply_auto_exposure(dt_iop_module_t *self)
756 {
757 if(darktable.gui->reset) return;
758 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
759 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
760
761 float RGB[3];
762 for(int c = 0; c < 3; c++)
763 {
764 RGB[c] = -log10f(p->Dmin[c] / fmaxf(self->picked_color_min[c], THRESHOLD));
765 RGB[c] *= p->wb_high[c] / p->D_max;
766 RGB[c] += p->wb_low[c] * p->offset;
767 RGB[c] = 0.96f / (1.0f - fast_exp10f(RGB[c]) + p->black); // actually, remap in [0; 0.96] for safety
768 }
769 p->exposure = v_minf(RGB);
770
771 ++darktable.gui->reset;
772 dt_bauhaus_slider_set(g->exposure, log2f(p->exposure));
773 --darktable.gui->reset;
774
775 dt_control_queue_redraw_widget(self->widget);
776 dt_dev_add_history_item(darktable.develop, self, TRUE);
777 }
778
779
color_picker_apply(dt_iop_module_t * self,GtkWidget * picker,dt_dev_pixelpipe_iop_t * piece)780 void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_iop_t *piece)
781 {
782 if(darktable.gui->reset) return;
783 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
784
785 if (picker == g->Dmin_sampler)
786 apply_auto_Dmin(self);
787 else if(picker == g->WB_high_sampler)
788 apply_auto_WB_high(self);
789 else if(picker == g->offset)
790 apply_auto_offset(self);
791 else if(picker == g->D_max)
792 apply_auto_Dmax(self);
793 else if(picker == g->WB_low_sampler)
794 apply_auto_WB_low(self);
795 else if(picker == g->exposure)
796 apply_auto_exposure(self);
797 else if(picker == g->black)
798 apply_auto_black(self);
799 else
800 fprintf(stderr, "[negadoctor] unknown color picker\n");
801 }
802
gui_init(dt_iop_module_t * self)803 void gui_init(dt_iop_module_t *self)
804 {
805 dt_iop_negadoctor_gui_data_t *g = IOP_GUI_ALLOC(negadoctor);
806
807 g->notebook = GTK_NOTEBOOK(gtk_notebook_new());
808
809 // Page FILM PROPERTIES
810 GtkWidget *page1 = self->widget = dt_ui_notebook_page(g->notebook, _("film properties"), NULL);
811
812 // Dmin
813
814 gtk_box_pack_start(GTK_BOX(page1), dt_ui_section_label_new(_("color of the film base")), FALSE, FALSE, 0);
815
816 GtkWidget *row1 = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
817
818 g->Dmin_picker = gtk_color_button_new();
819 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(g->Dmin_picker), FALSE);
820 gtk_color_button_set_title(GTK_COLOR_BUTTON(g->Dmin_picker), _("select color of film material from a swatch"));
821 gtk_box_pack_start(GTK_BOX(row1), GTK_WIDGET(g->Dmin_picker), TRUE, TRUE, 0);
822 g_signal_connect(G_OBJECT(g->Dmin_picker), "color-set", G_CALLBACK(Dmin_picker_callback), self);
823
824 g->Dmin_sampler = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, row1);
825 gtk_widget_set_tooltip_text(g->Dmin_sampler , _("pick color of film material from image"));
826
827 gtk_box_pack_start(GTK_BOX(page1), GTK_WIDGET(row1), FALSE, FALSE, 0);
828
829 g->Dmin_R = dt_bauhaus_slider_from_params(self, "Dmin[0]");
830 dt_bauhaus_slider_set_digits(g->Dmin_R, 4);
831 dt_bauhaus_slider_set_step(g->Dmin_R, 0.0025);
832 dt_bauhaus_slider_set_format(g->Dmin_R, "%.2f %%");
833 dt_bauhaus_slider_set_factor(g->Dmin_R, 100);
834 dt_bauhaus_widget_set_label(g->Dmin_R, NULL, N_("D min red component"));
835 gtk_widget_set_tooltip_text(g->Dmin_R, _("adjust the color and shade of the film transparent base.\n"
836 "this value depends on the film material, \n"
837 "the chemical fog produced while developing the film,\n"
838 "and the scanner white balance."));
839
840 g->Dmin_G = dt_bauhaus_slider_from_params(self, "Dmin[1]");
841 dt_bauhaus_slider_set_digits(g->Dmin_G, 4);
842 dt_bauhaus_slider_set_step(g->Dmin_G, 0.0025);
843 dt_bauhaus_slider_set_format(g->Dmin_G, "%.2f %%");
844 dt_bauhaus_slider_set_factor(g->Dmin_G, 100);
845 dt_bauhaus_widget_set_label(g->Dmin_G, NULL, N_("D min green component"));
846 gtk_widget_set_tooltip_text(g->Dmin_G, _("adjust the color and shade of the film transparent base.\n"
847 "this value depends on the film material, \n"
848 "the chemical fog produced while developing the film,\n"
849 "and the scanner white balance."));
850
851 g->Dmin_B = dt_bauhaus_slider_from_params(self, "Dmin[2]");
852 dt_bauhaus_slider_set_digits(g->Dmin_B, 4);
853 dt_bauhaus_slider_set_step(g->Dmin_B, 0.0025);
854 dt_bauhaus_slider_set_format(g->Dmin_B, "%.2f %%");
855 dt_bauhaus_slider_set_factor(g->Dmin_B, 100);
856 dt_bauhaus_widget_set_label(g->Dmin_B, NULL, N_("D min blue component"));
857 gtk_widget_set_tooltip_text(g->Dmin_B, _("adjust the color and shade of the film transparent base.\n"
858 "this value depends on the film material, \n"
859 "the chemical fog produced while developing the film,\n"
860 "and the scanner white balance."));
861
862 // D max and scanner bias
863
864 gtk_box_pack_start(GTK_BOX(page1), dt_ui_section_label_new(_("dynamic range of the film")), FALSE, FALSE, 0);
865
866 g->D_max = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "D_max"));
867 dt_bauhaus_slider_set_format(g->D_max, "%.2f dB");
868 gtk_widget_set_tooltip_text(g->D_max, _("maximum density of the film, corresponding to white after inversion.\n"
869 "this value depends on the film specifications, the developing process,\n"
870 "the dynamic range of the scene and the scanner exposure settings."));
871
872 gtk_box_pack_start(GTK_BOX(page1), dt_ui_section_label_new(_("scanner exposure settings")), FALSE, FALSE, 0);
873
874 g->offset = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "offset"));
875 dt_bauhaus_slider_set_format(g->offset, "%+.2f dB");
876 dt_color_picker_new(self, DT_COLOR_PICKER_AREA, g->offset);
877 gtk_widget_set_tooltip_text(g->offset, _("correct the exposure of the scanner, for all RGB channels,\n"
878 "before the inversion, so blacks are neither clipped or too pale."));
879
880 // Page CORRECTIONS
881 GtkWidget *page2 = self->widget = dt_ui_notebook_page(g->notebook, _("corrections"), NULL);
882
883 // WB shadows
884 gtk_box_pack_start(GTK_BOX(page2), dt_ui_section_label_new(_("shadows color cast")), FALSE, FALSE, 0);
885
886 GtkWidget *row3 = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
887
888 g->WB_low_picker = gtk_color_button_new();
889 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(g->WB_low_picker), FALSE);
890 gtk_color_button_set_title(GTK_COLOR_BUTTON(g->WB_low_picker), _("select color of shadows from a swatch"));
891 gtk_box_pack_start(GTK_BOX(row3), GTK_WIDGET(g->WB_low_picker), TRUE, TRUE, 0);
892 g_signal_connect(G_OBJECT(g->WB_low_picker), "color-set", G_CALLBACK(WB_low_picker_callback), self);
893
894 g->WB_low_sampler = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, row3);
895 gtk_widget_set_tooltip_text(g->WB_low_sampler, _("pick shadows color from image"));
896
897 gtk_box_pack_start(GTK_BOX(page2), GTK_WIDGET(row3), FALSE, FALSE, 0);
898
899 g->wb_low_R = dt_bauhaus_slider_from_params(self, "wb_low[0]");
900 dt_bauhaus_widget_set_label(g->wb_low_R, NULL, N_("shadows red offset"));
901 gtk_widget_set_tooltip_text(g->wb_low_R, _("correct the color cast in shadows so blacks are\n"
902 "truly achromatic. Setting this value before\n"
903 "the highlights illuminant white balance will help\n"
904 "recovering the global white balance in difficult cases."));
905
906 g->wb_low_G = dt_bauhaus_slider_from_params(self, "wb_low[1]");
907 dt_bauhaus_widget_set_label(g->wb_low_G, NULL, N_("shadows green offset"));
908 gtk_widget_set_tooltip_text(g->wb_low_G, _("correct the color cast in shadows so blacks are\n"
909 "truly achromatic. Setting this value before\n"
910 "the highlights illuminant white balance will help\n"
911 "recovering the global white balance in difficult cases."));
912
913 g->wb_low_B = dt_bauhaus_slider_from_params(self, "wb_low[2]");
914 dt_bauhaus_widget_set_label(g->wb_low_B, NULL, N_("shadows blue offset"));
915 gtk_widget_set_tooltip_text(g->wb_low_B, _("correct the color cast in shadows so blacks are\n"
916 "truly achromatic. Setting this value before\n"
917 "the highlights illuminant white balance will help\n"
918 "recovering the global white balance in difficult cases."));
919
920 // WB highlights
921 gtk_box_pack_start(GTK_BOX(page2), dt_ui_section_label_new(_("highlights white balance")), FALSE, FALSE, 0);
922
923 GtkWidget *row2 = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
924
925 g->WB_high_picker = gtk_color_button_new();
926 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(g->WB_high_picker), FALSE);
927 gtk_color_button_set_title(GTK_COLOR_BUTTON(g->WB_high_picker), _("select color of illuminant from a swatch"));
928 gtk_box_pack_start(GTK_BOX(row2), GTK_WIDGET(g->WB_high_picker), TRUE, TRUE, 0);
929 g_signal_connect(G_OBJECT(g->WB_high_picker), "color-set", G_CALLBACK(WB_high_picker_callback), self);
930
931 g->WB_high_sampler = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, row2);
932 gtk_widget_set_tooltip_text(g->WB_high_sampler , _("pick illuminant color from image"));
933
934 gtk_box_pack_start(GTK_BOX(page2), GTK_WIDGET(row2), FALSE, FALSE, 0);
935
936 g->wb_high_R = dt_bauhaus_slider_from_params(self, "wb_high[0]");
937 dt_bauhaus_widget_set_label(g->wb_high_R, NULL, N_("illuminant red gain"));
938 gtk_widget_set_tooltip_text(g->wb_high_R, _("correct the color of the illuminant so whites are\n"
939 "truly achromatic. Setting this value after\n"
940 "the shadows color cast will help\n"
941 "recovering the global white balance in difficult cases."));
942
943 g->wb_high_G = dt_bauhaus_slider_from_params(self, "wb_high[1]");
944 dt_bauhaus_widget_set_label(g->wb_high_G, NULL, N_("illuminant green gain"));
945 gtk_widget_set_tooltip_text(g->wb_high_G, _("correct the color of the illuminant so whites are\n"
946 "truly achromatic. Setting this value after\n"
947 "the shadows color cast will help\n"
948 "recovering the global white balance in difficult cases."));
949
950 g->wb_high_B = dt_bauhaus_slider_from_params(self, "wb_high[2]");
951 dt_bauhaus_widget_set_label(g->wb_high_B, NULL, N_("illuminant blue gain"));
952 gtk_widget_set_tooltip_text(g->wb_high_B, _("correct the color of the illuminant so whites are\n"
953 "truly achromatic. Setting this value after\n"
954 "the shadows color cast will help\n"
955 "recovering the global white balance in difficult cases."));
956
957 // Page PRINT PROPERTIES
958 GtkWidget *page3 = self->widget = dt_ui_notebook_page(g->notebook, _("print properties"), NULL);
959
960 // print corrections
961 gtk_box_pack_start(GTK_BOX(page3), dt_ui_section_label_new(_("virtual paper properties")), FALSE, FALSE, 0);
962
963 g->black = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "black"));
964 dt_bauhaus_slider_set_digits(g->black, 4);
965 dt_bauhaus_slider_set_step(g->black, 0.0005);
966 dt_bauhaus_slider_set_factor(g->black, 100);
967 dt_bauhaus_slider_set_format(g->black, "%+.2f %%");
968 gtk_widget_set_tooltip_text(g->black, _("correct the density of black after the inversion,\n"
969 "to adjust the global contrast while avoiding clipping shadows."));
970
971 g->gamma = dt_bauhaus_slider_from_params(self, "gamma");
972 dt_bauhaus_widget_set_label(g->gamma, NULL, N_("paper grade (gamma)"));
973 gtk_widget_set_tooltip_text(g->gamma, _("select the grade of the virtual paper, which is actually\n"
974 "equivalent to applying a gamma. it compensates the film D max\n"
975 "and recovers the contrast. use a high grade for high D max."));
976
977 g->soft_clip = dt_bauhaus_slider_from_params(self, "soft_clip");
978 dt_bauhaus_slider_set_factor(g->soft_clip, 100);
979 dt_bauhaus_slider_set_digits(g->soft_clip, 4);
980 dt_bauhaus_slider_set_format(g->soft_clip, "%.2f %%");
981 gtk_widget_set_tooltip_text(g->soft_clip, _("gradually compress specular highlights past this value\n"
982 "to avoid clipping while pushing the exposure for midtones.\n"
983 "this somewhat reproduces the behaviour of matte paper."));
984
985 gtk_box_pack_start(GTK_BOX(page3), dt_ui_section_label_new(_("virtual print emulation")), FALSE, FALSE, 0);
986
987 g->exposure = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "exposure"));
988 dt_bauhaus_slider_set_hard_min(g->exposure, -1.0);
989 dt_bauhaus_slider_set_soft_min(g->exposure, -1.0);
990 dt_bauhaus_slider_set_hard_max(g->exposure, 1.0);
991 dt_bauhaus_slider_set_default(g->exposure, 0.0);
992 dt_bauhaus_slider_set_format(g->exposure, "%+.2f EV");
993 gtk_widget_set_tooltip_text(g->exposure, _("correct the printing exposure after inversion to adjust\n"
994 "the global contrast and avoid clipping highlights."));
995
996 // start building top level widget
997 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
998
999 // Film emulsion
1000 g->film_stock = dt_bauhaus_combobox_from_params(self, "film_stock");
1001 gtk_widget_set_tooltip_text(g->film_stock, _("toggle on or off the color controls"));
1002
1003 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->notebook), FALSE, FALSE, 0);
1004 }
1005
1006
gui_changed(dt_iop_module_t * self,GtkWidget * w,void * previous)1007 void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1008 {
1009 dt_iop_negadoctor_params_t *p = (dt_iop_negadoctor_params_t *)self->params;
1010 dt_iop_negadoctor_gui_data_t *g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
1011 if(!w || w == g->film_stock)
1012 {
1013 toggle_stock_controls(self);
1014 Dmin_picker_update(self);
1015 }
1016 else if(w == g->Dmin_R && p->film_stock == DT_FILMSTOCK_NB)
1017 {
1018 dt_bauhaus_slider_set(g->Dmin_G, p->Dmin[0]);
1019 dt_bauhaus_slider_set(g->Dmin_B, p->Dmin[0]);
1020 }
1021 else if(w == g->Dmin_R || w == g->Dmin_G || w == g->Dmin_B)
1022 {
1023 Dmin_picker_update(self);
1024 }
1025 else if(w == g->exposure)
1026 {
1027 p->exposure = powf(2.0f, p->exposure);
1028 }
1029
1030 if(!w || w == g->wb_high_R || w == g->wb_high_G || w == g->wb_high_B)
1031 {
1032 WB_high_picker_update(self);
1033 }
1034
1035 if(!w || w == g->wb_low_R || w == g->wb_low_G || w == g->wb_low_B)
1036 {
1037 WB_low_picker_update(self);
1038 }
1039 }
1040
1041
gui_update(dt_iop_module_t * const self)1042 void gui_update(dt_iop_module_t *const self)
1043 {
1044 // let gui slider match current parameters:
1045 dt_iop_negadoctor_gui_data_t *const g = (dt_iop_negadoctor_gui_data_t *)self->gui_data;
1046 const dt_iop_negadoctor_params_t *const p = (dt_iop_negadoctor_params_t *)self->params;
1047
1048 dt_iop_color_picker_reset(self, TRUE);
1049
1050 self->color_picker_box[0] = self->color_picker_box[1] = .10f;
1051 self->color_picker_box[2] = self->color_picker_box[3] = .50f;
1052 self->color_picker_point[0] = self->color_picker_point[1] = 0.5f;
1053
1054 dt_bauhaus_combobox_set(g->film_stock, p->film_stock);
1055
1056 // Dmin
1057 dt_bauhaus_slider_set(g->Dmin_R, p->Dmin[0]);
1058 dt_bauhaus_slider_set(g->Dmin_G, p->Dmin[1]);
1059 dt_bauhaus_slider_set(g->Dmin_B, p->Dmin[2]);
1060
1061 // Dmax
1062 dt_bauhaus_slider_set(g->D_max, p->D_max);
1063
1064 // Scanner exposure offset
1065 dt_bauhaus_slider_set(g->offset, p->offset);
1066
1067 // WB_high
1068 dt_bauhaus_slider_set(g->wb_high_R, p->wb_high[0]);
1069 dt_bauhaus_slider_set(g->wb_high_G, p->wb_high[1]);
1070 dt_bauhaus_slider_set(g->wb_high_B, p->wb_high[2]);
1071
1072 // WB_low
1073 dt_bauhaus_slider_set(g->wb_low_R, p->wb_low[0]);
1074 dt_bauhaus_slider_set(g->wb_low_G, p->wb_low[1]);
1075 dt_bauhaus_slider_set(g->wb_low_B, p->wb_low[2]);
1076
1077 // Print
1078 dt_bauhaus_slider_set(g->exposure, log2f(p->exposure)); // warning: GUI is in EV
1079 dt_bauhaus_slider_set(g->black, p->black);
1080 dt_bauhaus_slider_set(g->gamma, p->gamma);
1081 dt_bauhaus_slider_set(g->soft_clip, p->soft_clip);
1082
1083 // Update custom stuff
1084 gui_changed(self, NULL, NULL);
1085 }
1086
gui_reset(dt_iop_module_t * self)1087 void gui_reset(dt_iop_module_t *self)
1088 {
1089 dt_iop_color_picker_reset(self, TRUE);
1090 }
1091