1 /*
2 This file is part of darktable,
3 Copyright (C) 2018-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_inline_conversions.h"
23 #include "common/darktable.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_math.h"
29 #include "dtgtk/button.h"
30 #include "dtgtk/drawingarea.h"
31 #include "dtgtk/expander.h"
32 #include "dtgtk/paint.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
40 #include "develop/imageop.h"
41 #include "gui/draw.h"
42
43 #include <assert.h>
44 #include <math.h>
45 #include <stdlib.h>
46 #include <string.h>
47
48 #ifdef __SSE2__
49 #include "common/sse.h"
50 #endif
51
52 #define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(1)
53
54
55 DT_MODULE_INTROSPECTION(3, dt_iop_filmic_params_t)
56
57 /**
58 * DOCUMENTATION
59 *
60 * This code ports :
61 * 1. Troy Sobotka's filmic curves for Blender (and other softs)
62 * https://github.com/sobotka/OpenAgX/blob/master/lib/agx_colour.py
63 * 2. ACES camera logarithmic encoding
64 * https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESutil.Lin_to_Log2_param.ctl
65 *
66 * The ACES log implementation is taken from the profile_gamma.c IOP
67 * where it works in camera RGB space. Here, it works on an arbitrary RGB
68 * space. ProPhotoRGB has been chosen for its wide gamut coverage and
69 * for conveniency because it's already in darktable's libs. Any other
70 * RGB working space could work. This chouice could (should) also be
71 * exposed to the user.
72 *
73 * The filmic curves are tonecurves intended to simulate the luminance
74 * transfer function of film with "S" curves. These could be reproduced in
75 * the tonecurve.c IOP, however what we offer here is a parametric
76 * interface useful to remap accurately and promptly the middle grey
77 * to any arbitrary value chosen accordingly to the destination space.
78 *
79 * The combined use of both define a modern way to deal with large
80 * dynamic range photographs by remapping the values with a comprehensive
81 * interface avoiding many of the back and forth adjustments darktable
82 * is prone to enforce.
83 *
84 * */
85
86 typedef struct dt_iop_filmic_params_t
87 {
88 float grey_point_source;
89 float black_point_source;
90 float white_point_source;
91 float security_factor;
92 float grey_point_target;
93 float black_point_target;
94 float white_point_target;
95 float output_power;
96 float latitude_stops;
97 float contrast;
98 float saturation;
99 float global_saturation;
100 float balance;
101 int interpolator;
102 int preserve_color;
103 } dt_iop_filmic_params_t;
104
105 typedef struct dt_iop_filmic_gui_data_t
106 {
107 GtkWidget *white_point_source;
108 GtkWidget *grey_point_source;
109 GtkWidget *black_point_source;
110 GtkWidget *security_factor;
111 GtkWidget *auto_button;
112 GtkWidget *grey_point_target;
113 GtkWidget *white_point_target;
114 GtkWidget *black_point_target;
115 GtkWidget *output_power;
116 GtkWidget *latitude_stops;
117 GtkWidget *contrast;
118 GtkWidget *global_saturation;
119 GtkWidget *saturation;
120 GtkWidget *balance;
121 GtkWidget *interpolator;
122 GtkWidget *preserve_color;
123 GtkWidget *extra_expander;
124 GtkWidget *extra_toggle;
125 GtkDrawingArea *area;
126 float table[256]; // precomputed look-up table
127 float table_temp[256]; // precomputed look-up for the optimized interpolation
128 } dt_iop_filmic_gui_data_t;
129
130 typedef struct dt_iop_filmic_data_t
131 {
132 float table[0x10000]; // precomputed look-up table
133 float table_temp[0x10000]; // precomputed look-up for the optimized interpolation
134 float grad_2[0x10000];
135 float max_grad;
136 float grey_source;
137 float black_source;
138 float dynamic_range;
139 float saturation;
140 float global_saturation;
141 float output_power;
142 float contrast;
143 int preserve_color;
144 float latitude_min;
145 float latitude_max;
146 } dt_iop_filmic_data_t;
147
148 typedef struct dt_iop_filmic_nodes_t
149 {
150 int nodes;
151 float y[5];
152 float x[5];
153 } dt_iop_filmic_nodes_t;
154
155 typedef struct dt_iop_filmic_global_data_t
156 {
157 int kernel_filmic;
158 int kernel_filmic_log;
159 } dt_iop_filmic_global_data_t;
160
161
name()162 const char *name()
163 {
164 return _("filmic");
165 }
166
default_group()167 int default_group()
168 {
169 return IOP_GROUP_TONE | IOP_GROUP_TECHNICAL;
170 }
171
flags()172 int flags()
173 {
174 return IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_SUPPORTS_BLENDING | IOP_FLAGS_DEPRECATED;
175 }
176
deprecated_msg()177 const char *deprecated_msg()
178 {
179 return _("this module is deprecated. better use filmic rgb module instead.");
180 }
181
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)182 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
183 {
184 return iop_cs_Lab;
185 }
186
legacy_params(dt_iop_module_t * self,const void * const old_params,const int old_version,void * new_params,const int new_version)187 int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
188 const int new_version)
189 {
190 if(old_version == 1 && new_version == 3)
191 {
192 typedef struct dt_iop_filmic_params_v1_t
193 {
194 float grey_point_source;
195 float black_point_source;
196 float white_point_source;
197 float security_factor;
198 float grey_point_target;
199 float black_point_target;
200 float white_point_target;
201 float output_power;
202 float latitude_stops;
203 float contrast;
204 float saturation;
205 float balance;
206 int interpolator;
207 } dt_iop_filmic_params_v1_t;
208
209 dt_iop_filmic_params_v1_t *o = (dt_iop_filmic_params_v1_t *)old_params;
210 dt_iop_filmic_params_t *n = (dt_iop_filmic_params_t *)new_params;
211 dt_iop_filmic_params_t *d = (dt_iop_filmic_params_t *)self->default_params;
212
213 *n = *d; // start with a fresh copy of default parameters
214
215 n->grey_point_source = o->grey_point_source;
216 n->white_point_source = o->white_point_source;
217 n->black_point_source = o->black_point_source;
218 n->security_factor = o->security_factor;
219 n->grey_point_target = o->grey_point_target;
220 n->black_point_target = o->black_point_target;
221 n->white_point_target = o->white_point_target;
222 n->output_power = o->output_power;
223 n->latitude_stops = o->latitude_stops;
224 n->contrast = o->contrast;
225 n->saturation = o->saturation;
226 n->balance = o->balance;
227 n->interpolator = o->interpolator;
228 n->preserve_color = 0;
229 n->global_saturation = 100;
230 return 0;
231 }
232
233 if (old_version == 2 && new_version == 3)
234 {
235 typedef struct dt_iop_filmic_params_v2_t
236 {
237 float grey_point_source;
238 float black_point_source;
239 float white_point_source;
240 float security_factor;
241 float grey_point_target;
242 float black_point_target;
243 float white_point_target;
244 float output_power;
245 float latitude_stops;
246 float contrast;
247 float saturation;
248 float balance;
249 int interpolator;
250 int preserve_color;
251 } dt_iop_filmic_params_v2_t;
252
253 dt_iop_filmic_params_v2_t *o = (dt_iop_filmic_params_v2_t *)old_params;
254 dt_iop_filmic_params_t *n = (dt_iop_filmic_params_t *)new_params;
255 dt_iop_filmic_params_t *d = (dt_iop_filmic_params_t *)self->default_params;
256
257 *n = *d; // start with a fresh copy of default parameters
258
259 n->grey_point_source = o->grey_point_source;
260 n->white_point_source = o->white_point_source;
261 n->black_point_source = o->black_point_source;
262 n->security_factor = o->security_factor;
263 n->grey_point_target = o->grey_point_target;
264 n->black_point_target = o->black_point_target;
265 n->white_point_target = o->white_point_target;
266 n->output_power = o->output_power;
267 n->latitude_stops = o->latitude_stops;
268 n->contrast = o->contrast;
269 n->saturation = o->saturation;
270 n->balance = o->balance;
271 n->interpolator = o->interpolator;
272 n->preserve_color = o->preserve_color;
273 n->global_saturation = 100;
274 return 0;
275 }
276 return 1;
277 }
278
init_presets(dt_iop_module_so_t * self)279 void init_presets(dt_iop_module_so_t *self)
280 {
281 dt_iop_filmic_params_t p;
282 memset(&p, 0, sizeof(p));
283
284 // Fine-tune settings, no use here
285 p.interpolator = CUBIC_SPLINE;
286
287 // Output - standard display, gamma 2.2
288 p.output_power = 2.2f;
289 p.white_point_target = 100.0f;
290 p.black_point_target = 0.0f;
291 p.grey_point_target = 18.0f;
292
293 // Input - standard raw picture
294 p.security_factor = 0.0f;
295 p.contrast = 1.618f;
296 p.preserve_color = 1;
297 p.balance = -12.0f;
298 p.saturation = 60.0f;
299 p.global_saturation = 70.0f;
300
301 // Presets low-key
302 p.grey_point_source = 25.4f;
303 p.latitude_stops = 2.25f;
304 p.white_point_source = 1.95f;
305 p.black_point_source = -7.05f;
306 dt_gui_presets_add_generic(_("09 EV (low-key)"), self->op,
307 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
308
309 // Presets indoors
310 p.grey_point_source = 18.0f;
311 p.latitude_stops = 2.75f;
312 p.white_point_source = 2.45f;
313 p.black_point_source = -7.55f;
314 dt_gui_presets_add_generic(_("10 EV (indoors)"), self->op,
315 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
316
317 // Presets dim-outdoors
318 p.grey_point_source = 12.77f;
319 p.latitude_stops = 3.0f;
320 p.white_point_source = 2.95f;
321 p.black_point_source = -8.05f;
322 dt_gui_presets_add_generic(_("11 EV (dim outdoors)"), self->op,
323 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
324
325 // Presets outdoors
326 p.grey_point_source = 9.0f;
327 p.latitude_stops = 3.5f;
328 p.white_point_source = 3.45f;
329 p.black_point_source = -8.55f;
330 dt_gui_presets_add_generic(_("12 EV (outdoors)"), self->op,
331 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
332
333 // Presets outdoors
334 p.grey_point_source = 6.38f;
335 p.latitude_stops = 3.75f;
336 p.white_point_source = 3.95f;
337 p.black_point_source = -9.05f;
338 dt_gui_presets_add_generic(_("13 EV (bright outdoors)"), self->op,
339 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
340
341 // Presets backlighting
342 p.grey_point_source = 4.5f;
343 p.latitude_stops = 4.25f;
344 p.white_point_source = 4.45f;
345 p.black_point_source = -9.55f;
346 dt_gui_presets_add_generic(_("14 EV (backlighting)"), self->op,
347 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
348
349 // Presets sunset
350 p.grey_point_source = 3.19f;
351 p.latitude_stops = 4.50f;
352 p.white_point_source = 4.95f;
353 p.black_point_source = -10.05f;
354 dt_gui_presets_add_generic(_("15 EV (sunset)"), self->op,
355 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
356
357 // Presets HDR
358 p.grey_point_source = 2.25f;
359 p.latitude_stops = 5.0f;
360 p.white_point_source = 5.45f;
361 p.black_point_source = -10.55f;
362 dt_gui_presets_add_generic(_("16 EV (HDR)"), self->op,
363 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
364
365 // Presets HDR+
366 p.grey_point_source = 1.125f;
367 p.latitude_stops = 6.0f;
368 p.white_point_source = 6.45f;
369 p.black_point_source = -11.55f;
370 dt_gui_presets_add_generic(_("18 EV (HDR++)"), self->op,
371 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
372 }
373
gaussian(float x,float std)374 static inline float gaussian(float x, float std)
375 {
376 return expf(- (x * x) / (2.0f * std * std)) / (std * powf(2.0f * M_PI, 0.5f));
377 }
378
process(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)379 void process(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid,
380 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
381 {
382 dt_iop_filmic_data_t *const data = (dt_iop_filmic_data_t *)piece->data;
383
384 const int ch = piece->colors;
385
386 /** The log2(x) -> -INF when x -> 0
387 * thus very low values (noise) will get even lower, resulting in noise negative amplification,
388 * which leads to pepper noise in shadows. To avoid that, we need to clip values that are noise for sure.
389 * Using 16 bits RAW data, the black value (known by rawspeed for every manufacturer) could be used as a threshold.
390 * However, at this point of the pixelpipe, the RAW levels have already been corrected and everything can happen with black levels
391 * in the exposure module. So we define the threshold as the first non-null 16 bit integer
392 */
393 const float EPS = powf(2.0f, -16);
394 const int preserve_color = data->preserve_color;
395
396 // If saturation == 100, we have a no-op. Disable the op then.
397 const int desaturate = (data->global_saturation == 100.0f) ? FALSE : TRUE;
398 const float saturation = data->global_saturation / 100.0f;
399
400 #ifdef _OPENMP
401 #pragma omp parallel for SIMD() default(none) \
402 dt_omp_firstprivate(ch, data, desaturate, ivoid, ovoid, preserve_color, roi_out, saturation, EPS) \
403 schedule(static)
404 #endif
405 for(size_t k = 0; k < (size_t)roi_out->height * roi_out->width * ch; k += ch)
406 {
407 float *in = ((float *)ivoid) + k;
408 float *out = ((float *)ovoid) + k;
409
410 dt_aligned_pixel_t XYZ;
411 dt_Lab_to_XYZ(in, XYZ);
412
413 dt_aligned_pixel_t rgb = { 0.0f };
414 dt_XYZ_to_prophotorgb(XYZ, rgb);
415
416 float concavity, luma;
417
418 // Global desaturation
419 if (desaturate)
420 {
421 luma = XYZ[1];
422
423 for(int c = 0; c < 3; c++)
424 {
425 rgb[c] = luma + saturation * (rgb[c] - luma);
426 }
427 }
428
429 if (preserve_color)
430 {
431 int index;
432 dt_aligned_pixel_t ratios;
433 float max = fmaxf(fmaxf(rgb[0], rgb[1]), rgb[2]);
434
435 // Save the ratios
436 for (int c = 0; c < 3; ++c) ratios[c] = rgb[c] / max;
437
438 // Log tone-mapping
439 max = max / data->grey_source;
440 max = (max > EPS) ? (fastlog2(max) - data->black_source) / data->dynamic_range : EPS;
441 max = CLAMP(max, 0.0f, 1.0f);
442
443 // Filmic S curve on the max RGB
444 index = CLAMP(max * 0x10000ul, 0, 0xffff);
445 max = data->table[index];
446 concavity = data->grad_2[index];
447
448 // Re-apply ratios
449 for (int c = 0; c < 3; ++c) rgb[c] = ratios[c] * max;
450
451 luma = max;
452 }
453 else
454 {
455 int DT_ALIGNED_ARRAY index[4];
456
457 for(int c = 0; c < 3; c++)
458 {
459 // Log tone-mapping on RGB
460 rgb[c] = rgb[c] / data->grey_source;
461 rgb[c] = (rgb[c] > EPS) ? (fastlog2(rgb[c]) - data->black_source) / data->dynamic_range : EPS;
462 rgb[c] = CLAMP(rgb[c], 0.0f, 1.0f);
463
464 // Store the index of the LUT
465 index[c] = CLAMP(rgb[c] * 0x10000ul, 0, 0xffff);
466 }
467
468 // Concavity
469 dt_prophotorgb_to_XYZ(rgb, XYZ);
470 concavity = data->grad_2[(int)CLAMP(XYZ[1] * 0x10000ul, 0, 0xffff)];
471
472 // Filmic S curve
473 for(int c = 0; c < 3; c++) rgb[c] = data->table[index[c]];
474
475 dt_prophotorgb_to_XYZ(rgb, XYZ);
476 luma = XYZ[1];
477 }
478
479 // Desaturate on the non-linear parts of the curve
480 for(int c = 0; c < 3; c++)
481 {
482 // Desaturate on the non-linear parts of the curve
483 rgb[c] = luma + concavity * (rgb[c] - luma);
484
485 // Apply the transfer function of the display
486 rgb[c] = powf(CLAMP(rgb[c], 0.0f, 1.0f), data->output_power);
487 }
488
489 // transform the result back to Lab
490 // sRGB -> XYZ
491 dt_prophotorgb_to_Lab(rgb, out);
492 }
493
494 if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK)
495 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
496 }
497
498
499 #if defined(__SSE__)
process_sse2(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)500 void process_sse2(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
501 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
502 {
503 dt_iop_filmic_data_t *const data = (dt_iop_filmic_data_t *)piece->data;
504
505 const int ch = piece->colors;
506 const int preserve_color = data->preserve_color;
507
508 const float grey = data->grey_source;
509 const float black = data->black_source;
510 const float dynamic_range = data->dynamic_range;
511 const float saturation = (data->global_saturation / 100.0f);
512
513 const __m128 grey_sse = _mm_set1_ps(grey);
514 const __m128 black_sse = _mm_set1_ps(black);
515 const __m128 dynamic_range_sse = _mm_set1_ps(dynamic_range);
516 const __m128 power = _mm_set1_ps(data->output_power);
517 const __m128 saturation_sse = _mm_set1_ps(saturation);
518
519 // If saturation == 100, we have a no-op. Disable the op then.
520 const int desaturate = (data->global_saturation == 100.0f) ? FALSE : TRUE;
521
522 const float eps = powf(2.0f, -16);
523 const __m128 EPS = _mm_setr_ps(eps, eps, eps, 0.0f);
524 const __m128 zero = _mm_setzero_ps();
525 const __m128 one = _mm_set1_ps(1.0f);
526
527 #ifdef _OPENMP
528 #pragma omp parallel for default(none) \
529 dt_omp_firstprivate(black, black_sse, ch, data, desaturate, dynamic_range, \
530 dynamic_range_sse, EPS, grey, grey_sse, ivoid, one, \
531 ovoid, power, preserve_color, roi_out, saturation_sse, \
532 zero, eps) \
533 schedule(static)
534 #endif
535 for(size_t k = 0; k < (size_t)roi_out->height * roi_out->width * ch; k += ch)
536 {
537 float *in = ((float *)ivoid) + k;
538 float *out = ((float *)ovoid) + k;
539
540 __m128 XYZ = dt_Lab_to_XYZ_sse2(_mm_load_ps(in));
541 __m128 rgb = dt_XYZ_to_prophotoRGB_sse2(XYZ);
542
543 __m128 concavity;
544 __m128 luma;
545
546 // Global saturation adjustment
547 if (desaturate)
548 {
549 luma = _mm_set1_ps(XYZ[1]);
550 rgb = luma + saturation_sse * (rgb - luma);
551 }
552
553 if (preserve_color)
554 {
555 // Get the max of the RGB values
556 float max = fmax(fmaxf(rgb[0], rgb[1]), rgb[2]);
557 __m128 max_sse = _mm_set1_ps(max);
558
559 // Save the ratios
560 const __m128 ratios = rgb / max_sse;
561
562 // Log tone-mapping
563 max = max / grey;
564 max = (max > eps) ? (fastlog2(max) - black) / dynamic_range : eps;
565 max = CLAMP(max, 0.0f, 1.0f);
566
567 // Filmic S curve on the max RGB
568 const int index = CLAMP(max * 0x10000ul, 0, 0xffff);
569 max = data->table[index];
570 concavity = _mm_set1_ps(data->grad_2[index]);
571
572 // Re-apply ratios
573 max_sse = _mm_set1_ps(max);
574 rgb = ratios * max_sse;
575 luma = max_sse;
576 }
577 else
578 {
579 // Log tone-mapping
580 rgb = rgb / grey_sse;
581 rgb = _mm_max_ps(rgb, EPS);
582 rgb = _mm_log2_ps(rgb);
583 rgb -= black_sse;
584 rgb /= dynamic_range_sse;
585 rgb = _mm_max_ps(rgb, zero);
586 rgb = _mm_min_ps(rgb, one);
587
588 // Store the derivative at the pixel luminance
589 XYZ = dt_prophotoRGB_to_XYZ_sse2(rgb);
590 concavity = _mm_set1_ps(data->grad_2[(int)CLAMP(XYZ[1] * 0x10000ul, 0, 0xffff)]);
591
592 // Unpack SSE vector to regular array
593 dt_aligned_pixel_t rgb_unpack;
594
595 // Filmic S curve
596 for (int c = 0; c < 4; ++c)
597 {
598 rgb_unpack[c] = data->table[(int)CLAMP(rgb[c] * 0x10000ul, 0, 0xffff)];
599 }
600
601 rgb = _mm_load_ps(rgb_unpack);
602 XYZ = dt_prophotoRGB_to_XYZ_sse2(rgb);
603 luma = _mm_set1_ps(XYZ[1]);
604 }
605
606 rgb = luma + concavity * (rgb - luma);
607 rgb = _mm_max_ps(rgb, zero);
608 rgb = _mm_min_ps(rgb, one);
609
610 // Apply the transfer function of the display
611 rgb = _mm_pow_ps(rgb, power);
612
613 // transform the result back to Lab
614 // sRGB -> XYZ
615 XYZ = dt_prophotoRGB_to_XYZ_sse2(rgb);
616 // XYZ -> Lab
617 _mm_stream_ps(out, dt_XYZ_to_Lab_sse2(XYZ));
618 }
619
620 if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
621 }
622 #endif
623
624
625 #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)626 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
627 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
628 {
629 dt_iop_filmic_data_t *d = (dt_iop_filmic_data_t *)piece->data;
630 dt_iop_filmic_global_data_t *gd = (dt_iop_filmic_global_data_t *)self->global_data;
631
632 cl_int err = -999;
633 const int devid = piece->pipe->devid;
634 const int width = roi_in->width;
635 const int height = roi_in->height;
636
637 size_t sizes[] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
638
639 cl_mem dev_table = NULL;
640 cl_mem diff_table = NULL;
641
642 dev_table = dt_opencl_copy_host_to_device(devid, d->table, 256, 256, sizeof(float));
643 if(dev_table == NULL) goto error;
644
645 diff_table = dt_opencl_copy_host_to_device(devid, d->grad_2, 256, 256, sizeof(float));
646 if(diff_table == NULL) goto error;
647
648 const float dynamic_range = d->dynamic_range;
649 const float shadows_range = d->black_source;
650 const float grey = d->grey_source;
651 const float contrast = d->contrast;
652 const float power = d->output_power;
653 const int preserve_color = d->preserve_color;
654 const float saturation = d->global_saturation / 100.0f;
655
656 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 0, sizeof(cl_mem), (void *)&dev_in);
657 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 1, sizeof(cl_mem), (void *)&dev_out);
658 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 2, sizeof(int), (void *)&width);
659 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 3, sizeof(int), (void *)&height);
660 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 4, sizeof(float), (void *)&dynamic_range);
661 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 5, sizeof(float), (void *)&shadows_range);
662 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 6, sizeof(float), (void *)&grey);
663 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 7, sizeof(cl_mem), (void *)&dev_table);
664 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 8, sizeof(cl_mem), (void *)&diff_table);
665 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 9, sizeof(float), (void *)&contrast);
666 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 10, sizeof(float), (void *)&power);
667 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 11, sizeof(int), (void *)&preserve_color);
668 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic, 12, sizeof(int), (void *)&saturation);
669
670 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic, sizes);
671 if(err != CL_SUCCESS) goto error;
672 dt_opencl_release_mem_object(dev_table);
673 dt_opencl_release_mem_object(diff_table);
674 return TRUE;
675
676 error:
677 dt_opencl_release_mem_object(dev_table);
678 dt_opencl_release_mem_object(diff_table);
679 dt_print(DT_DEBUG_OPENCL, "[opencl_filmic] couldn't enqueue kernel! %d\n", err);
680 return FALSE;
681 }
682 #endif
683
sanitize_latitude(dt_iop_filmic_params_t * p,dt_iop_filmic_gui_data_t * g)684 static void sanitize_latitude(dt_iop_filmic_params_t *p, dt_iop_filmic_gui_data_t *g)
685 {
686 if (p->latitude_stops > (p->white_point_source - p->black_point_source) * 0.99f)
687 {
688 // The film latitude is its linear part
689 // it can never be higher than the dynamic range
690 p->latitude_stops = (p->white_point_source - p->black_point_source) * 0.99f;
691 ++darktable.gui->reset;
692 dt_bauhaus_slider_set_soft(g->latitude_stops, p->latitude_stops);
693 --darktable.gui->reset;
694 }
695 }
696
apply_auto_grey(dt_iop_module_t * self)697 static void apply_auto_grey(dt_iop_module_t *self)
698 {
699 if(darktable.gui->reset) return;
700 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
701 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
702
703 dt_aligned_pixel_t XYZ = { 0.0f };
704 dt_Lab_to_XYZ(self->picked_color, XYZ);
705
706 const float grey = XYZ[1];
707 const float prev_grey = p->grey_point_source;
708 p->grey_point_source = 100.f * grey;
709 const float grey_var = Log2(prev_grey / p->grey_point_source);
710 p->black_point_source = p->black_point_source - grey_var;
711 p->white_point_source = p->white_point_source + grey_var;
712
713 ++darktable.gui->reset;
714 dt_bauhaus_slider_set_soft(g->grey_point_source, p->grey_point_source);
715 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
716 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
717 --darktable.gui->reset;
718
719 dt_dev_add_history_item(darktable.develop, self, TRUE);
720 gtk_widget_queue_draw(self->widget);
721 }
722
apply_auto_black(dt_iop_module_t * self)723 static void apply_auto_black(dt_iop_module_t *self)
724 {
725 if(darktable.gui->reset) return;
726 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
727 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
728
729 const float noise = powf(2.0f, -16.0f);
730 dt_aligned_pixel_t XYZ = { 0.0f };
731
732 // Black
733 dt_Lab_to_XYZ(self->picked_color_min, XYZ);
734 const float black = XYZ[1];
735 float EVmin = Log2Thres(black / (p->grey_point_source / 100.0f), noise);
736 EVmin *= (1.0f + p->security_factor / 100.0f);
737
738 p->black_point_source = EVmin;
739
740 ++darktable.gui->reset;
741 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
742 --darktable.gui->reset;
743
744 sanitize_latitude(p, g);
745
746 dt_dev_add_history_item(darktable.develop, self, TRUE);
747 gtk_widget_queue_draw(self->widget);
748 }
749
750
apply_auto_white_point_source(dt_iop_module_t * self)751 static void apply_auto_white_point_source(dt_iop_module_t *self)
752 {
753 if(darktable.gui->reset) return;
754 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
755 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
756
757 const float noise = powf(2.0f, -16.0f);
758 dt_aligned_pixel_t XYZ = { 0.0f };
759
760 // White
761 dt_Lab_to_XYZ(self->picked_color_max, XYZ);
762 const float white = XYZ[1];
763 float EVmax = Log2Thres(white / (p->grey_point_source / 100.0f), noise);
764 EVmax *= (1.0f + p->security_factor / 100.0f);
765
766 p->white_point_source = EVmax;
767
768 ++darktable.gui->reset;
769 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
770 --darktable.gui->reset;
771
772 sanitize_latitude(p, g);
773
774 dt_dev_add_history_item(darktable.develop, self, TRUE);
775 gtk_widget_queue_draw(self->widget);
776 }
777
security_threshold_callback(GtkWidget * slider,gpointer user_data)778 static void security_threshold_callback(GtkWidget *slider, gpointer user_data)
779 {
780 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
781 if(darktable.gui->reset) return;
782 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
783 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
784
785 float previous = p->security_factor;
786 p->security_factor = dt_bauhaus_slider_get(slider);
787 float ratio = (p->security_factor - previous) / (previous + 100.0f);
788
789 float EVmin = p->black_point_source;
790 EVmin = EVmin + ratio * EVmin;
791
792 float EVmax = p->white_point_source;
793 EVmax = EVmax + ratio * EVmax;
794
795 p->white_point_source = EVmax;
796 p->black_point_source = EVmin;
797
798 ++darktable.gui->reset;
799 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
800 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
801 --darktable.gui->reset;
802
803 sanitize_latitude(p, g);
804
805 dt_iop_color_picker_reset(self, TRUE);
806
807 dt_dev_add_history_item(darktable.develop, self, TRUE);
808 gtk_widget_queue_draw(self->widget);
809 }
810
apply_autotune(dt_iop_module_t * self)811 static void apply_autotune(dt_iop_module_t *self)
812 {
813 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
814 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
815
816 const float noise = powf(2.0f, -16.0f);
817 dt_aligned_pixel_t XYZ = { 0.0f };
818
819 // Grey
820 dt_Lab_to_XYZ(self->picked_color, XYZ);
821 const float grey = XYZ[1];
822 p->grey_point_source = 100.f * grey;
823
824 // Black
825 dt_Lab_to_XYZ(self->picked_color_min, XYZ);
826 const float black = XYZ[1];
827 float EVmin = Log2Thres(black / (p->grey_point_source / 100.0f), noise);
828 EVmin *= (1.0f + p->security_factor / 100.0f);
829
830 // White
831 dt_Lab_to_XYZ(self->picked_color_max, XYZ);
832 const float white = XYZ[1];
833 float EVmax = Log2Thres(white / (p->grey_point_source / 100.0f), noise);
834 EVmax *= (1.0f + p->security_factor / 100.0f);
835
836 p->black_point_source = EVmin;
837 p->white_point_source = EVmax;
838
839 ++darktable.gui->reset;
840 dt_bauhaus_slider_set_soft(g->grey_point_source, p->grey_point_source);
841 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
842 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
843 --darktable.gui->reset;
844
845 sanitize_latitude(p, g);
846
847 dt_dev_add_history_item(darktable.develop, self, TRUE);
848 gtk_widget_queue_draw(self->widget);
849 }
850
color_picker_apply(dt_iop_module_t * self,GtkWidget * picker,dt_dev_pixelpipe_iop_t * piece)851 void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_iop_t *piece)
852 {
853 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
854 if (picker == g->grey_point_source)
855 apply_auto_grey(self);
856 else if(picker == g->black_point_source)
857 apply_auto_black(self);
858 else if(picker == g->white_point_source)
859 apply_auto_white_point_source(self);
860 else if(picker == g->auto_button)
861 apply_autotune(self);
862 else
863 fprintf(stderr, "[filmic] unknown color picker\n");
864 }
865
grey_point_source_callback(GtkWidget * slider,gpointer user_data)866 static void grey_point_source_callback(GtkWidget *slider, gpointer user_data)
867 {
868 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
869 if(darktable.gui->reset) return;
870 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
871 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
872 float prev_grey = p->grey_point_source;
873 p->grey_point_source = dt_bauhaus_slider_get(slider);
874
875 float grey_var = Log2(prev_grey / p->grey_point_source);
876 p->black_point_source = p->black_point_source - grey_var;
877 p->white_point_source = p->white_point_source + grey_var;
878
879 ++darktable.gui->reset;
880 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
881 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
882 --darktable.gui->reset;
883
884 dt_iop_color_picker_reset(self, TRUE);
885
886 dt_dev_add_history_item(darktable.develop, self, TRUE);
887 gtk_widget_queue_draw(self->widget);
888 }
889
white_point_source_callback(GtkWidget * slider,gpointer user_data)890 static void white_point_source_callback(GtkWidget *slider, gpointer user_data)
891 {
892 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
893 if(darktable.gui->reset) return;
894 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
895 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
896 p->white_point_source = dt_bauhaus_slider_get(slider);
897
898 sanitize_latitude(p, g);
899
900 dt_iop_color_picker_reset(self, TRUE);
901
902 dt_dev_add_history_item(darktable.develop, self, TRUE);
903 gtk_widget_queue_draw(self->widget);
904 }
905
black_point_source_callback(GtkWidget * slider,gpointer user_data)906 static void black_point_source_callback(GtkWidget *slider, gpointer user_data)
907 {
908 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
909 if(darktable.gui->reset) return;
910 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
911 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
912 p->black_point_source = dt_bauhaus_slider_get(slider);
913
914 sanitize_latitude(p, g);
915
916 dt_iop_color_picker_reset(self, TRUE);
917
918 dt_dev_add_history_item(darktable.develop, self, TRUE);
919 gtk_widget_queue_draw(self->widget);
920 }
921
grey_point_target_callback(GtkWidget * slider,gpointer user_data)922 static void grey_point_target_callback(GtkWidget *slider, gpointer user_data)
923 {
924 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
925 if(darktable.gui->reset) return;
926 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
927 p->grey_point_target = dt_bauhaus_slider_get(slider);
928 dt_iop_color_picker_reset(self, TRUE);
929 dt_dev_add_history_item(darktable.develop, self, TRUE);
930 gtk_widget_queue_draw(self->widget);
931 }
932
latitude_stops_callback(GtkWidget * slider,gpointer user_data)933 static void latitude_stops_callback(GtkWidget *slider, gpointer user_data)
934 {
935 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
936 if(darktable.gui->reset) return;
937 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
938 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
939
940 p->latitude_stops = dt_bauhaus_slider_get(slider);
941
942 sanitize_latitude(p, g);
943
944 dt_iop_color_picker_reset(self, TRUE);
945 dt_dev_add_history_item(darktable.develop, self, TRUE);
946 gtk_widget_queue_draw(self->widget);
947 }
948
contrast_callback(GtkWidget * slider,gpointer user_data)949 static void contrast_callback(GtkWidget *slider, gpointer user_data)
950 {
951 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
952 if(darktable.gui->reset) return;
953 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
954 p->contrast = dt_bauhaus_slider_get(slider);
955 dt_iop_color_picker_reset(self, TRUE);
956 dt_dev_add_history_item(darktable.develop, self, TRUE);
957 gtk_widget_queue_draw(self->widget);
958 }
959
saturation_callback(GtkWidget * slider,gpointer user_data)960 static void saturation_callback(GtkWidget *slider, gpointer user_data)
961 {
962 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
963 if(darktable.gui->reset) return;
964 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
965 p->saturation = logf(9.0f * dt_bauhaus_slider_get(slider)/100.0 + 1.0f) / logf(10.0f) * 100.0f;
966 dt_iop_color_picker_reset(self, TRUE);
967 dt_dev_add_history_item(darktable.develop, self, TRUE);
968 }
969
global_saturation_callback(GtkWidget * slider,gpointer user_data)970 static void global_saturation_callback(GtkWidget *slider, gpointer user_data)
971 {
972 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
973 if(darktable.gui->reset) return;
974 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
975 p->global_saturation = dt_bauhaus_slider_get(slider);
976 dt_iop_color_picker_reset(self, TRUE);
977 dt_dev_add_history_item(darktable.develop, self, TRUE);
978 }
979
white_point_target_callback(GtkWidget * slider,gpointer user_data)980 static void white_point_target_callback(GtkWidget *slider, gpointer user_data)
981 {
982 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
983 if(darktable.gui->reset) return;
984 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
985 p->white_point_target = dt_bauhaus_slider_get(slider);
986 dt_iop_color_picker_reset(self, TRUE);
987 dt_dev_add_history_item(darktable.develop, self, TRUE);
988 gtk_widget_queue_draw(self->widget);
989 }
990
black_point_target_callback(GtkWidget * slider,gpointer user_data)991 static void black_point_target_callback(GtkWidget *slider, gpointer user_data)
992 {
993 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
994 if(darktable.gui->reset) return;
995 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
996 p->black_point_target = dt_bauhaus_slider_get(slider);
997 dt_iop_color_picker_reset(self, TRUE);
998 dt_dev_add_history_item(darktable.develop, self, TRUE);
999 gtk_widget_queue_draw(self->widget);
1000 }
1001
output_power_callback(GtkWidget * slider,gpointer user_data)1002 static void output_power_callback(GtkWidget *slider, gpointer user_data)
1003 {
1004 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1005 if(darktable.gui->reset) return;
1006 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
1007 p->output_power = dt_bauhaus_slider_get(slider);
1008 dt_iop_color_picker_reset(self, TRUE);
1009 dt_dev_add_history_item(darktable.develop, self, TRUE);
1010 gtk_widget_queue_draw(self->widget);
1011 }
1012
balance_callback(GtkWidget * slider,gpointer user_data)1013 static void balance_callback(GtkWidget *slider, gpointer user_data)
1014 {
1015 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1016 if(darktable.gui->reset) return;
1017 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
1018 p->balance = dt_bauhaus_slider_get(slider);
1019 dt_iop_color_picker_reset(self, TRUE);
1020 dt_dev_add_history_item(darktable.develop, self, TRUE);
1021 gtk_widget_queue_draw(self->widget);
1022 }
1023
interpolator_callback(GtkWidget * widget,dt_iop_module_t * self)1024 static void interpolator_callback(GtkWidget *widget, dt_iop_module_t *self)
1025 {
1026 if(darktable.gui->reset) return;
1027 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
1028 dt_iop_color_picker_reset(self, TRUE);
1029 const int combo = dt_bauhaus_combobox_get(widget);
1030
1031 switch (combo)
1032 {
1033 case CUBIC_SPLINE:
1034 {
1035 p->interpolator = CUBIC_SPLINE;
1036 break;
1037 }
1038 case CATMULL_ROM:
1039 {
1040 p->interpolator = CATMULL_ROM;
1041 break;
1042 }
1043 case MONOTONE_HERMITE:
1044 {
1045 p->interpolator = MONOTONE_HERMITE;
1046 break;
1047 }
1048 case 3:
1049 {
1050 p->interpolator = 3; // Optimized
1051 break;
1052 }
1053 default:
1054 {
1055 p->interpolator = CUBIC_SPLINE;
1056 break;
1057 }
1058 }
1059
1060 dt_dev_add_history_item(darktable.develop, self, TRUE);
1061 gtk_widget_queue_draw(self->widget);
1062 }
1063
preserve_color_callback(GtkWidget * widget,dt_iop_module_t * self)1064 static void preserve_color_callback(GtkWidget *widget, dt_iop_module_t *self)
1065 {
1066 if(darktable.gui->reset) return;
1067 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
1068 p->preserve_color = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
1069 dt_dev_add_history_item(darktable.develop, self, TRUE);
1070 }
1071
compute_curve_lut(dt_iop_filmic_params_t * p,float * table,float * table_temp,int res,dt_iop_filmic_data_t * d,dt_iop_filmic_nodes_t * nodes_data)1072 void compute_curve_lut(dt_iop_filmic_params_t *p, float *table, float *table_temp, int res,
1073 dt_iop_filmic_data_t *d, dt_iop_filmic_nodes_t *nodes_data)
1074 {
1075 dt_draw_curve_t *curve;
1076
1077 const float white_source = p->white_point_source;
1078 const float black_source = p->black_point_source;
1079 const float dynamic_range = white_source - black_source;
1080
1081 // luminance after log encoding
1082 const float black_log = 0.0f; // assumes user set log as in the autotuner
1083 const float grey_log = fabsf(p->black_point_source) / dynamic_range;
1084 const float white_log = 1.0f; // assumes user set log as in the autotuner
1085
1086 // target luminance desired after filmic curve
1087 const float black_display = CLAMP(p->black_point_target, 0.0f, p->grey_point_target) / 100.0f; // in %
1088 const float grey_display = powf(CLAMP(p->grey_point_target, p->black_point_target, p->white_point_target) / 100.0f, 1.0f / (p->output_power));
1089 const float white_display = CLAMP(p->white_point_target, p->grey_point_target, 100.0f) / 100.0f; // in %
1090
1091 const float latitude = CLAMP(p->latitude_stops, 0.01f, dynamic_range * 0.99f);
1092 const float balance = CLAMP(p->balance, -50.0f, 50.0f) / 100.0f; // in %
1093
1094 const float contrast = p->contrast;
1095
1096 // nodes for mapping from log encoding to desired target luminance
1097 // X coordinates
1098 float toe_log = grey_log - latitude/dynamic_range * fabsf(black_source/dynamic_range);
1099 float shoulder_log = grey_log + latitude/dynamic_range * white_source/dynamic_range;
1100
1101
1102 // interception
1103 float linear_intercept = grey_display - (contrast * grey_log);
1104
1105 // y coordinates
1106 float toe_display = (toe_log * contrast + linear_intercept);
1107 float shoulder_display = (shoulder_log * contrast + linear_intercept);
1108
1109 // Apply the highlights/shadows balance as a shift along the contrast slope
1110 const float norm = powf(powf(contrast, 2.0f) + 1.0f, 0.5f);
1111
1112 // negative values drag to the left and compress the shadows, on the UI negative is the inverse
1113 const float coeff = -(dynamic_range - latitude) / dynamic_range * balance;
1114
1115 toe_display += coeff * contrast /norm;
1116 shoulder_display += coeff * contrast /norm;
1117 toe_log += coeff /norm;
1118 shoulder_log += coeff /norm;
1119
1120 // Sanitize pass 1
1121 toe_log = CLAMP(toe_log, 0.0f, grey_log);
1122 shoulder_log = CLAMP(shoulder_log, grey_log, 1.0f);
1123 toe_display = CLAMP(toe_display, black_display, grey_display);
1124 shoulder_display = CLAMP(shoulder_display, grey_display, white_display);
1125
1126 /**
1127 * Now we have 3 segments :
1128 * - x = [0.0 ; toe_log], curved part
1129 * - x = [toe_log ; grey_log ; shoulder_log], linear part
1130 * - x = [shoulder_log ; 1.0] curved part
1131 *
1132 * BUT : in case some nodes overlap, we need to remove them to avoid
1133 * degenerating of the curve
1134 **/
1135
1136 // sanitize pass 2
1137 int TOE_LOST = FALSE;
1138 int SHOULDER_LOST = FALSE;
1139
1140 if ((toe_log == grey_log && toe_display == grey_display) || (toe_log == 0.0f && toe_display == black_display))
1141 {
1142 TOE_LOST = TRUE;
1143 }
1144 if ((shoulder_log == grey_log && shoulder_display == grey_display) || (shoulder_log == 1.0f && shoulder_display == white_display))
1145 {
1146 SHOULDER_LOST = TRUE;
1147 }
1148
1149 // Build the curve from the nodes
1150
1151 if (SHOULDER_LOST && !TOE_LOST)
1152 {
1153 // shoulder only broke - we remove it
1154 nodes_data->nodes = 4;
1155 nodes_data->x[0] = black_log;
1156 nodes_data->x[1] = toe_log;
1157 nodes_data->x[2] = grey_log;
1158 nodes_data->x[3] = white_log;
1159
1160 nodes_data->y[0] = black_display;
1161 nodes_data->y[1] = toe_display;
1162 nodes_data->y[2] = grey_display;
1163 nodes_data->y[3] = white_display;
1164
1165 if(d)
1166 {
1167 d->latitude_min = toe_log;
1168 d->latitude_max = white_log;
1169 }
1170
1171 //dt_control_log(_("filmic curve using 4 nodes - highlights lost"));
1172
1173 }
1174 else if (TOE_LOST && !SHOULDER_LOST)
1175 {
1176 // toe only broke - we remove it
1177 nodes_data->nodes = 4;
1178
1179 nodes_data->x[0] = black_log;
1180 nodes_data->x[1] = grey_log;
1181 nodes_data->x[2] = shoulder_log;
1182 nodes_data->x[3] = white_log;
1183
1184 nodes_data->y[0] = black_display;
1185 nodes_data->y[1] = grey_display;
1186 nodes_data->y[2] = shoulder_display;
1187 nodes_data->y[3] = white_display;
1188
1189 if(d)
1190 {
1191 d->latitude_min = black_log;
1192 d->latitude_max = shoulder_log;
1193 }
1194
1195 //dt_control_log(_("filmic curve using 4 nodes - shadows lost"));
1196
1197 }
1198 else if (TOE_LOST && SHOULDER_LOST)
1199 {
1200 // toe and shoulder both broke - we remove them
1201 nodes_data->nodes = 3;
1202
1203 nodes_data->x[0] = black_log;
1204 nodes_data->x[1] = grey_log;
1205 nodes_data->x[2] = white_log;
1206
1207 nodes_data->y[0] = black_display;
1208 nodes_data->y[1] = grey_display;
1209 nodes_data->y[2] = white_display;
1210
1211 if(d)
1212 {
1213 d->latitude_min = black_log;
1214 d->latitude_max = white_log;
1215 }
1216
1217 //dt_control_log(_("filmic curve using 3 nodes - highlights & shadows lost"));
1218
1219 }
1220 else
1221 {
1222 // everything OK
1223 nodes_data->nodes = 4;
1224
1225 nodes_data->x[0] = black_log;
1226 nodes_data->x[1] = toe_log;
1227 //nodes_data->x[2] = grey_log,
1228 nodes_data->x[2] = shoulder_log;
1229 nodes_data->x[3] = white_log;
1230
1231 nodes_data->y[0] = black_display;
1232 nodes_data->y[1] = toe_display;
1233 //nodes_data->y[2] = grey_display,
1234 nodes_data->y[2] = shoulder_display;
1235 nodes_data->y[3] = white_display;
1236
1237 if(d)
1238 {
1239 d->latitude_min = toe_log;
1240 d->latitude_max = shoulder_log;
1241 }
1242
1243 //dt_control_log(_("filmic curve using 5 nodes - everything alright"));
1244 }
1245
1246 if (p->interpolator != 3)
1247 {
1248 // Compute the interpolation
1249
1250 // Catch bad interpolators exceptions (errors in saved params)
1251 int interpolator = CUBIC_SPLINE;
1252 if (p->interpolator > CUBIC_SPLINE && p->interpolator <= MONOTONE_HERMITE) interpolator = p->interpolator;
1253
1254 curve = dt_draw_curve_new(0.0, 1.0, interpolator);
1255 for(int k = 0; k < nodes_data->nodes; k++) (void)dt_draw_curve_add_point(curve, nodes_data->x[k], nodes_data->y[k]);
1256
1257 // Compute the LUT
1258 dt_draw_curve_calc_values(curve, 0.0f, 1.0f, res, NULL, table);
1259 dt_draw_curve_destroy(curve);
1260
1261 }
1262 else
1263 {
1264 // Compute the monotonic interpolation
1265 curve = dt_draw_curve_new(0.0, 1.0, MONOTONE_HERMITE);
1266 for(int k = 0; k < nodes_data->nodes; k++) (void)dt_draw_curve_add_point(curve, nodes_data->x[k], nodes_data->y[k]);
1267 dt_draw_curve_calc_values(curve, 0.0f, 1.0f, res, NULL, table_temp);
1268 dt_draw_curve_destroy(curve);
1269
1270 // Compute the cubic spline interpolation
1271 curve = dt_draw_curve_new(0.0, 1.0, CUBIC_SPLINE);
1272 for(int k = 0; k < nodes_data->nodes; k++) (void)dt_draw_curve_add_point(curve, nodes_data->x[k], nodes_data->y[k]);
1273 dt_draw_curve_calc_values(curve, 0.0f, 1.0f, res, NULL, table);
1274 dt_draw_curve_destroy(curve);
1275
1276 // Average both LUT
1277 #ifdef _OPENMP
1278 #pragma omp parallel for SIMD() default(none) shared(table, table_temp, res) schedule(static)
1279 #endif
1280 for(int k = 0; k < res; k++) table[k] = (table[k] + table_temp[k]) / 2.0f;
1281 }
1282
1283 }
1284
commit_params(dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1285 void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
1286 dt_dev_pixelpipe_iop_t *piece)
1287 {
1288 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)p1;
1289 dt_iop_filmic_data_t *d = (dt_iop_filmic_data_t *)piece->data;
1290
1291 d->preserve_color = p->preserve_color;
1292
1293 // source luminance - Used only in the log encoding
1294 const float white_source = p->white_point_source;
1295 const float grey_source = p->grey_point_source / 100.0f; // in %
1296 const float black_source = p->black_point_source;
1297 const float dynamic_range = white_source - black_source;
1298
1299 // luminance after log encoding
1300 const float grey_log = fabsf(p->black_point_source) / dynamic_range;
1301
1302 // target luminance desired after filmic curve
1303 const float grey_display = powf(p->grey_point_target / 100.0f, 1.0f / (p->output_power));
1304
1305 float contrast = p->contrast;
1306 if (contrast < grey_display / grey_log)
1307 {
1308 // We need grey_display - (contrast * grey_log) <= 0.0
1309 contrast = 1.0001f * grey_display / grey_log;
1310 }
1311
1312 // commitproducts with no low-pass filter, you will increase the contrast of nois
1313 d->dynamic_range = dynamic_range;
1314 d->black_source = black_source;
1315 d->grey_source = grey_source;
1316 d->output_power = p->output_power;
1317 d->saturation = p->saturation;
1318 d->global_saturation = p->global_saturation;
1319 d->contrast = contrast;
1320
1321 // compute the curves and their LUT
1322 dt_iop_filmic_nodes_t *nodes_data = (dt_iop_filmic_nodes_t *)malloc(sizeof(dt_iop_filmic_nodes_t));
1323 compute_curve_lut(p, d->table, d->table_temp, 0x10000, d, nodes_data);
1324 free(nodes_data);
1325 nodes_data = NULL;
1326
1327 // Build a window function based on the log.
1328 // This will be used to selectively desaturate the non-linear parts
1329 // to avoid over-saturation in the toe and shoulder.
1330
1331 const float latitude = d->latitude_max - d->latitude_min;
1332 const float center = (d->latitude_max + d->latitude_min)/2.0f;
1333 const float saturation = d->saturation / 100.0f;
1334 const float sigma = saturation * saturation * latitude * latitude;
1335
1336 #ifdef _OPENMP
1337 #pragma omp parallel for SIMD() default(none) \
1338 dt_omp_firstprivate(center, sigma) \
1339 shared(d) \
1340 schedule(static)
1341 #endif
1342 for(int k = 0; k < 65536; k++)
1343 {
1344 const float x = ((float)k) / 65536.0f;
1345 if (sigma != 0.0f)
1346 {
1347 d->grad_2[k] = expf(-0.5f * (center - x) * (center - x) / sigma);
1348 }
1349 else
1350 {
1351 d->grad_2[k] = 0.0f;
1352 }
1353 }
1354
1355 }
1356
init_pipe(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1357 void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
1358 {
1359 piece->data = calloc(1, sizeof(dt_iop_filmic_data_t));
1360 }
1361
cleanup_pipe(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1362 void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
1363 {
1364 free(piece->data);
1365 piece->data = NULL;
1366 }
1367
gui_update(dt_iop_module_t * self)1368 void gui_update(dt_iop_module_t *self)
1369 {
1370 dt_iop_module_t *module = (dt_iop_module_t *)self;
1371 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
1372 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)module->params;
1373
1374 dt_iop_color_picker_reset(self, TRUE);
1375
1376 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
1377 dt_bauhaus_slider_set_soft(g->grey_point_source, p->grey_point_source);
1378 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
1379 dt_bauhaus_slider_set_soft(g->security_factor, p->security_factor);
1380 dt_bauhaus_slider_set_soft(g->white_point_target, p->white_point_target);
1381 dt_bauhaus_slider_set_soft(g->grey_point_target, p->grey_point_target);
1382 dt_bauhaus_slider_set_soft(g->black_point_target, p->black_point_target);
1383 dt_bauhaus_slider_set_soft(g->output_power, p->output_power);
1384 dt_bauhaus_slider_set_soft(g->latitude_stops, p->latitude_stops);
1385 dt_bauhaus_slider_set(g->contrast, p->contrast);
1386 dt_bauhaus_slider_set(g->global_saturation, p->global_saturation);
1387 dt_bauhaus_slider_set(g->saturation, (powf(10.0f, p->saturation/100.0f) - 1.0f) / 9.0f * 100.0f);
1388 dt_bauhaus_slider_set(g->balance, p->balance);
1389
1390 dt_bauhaus_combobox_set(g->interpolator, p->interpolator);
1391 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->preserve_color), p->preserve_color);
1392
1393 dtgtk_expander_set_expanded(DTGTK_EXPANDER(g->extra_expander),
1394 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->extra_toggle)));
1395
1396 gtk_widget_queue_draw(self->widget);
1397
1398 }
1399
init(dt_iop_module_t * module)1400 void init(dt_iop_module_t *module)
1401 {
1402 module->params = calloc(1, sizeof(dt_iop_filmic_params_t));
1403 module->default_params = calloc(1, sizeof(dt_iop_filmic_params_t));
1404 module->default_enabled = 0;
1405 module->params_size = sizeof(dt_iop_filmic_params_t);
1406 module->gui_data = NULL;
1407
1408 *(dt_iop_filmic_params_t *)module->default_params
1409 = (dt_iop_filmic_params_t){
1410 .grey_point_source = 18, // source grey
1411 .black_point_source = -8.65, // source black
1412 .white_point_source = 2.45, // source white
1413 .security_factor = 0.0, // security factor
1414 .grey_point_target = 18.0, // target grey
1415 .black_point_target = 0.0, // target black
1416 .white_point_target = 100.0, // target white
1417 .output_power = 2.2, // target power (~ gamma)
1418 .latitude_stops = 2.0, // intent latitude
1419 .contrast = 1.5, // intent contrast
1420 .saturation = 100.0, // intent saturation
1421 .global_saturation = 100.0,
1422 .balance = 0.0, // balance shadows/highlights
1423 .interpolator = CUBIC_SPLINE, //interpolator
1424 .preserve_color = 0, // run the saturated variant
1425 };
1426 }
1427
init_global(dt_iop_module_so_t * module)1428 void init_global(dt_iop_module_so_t *module)
1429 {
1430 const int program = 22; // filmic.cl, from programs.conf
1431 dt_iop_filmic_global_data_t *gd
1432 = (dt_iop_filmic_global_data_t *)malloc(sizeof(dt_iop_filmic_global_data_t));
1433
1434 module->data = gd;
1435 gd->kernel_filmic = dt_opencl_create_kernel(program, "filmic");
1436 }
1437
cleanup(dt_iop_module_t * module)1438 void cleanup(dt_iop_module_t *module)
1439 {
1440 free(module->params);
1441 module->params = NULL;
1442 free(module->default_params);
1443 module->default_params = NULL;
1444 }
1445
cleanup_global(dt_iop_module_so_t * module)1446 void cleanup_global(dt_iop_module_so_t *module)
1447 {
1448 dt_iop_filmic_global_data_t *gd = (dt_iop_filmic_global_data_t *)module->data;
1449 dt_opencl_free_kernel(gd->kernel_filmic);
1450 free(module->data);
1451 module->data = NULL;
1452 }
1453
gui_reset(dt_iop_module_t * self)1454 void gui_reset(dt_iop_module_t *self)
1455 {
1456 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
1457 dt_iop_color_picker_reset(self, TRUE);
1458 dtgtk_expander_set_expanded(DTGTK_EXPANDER(g->extra_expander), FALSE);
1459 dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->extra_toggle), dtgtk_cairo_paint_solid_arrow,
1460 CPF_STYLE_BOX | CPF_DIRECTION_LEFT, NULL);
1461 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->extra_toggle), FALSE);
1462 }
1463
dt_iop_tonecurve_draw(GtkWidget * widget,cairo_t * crf,gpointer user_data)1464 static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1465 {
1466 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1467 dt_iop_filmic_gui_data_t *c = (dt_iop_filmic_gui_data_t *)self->gui_data;
1468 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->params;
1469 dt_iop_filmic_nodes_t *nodes_data = (dt_iop_filmic_nodes_t *)malloc(sizeof(dt_iop_filmic_nodes_t));
1470 compute_curve_lut(p, c->table, c->table_temp, 256, NULL, nodes_data);
1471
1472 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1473 GtkAllocation allocation;
1474 gtk_widget_get_allocation(widget, &allocation);
1475 int width = allocation.width, height = allocation.height;
1476 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1477 cairo_t *cr = cairo_create(cst);
1478
1479 // clear bg
1480 cairo_set_source_rgb(cr, .2, .2, .2);
1481 cairo_paint(cr);
1482
1483 cairo_translate(cr, inset, inset);
1484 width -= 2 * inset;
1485 height -= 2 * inset;
1486
1487 cairo_set_source_rgb(cr, .3, .3, .3);
1488 cairo_rectangle(cr, 0, 0, width, height);
1489 cairo_fill(cr);
1490
1491 // draw grid
1492 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
1493 cairo_set_source_rgb(cr, .1, .1, .1);
1494 dt_draw_grid(cr, 4, 0, 0, width, height);
1495
1496 // solve the equations for the rescaling parameters
1497 const float DR = (p->white_point_source - p->black_point_source);
1498 const float grey = -p->black_point_source / DR;
1499 int rescale = FALSE;
1500
1501 float a, b, d;
1502 a = DR;
1503 b = Log2( 1.0f / (-1 + powf(2.0f, a)));
1504 d = - powf(2.0f, b);
1505
1506 if (grey > powf(p->grey_point_target / 100.0f, p->output_power))
1507 {
1508 // The x-coordinate rescaling is valid only when the log grey value (dynamic range center)
1509 // is greater or equal to the destination grey value
1510 rescale = TRUE;
1511
1512 for (int i = 0; i < 50; ++i)
1513 { // Optimization loop for the non-linear problem
1514 a = Log2((0.5f - d) / (1.0f - d)) / (grey - 1.0f);
1515 b = Log2( 1.0f / (-1 + powf(2.0f, a)));
1516 d = - powf(2.0f, b);
1517 }
1518 }
1519
1520 const float gamma = (logf(p->grey_point_target / 100.0f) / logf(0.5f)) / p->output_power;
1521
1522 // draw nodes
1523 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1524 cairo_set_source_rgb(cr, 0.9, 0.9, 0.9);
1525
1526 for(int k = 0; k < nodes_data->nodes; k++)
1527 {
1528 /*
1529 * Use double precision locally to avoid cancellation effect on
1530 * the "+ d" operation.
1531 */
1532 const float x = (rescale) ? powf(2.0f, (double)a * nodes_data->x[k] + b) + d : nodes_data->x[k];
1533 const float y = powf(nodes_data->y[k], 1.0f / gamma);
1534
1535 cairo_arc(cr, x * width, (1.0 - y) * (double)height, DT_PIXEL_APPLY_DPI(3), 0, 2. * M_PI);
1536 cairo_stroke_preserve(cr);
1537 cairo_fill(cr);
1538 cairo_stroke(cr);
1539 }
1540 free(nodes_data);
1541 nodes_data = NULL;
1542
1543 // draw curve
1544 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1545 cairo_set_source_rgb(cr, .9, .9, .9);
1546 cairo_move_to(cr, 0, height * (1.0 - c->table[0]));
1547
1548 for(int k = 1; k < 256; k++)
1549 {
1550 /*
1551 * Use double precision locally to avoid cancellation effect on
1552 * the "+ d" operation.
1553 */
1554 const float x = (rescale) ? powf(2.0f, (double)a * k / 255.0f + b) + d : k / 255.0f;
1555 const float y = powf(c->table[k], 1.0f / gamma);
1556 cairo_line_to(cr, x * width, (double)height * (1.0 - y));
1557 }
1558 cairo_stroke(cr);
1559 cairo_destroy(cr);
1560 cairo_set_source_surface(crf, cst, 0, 0);
1561 cairo_paint(crf);
1562 cairo_surface_destroy(cst);
1563 return TRUE;
1564 }
1565
_extra_options_button_changed(GtkDarktableToggleButton * widget,gpointer user_data)1566 static void _extra_options_button_changed(GtkDarktableToggleButton *widget, gpointer user_data)
1567 {
1568 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1569 dt_iop_filmic_gui_data_t *g = (dt_iop_filmic_gui_data_t *)self->gui_data;
1570 const gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->extra_toggle));
1571 dtgtk_expander_set_expanded(DTGTK_EXPANDER(g->extra_expander), active);
1572 dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->extra_toggle), dtgtk_cairo_paint_solid_arrow,
1573 CPF_STYLE_BOX | (active?CPF_DIRECTION_DOWN:CPF_DIRECTION_LEFT), NULL);
1574 }
1575
gui_init(dt_iop_module_t * self)1576 void gui_init(dt_iop_module_t *self)
1577 {
1578 dt_iop_filmic_gui_data_t *g = IOP_GUI_ALLOC(filmic);
1579 dt_iop_filmic_params_t *p = (dt_iop_filmic_params_t *)self->default_params;
1580
1581 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
1582
1583 // don't make the area square to safe some vertical space -- it's not interactive anyway
1584 g->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(0.618));
1585 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("read-only graph, use the parameters below to set the nodes"));
1586 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->area), TRUE, TRUE, 0);
1587 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(dt_iop_tonecurve_draw), self);
1588
1589 gtk_box_pack_start(GTK_BOX(self->widget), dt_ui_section_label_new(_("logarithmic shaper")), FALSE, FALSE, 0);
1590
1591 // grey_point_source slider
1592 g->grey_point_source = dt_bauhaus_slider_new_with_range(self, 0.1, 36., 0.1, p->grey_point_source, 2);
1593 dt_bauhaus_slider_enable_soft_boundaries(g->grey_point_source, 0.0, 100.0);
1594 dt_bauhaus_widget_set_label(g->grey_point_source, NULL, N_("middle gray luminance"));
1595 gtk_box_pack_start(GTK_BOX(self->widget), g->grey_point_source, TRUE, TRUE, 0);
1596 dt_bauhaus_slider_set_format(g->grey_point_source, "%.2f %%");
1597 gtk_widget_set_tooltip_text(g->grey_point_source, _("adjust to match the average luminance of the subject.\n"
1598 "except in back-lighting situations, this should be around 18%."));
1599 g_signal_connect(G_OBJECT(g->grey_point_source), "value-changed", G_CALLBACK(grey_point_source_callback), self);
1600 dt_color_picker_new(self, DT_COLOR_PICKER_AREA, g->grey_point_source);
1601
1602 // White slider
1603 g->white_point_source = dt_bauhaus_slider_new_with_range(self, 2.0, 8.0, 0.1, p->white_point_source, 2);
1604 dt_bauhaus_slider_enable_soft_boundaries(g->white_point_source, 0.0, 16.0);
1605 dt_bauhaus_widget_set_label(g->white_point_source, NULL, N_("white relative exposure"));
1606 gtk_box_pack_start(GTK_BOX(self->widget), g->white_point_source, TRUE, TRUE, 0);
1607 dt_bauhaus_slider_set_format(g->white_point_source, "%.2f EV");
1608 gtk_widget_set_tooltip_text(g->white_point_source, _("number of stops between middle gray and pure white.\n"
1609 "this is a reading a lightmeter would give you on the scene.\n"
1610 "adjust so highlights clipping is avoided"));
1611 g_signal_connect(G_OBJECT(g->white_point_source), "value-changed", G_CALLBACK(white_point_source_callback), self);
1612 dt_color_picker_new(self, DT_COLOR_PICKER_AREA, g->white_point_source);
1613
1614 // Black slider
1615 g->black_point_source = dt_bauhaus_slider_new_with_range(self, -14.0, -3.0, 0.1, p->black_point_source, 2);
1616 dt_bauhaus_slider_enable_soft_boundaries(g->black_point_source, -16.0, -0.1);
1617 dt_bauhaus_widget_set_label(g->black_point_source, NULL, N_("black relative exposure"));
1618 gtk_box_pack_start(GTK_BOX(self->widget), g->black_point_source, TRUE, TRUE, 0);
1619 dt_bauhaus_slider_set_format(g->black_point_source, "%.2f EV");
1620 gtk_widget_set_tooltip_text(g->black_point_source, _("number of stops between middle gray and pure black.\n"
1621 "this is a reading a lightmeter would give you on the scene.\n"
1622 "increase to get more contrast.\ndecrease to recover more details in low-lights."));
1623 g_signal_connect(G_OBJECT(g->black_point_source), "value-changed", G_CALLBACK(black_point_source_callback), self);
1624 dt_color_picker_new(self, DT_COLOR_PICKER_AREA, g->black_point_source);
1625
1626 // Security factor
1627 g->security_factor = dt_bauhaus_slider_new_with_range(self, -50., 50., 1.0, p->security_factor, 2);
1628 dt_bauhaus_widget_set_label(g->security_factor, NULL, N_("safety factor"));
1629 gtk_box_pack_start(GTK_BOX(self->widget), g->security_factor, TRUE, TRUE, 0);
1630 dt_bauhaus_slider_set_format(g->security_factor, "%.2f %%");
1631 gtk_widget_set_tooltip_text(g->security_factor, _("enlarge or shrink the computed dynamic range.\n"
1632 "useful in conjunction with \"auto tune levels\"."));
1633 g_signal_connect(G_OBJECT(g->security_factor), "value-changed", G_CALLBACK(security_threshold_callback), self);
1634
1635 // Auto tune slider
1636 g->auto_button = dt_bauhaus_combobox_new(self);
1637 dt_bauhaus_widget_set_label(g->auto_button, NULL, N_("auto tune levels"));
1638 dt_color_picker_new(self, DT_COLOR_PICKER_AREA, g->auto_button);
1639 gtk_widget_set_tooltip_text(g->auto_button, _("try to optimize the settings with some guessing.\n"
1640 "this will fit the luminance range inside the histogram bounds.\n"
1641 "works better for landscapes and evenly-lit pictures\nbut fails for high-keys and low-keys." ));
1642 gtk_box_pack_start(GTK_BOX(self->widget), g->auto_button, TRUE, TRUE, 0);
1643
1644 gtk_box_pack_start(GTK_BOX(self->widget), dt_ui_section_label_new(_("filmic S curve")), FALSE, FALSE, 0);
1645
1646 // contrast slider
1647 g->contrast = dt_bauhaus_slider_new_with_range(self, 1., 2., 0.01, p->contrast, 3);
1648 dt_bauhaus_slider_enable_soft_boundaries(g->contrast, 0.0, 5.0);
1649 dt_bauhaus_widget_set_label(g->contrast, NULL, N_("contrast"));
1650 gtk_box_pack_start(GTK_BOX(self->widget), g->contrast, TRUE, TRUE, 0);
1651 gtk_widget_set_tooltip_text(g->contrast, _("slope of the linear part of the curve\n"
1652 "affects mostly the mid-tones"));
1653 g_signal_connect(G_OBJECT(g->contrast), "value-changed", G_CALLBACK(contrast_callback), self);
1654
1655 // latitude slider
1656 g->latitude_stops = dt_bauhaus_slider_new_with_range(self, 2., 8.0, 0.05, p->latitude_stops, 3);
1657 dt_bauhaus_slider_enable_soft_boundaries(g->latitude_stops, 0.01, 16.0);
1658 dt_bauhaus_widget_set_label(g->latitude_stops, NULL, N_("latitude"));
1659 dt_bauhaus_slider_set_format(g->latitude_stops, "%.2f EV");
1660 gtk_box_pack_start(GTK_BOX(self->widget), g->latitude_stops, TRUE, TRUE, 0);
1661 gtk_widget_set_tooltip_text(g->latitude_stops, _("width of the linear domain in the middle of the curve.\n"
1662 "increase to get more contrast at the extreme luminances.\n"
1663 "this has no effect on mid-tones."));
1664 g_signal_connect(G_OBJECT(g->latitude_stops), "value-changed", G_CALLBACK(latitude_stops_callback), self);
1665
1666 // balance slider
1667 g->balance = dt_bauhaus_slider_new_with_range(self, -50., 50., 1.0, p->balance, 2);
1668 dt_bauhaus_widget_set_label(g->balance, NULL, N_("shadows/highlights balance"));
1669 gtk_box_pack_start(GTK_BOX(self->widget), g->balance, TRUE, TRUE, 0);
1670 dt_bauhaus_slider_set_format(g->balance, "%.2f %%");
1671 gtk_widget_set_tooltip_text(g->balance, _("slides the latitude along the slope\nto give more room to shadows or highlights.\n"
1672 "use it if you need to protect the details\nat one extremity of the histogram."));
1673 g_signal_connect(G_OBJECT(g->balance), "value-changed", G_CALLBACK(balance_callback), self);
1674
1675 // saturation slider
1676 g->global_saturation = dt_bauhaus_slider_new_with_range(self, 0., 200., 0.5, p->global_saturation, 2);
1677 dt_bauhaus_widget_set_label(g->global_saturation, NULL, N_("global saturation"));
1678 dt_bauhaus_slider_enable_soft_boundaries(g->global_saturation, 0.0, 1000.0);
1679 dt_bauhaus_slider_set_format(g->global_saturation, "%.2f %%");
1680 gtk_box_pack_start(GTK_BOX(self->widget), g->global_saturation, TRUE, TRUE, 0);
1681 gtk_widget_set_tooltip_text(g->global_saturation, _("desaturates the input of the module globally.\n"
1682 "you need to set this value below 100%\nif the chrominance preservation is enabled."));
1683 g_signal_connect(G_OBJECT(g->global_saturation), "value-changed", G_CALLBACK(global_saturation_callback), self);
1684
1685 // saturation slider
1686 g->saturation = dt_bauhaus_slider_new_with_range(self, 0., 200., 0.5, (powf(10.0f, p->saturation/100.0f) - 1.0f) / 9.0f *100.0f, 2);
1687 dt_bauhaus_widget_set_label(g->saturation, NULL, N_("extreme luminance saturation"));
1688 dt_bauhaus_slider_enable_soft_boundaries(g->saturation, 0.0, 1000.0);
1689 dt_bauhaus_slider_set_format(g->saturation, "%.2f %%");
1690 gtk_box_pack_start(GTK_BOX(self->widget), g->saturation, TRUE, TRUE, 0);
1691 gtk_widget_set_tooltip_text(g->saturation, _("desaturates the output of the module\nspecifically at extreme luminances.\n"
1692 "decrease if shadows and/or highlights are over-saturated."));
1693 g_signal_connect(G_OBJECT(g->saturation), "value-changed", G_CALLBACK(saturation_callback), self);
1694
1695 /* From src/common/curve_tools.h :
1696 #define CUBIC_SPLINE 0
1697 #define CATMULL_ROM 1
1698 #define MONOTONE_HERMITE 2
1699 */
1700 g->interpolator = dt_bauhaus_combobox_new(self);
1701 dt_bauhaus_widget_set_label(g->interpolator, NULL, N_("intent"));
1702 dt_bauhaus_combobox_add(g->interpolator, _("contrasted")); // cubic spline
1703 dt_bauhaus_combobox_add(g->interpolator, _("faded")); // centripetal spline
1704 dt_bauhaus_combobox_add(g->interpolator, _("linear")); // monotonic spline
1705 dt_bauhaus_combobox_add(g->interpolator, _("optimized")); // monotonic spline
1706 gtk_box_pack_start(GTK_BOX(self->widget), g->interpolator , TRUE, TRUE, 0);
1707 gtk_widget_set_tooltip_text(g->interpolator, _("change this method if you see reversed contrast or faded blacks"));
1708 g_signal_connect(G_OBJECT(g->interpolator), "value-changed", G_CALLBACK(interpolator_callback), self);
1709
1710 // Preserve color
1711 g->preserve_color = gtk_check_button_new_with_label(_("preserve the chrominance"));
1712 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(g->preserve_color), p->preserve_color);
1713 gtk_widget_set_tooltip_text(g->preserve_color, _("ensure the original color are preserved.\n"
1714 "may reinforce chromatic aberrations.\n"
1715 "you need to manually tune the saturation when using this mode."));
1716 gtk_box_pack_start(GTK_BOX(self->widget), g->preserve_color , TRUE, TRUE, 0);
1717 g_signal_connect(G_OBJECT(g->preserve_color), "toggled", G_CALLBACK(preserve_color_callback), self);
1718
1719
1720 // add collapsible section for those extra options that are generally not to be used
1721
1722 GtkWidget *destdisp_head = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_BAUHAUS_SPACE);
1723 GtkWidget *destdisp = dt_ui_section_label_new(_("destination/display"));
1724 g->extra_toggle =
1725 dtgtk_togglebutton_new(dtgtk_cairo_paint_solid_arrow, CPF_STYLE_BOX | CPF_DIRECTION_LEFT, NULL);
1726 gtk_widget_set_name(GTK_WIDGET(g->extra_toggle), "control-button");
1727 GtkWidget *extra_options = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
1728 gtk_box_pack_start(GTK_BOX(destdisp_head), destdisp, TRUE, TRUE, 0);
1729 gtk_box_pack_start(GTK_BOX(destdisp_head), g->extra_toggle, FALSE, FALSE, 0);
1730 gtk_widget_set_visible(extra_options, FALSE);
1731 g->extra_expander = dtgtk_expander_new(destdisp_head, extra_options);
1732 dtgtk_expander_set_expanded(DTGTK_EXPANDER(g->extra_expander), TRUE);
1733 gtk_box_pack_start(GTK_BOX(self->widget), g->extra_expander, FALSE, FALSE, 0);
1734
1735 g_signal_connect(G_OBJECT(g->extra_toggle), "toggled", G_CALLBACK(_extra_options_button_changed), (gpointer)self);
1736
1737 // Black slider
1738 g->black_point_target = dt_bauhaus_slider_new_with_range(self, 0.0, 100.0, 1, p->black_point_target, 2);
1739 dt_bauhaus_widget_set_label(g->black_point_target, NULL, N_("target black luminance"));
1740 gtk_box_pack_start(GTK_BOX(extra_options), g->black_point_target, FALSE, FALSE, 0);
1741 dt_bauhaus_slider_set_format(g->black_point_target, "%.2f %%");
1742 gtk_widget_set_tooltip_text(g->black_point_target, _("luminance of output pure black, "
1743 "this should be 0%\nexcept if you want a faded look"));
1744 g_signal_connect(G_OBJECT(g->black_point_target), "value-changed", G_CALLBACK(black_point_target_callback), self);
1745
1746 // grey_point_source slider
1747 g->grey_point_target = dt_bauhaus_slider_new_with_range(self, 0.1, 50., 0.5, p->grey_point_target, 2);
1748 dt_bauhaus_widget_set_label(g->grey_point_target, NULL, N_("target middle gray"));
1749 gtk_box_pack_start(GTK_BOX(extra_options), g->grey_point_target, FALSE, FALSE, 0);
1750 dt_bauhaus_slider_set_format(g->grey_point_target, "%.2f %%");
1751 gtk_widget_set_tooltip_text(g->grey_point_target, _("middle gray value of the target display or color space.\n"
1752 "you should never touch that unless you know what you are doing."));
1753 g_signal_connect(G_OBJECT(g->grey_point_target), "value-changed", G_CALLBACK(grey_point_target_callback), self);
1754
1755 // White slider
1756 g->white_point_target = dt_bauhaus_slider_new_with_range(self, 0.0, 100.0, 1., p->white_point_target, 2);
1757 dt_bauhaus_widget_set_label(g->white_point_target, NULL, N_("target white luminance"));
1758 gtk_box_pack_start(GTK_BOX(extra_options), g->white_point_target, FALSE, FALSE, 0);
1759 dt_bauhaus_slider_set_format(g->white_point_target, "%.2f %%");
1760 gtk_widget_set_tooltip_text(g->white_point_target, _("luminance of output pure white, "
1761 "this should be 100%\nexcept if you want a faded look"));
1762 g_signal_connect(G_OBJECT(g->white_point_target), "value-changed", G_CALLBACK(white_point_target_callback), self);
1763
1764 // power/gamma slider
1765 g->output_power = dt_bauhaus_slider_new_with_range(self, 1.0, 2.4, 0.1, p->output_power, 2);
1766 dt_bauhaus_widget_set_label(g->output_power, NULL, N_("target gamma"));
1767 gtk_box_pack_start(GTK_BOX(extra_options), g->output_power, FALSE, FALSE, 0);
1768 gtk_widget_set_tooltip_text(g->output_power, _("power or gamma of the transfer function\nof the display or color space.\n"
1769 "you should never touch that unless you know what you are doing."));
1770 g_signal_connect(G_OBJECT(g->output_power), "value-changed", G_CALLBACK(output_power_callback), self);
1771 }
1772
1773
1774 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1775 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1776 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1777