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