1 /*
2    This file is part of darktable,
3    Copyright (C) 2010-2021 darktable developers.
4 
5    darktable is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9 
10    darktable is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 #include "bauhaus/bauhaus.h"
22 #include "common/colorspaces_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 "develop/imageop_gui.h"
30 #include "dtgtk/button.h"
31 #include "gui/accelerators.h"
32 #include "gui/gtk.h"
33 #include "gui/presets.h"
34 #include "gui/color_picker_proxy.h"
35 #include "iop/iop_api.h"
36 #include <assert.h>
37 #include <math.h>
38 #include <stdlib.h>
39 #include <string.h>
40 
41 DT_MODULE_INTROSPECTION(2, dt_iop_profilegamma_params_t)
42 
43 typedef enum dt_iop_profilegamma_mode_t
44 {
45   PROFILEGAMMA_LOG = 0,  // $DESCRIPTION: "logarithmic"
46   PROFILEGAMMA_GAMMA = 1 // $DESCRIPTION: "gamma"
47 } dt_iop_profilegamma_mode_t;
48 
49 typedef struct dt_iop_profilegamma_params_t
50 {
51   dt_iop_profilegamma_mode_t mode; // $DEFAULT: PROFILEGAMMA_LOG
52   float linear;          // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.1
53   float gamma;           // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.45
54   float dynamic_range;   // $MIN: 0.01 $MAX: 32.0 $DEFAULT: 10.0 $DESCRIPTION: "dynamic range"
55   float grey_point;      // $MIN: 0.1 $MAX: 100.0 $DEFAULT: 18.0 $DESCRIPTION: "middle gray luma"
56   float shadows_range;   // $MIN: -16.0 $MAX: 16.0 $DEFAULT: -5.0 $DESCRIPTION: "black relative exposure"
57   float security_factor; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION: "safety factor"
58 } dt_iop_profilegamma_params_t;
59 
60 typedef struct dt_iop_profilegamma_gui_data_t
61 {
62   GtkWidget *mode;
63   GtkWidget *mode_stack;
64   GtkWidget *linear;
65   GtkWidget *gamma;
66   GtkWidget *dynamic_range;
67   GtkWidget *grey_point;
68   GtkWidget *shadows_range;
69   GtkWidget *security_factor;
70   GtkWidget *auto_button;
71 } dt_iop_profilegamma_gui_data_t;
72 
73 typedef struct dt_iop_profilegamma_data_t
74 {
75   dt_iop_profilegamma_mode_t mode;
76   float linear;
77   float gamma;
78   float table[0x10000];      // precomputed look-up table
79   float unbounded_coeffs[3]; // approximation for extrapolation of curve
80   float dynamic_range;
81   float grey_point;
82   float shadows_range;
83   float security_factor;
84 } dt_iop_profilegamma_data_t;
85 
86 typedef struct dt_iop_profilegamma_global_data_t
87 {
88   int kernel_profilegamma;
89   int kernel_profilegamma_log;
90 } dt_iop_profilegamma_global_data_t;
91 
92 
name()93 const char *name()
94 {
95   return _("unbreak input profile");
96 }
97 
description(struct dt_iop_module_t * self)98 const char *description(struct dt_iop_module_t *self)
99 {
100   return dt_iop_set_description(self, _("correct input color profiles meant to be applied on non-linear RGB"),
101                                       _("corrective"),
102                                       _("linear, RGB, display-referred"),
103                                       _("non-linear, RGB"),
104                                       _("non-linear, RGB, display-referred"));
105 }
106 
default_group()107 int default_group()
108 {
109   return IOP_GROUP_COLOR | IOP_GROUP_TECHNICAL;
110 }
111 
flags()112 int flags()
113 {
114   return IOP_FLAGS_ONE_INSTANCE | IOP_FLAGS_ALLOW_TILING | IOP_FLAGS_INCLUDE_IN_STYLES
115          | IOP_FLAGS_SUPPORTS_BLENDING;
116 }
117 
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)118 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
119 {
120   return iop_cs_rgb;
121 }
122 
init_presets(dt_iop_module_so_t * self)123 void init_presets(dt_iop_module_so_t *self)
124 {
125   dt_iop_profilegamma_params_t p;
126   memset(&p, 0, sizeof(p));
127 
128   p.mode = PROFILEGAMMA_LOG;
129   p.grey_point = 18.00f;
130   p.security_factor = 0.0f;
131 
132   // 16 EV preset
133   p.dynamic_range = 16.0f;
134   p.shadows_range = -12.0f;
135   dt_gui_presets_add_generic(_("16 EV dynamic range (generic)"), self->op,
136                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
137 
138   // 14 EV preset
139   p.dynamic_range = 14.0f;
140   p.shadows_range = -10.50f;
141   dt_gui_presets_add_generic(_("14 EV dynamic range (generic)"), self->op,
142                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
143 
144   // 12 EV preset
145   p.dynamic_range = 12.0f;
146   p.shadows_range = -9.0f;
147   dt_gui_presets_add_generic(_("12 EV dynamic range (generic)"), self->op,
148                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
149 
150   // 10 EV preset
151   p.dynamic_range = 10.0f;
152   p.shadows_range = -7.50f;
153   dt_gui_presets_add_generic(_("10 EV dynamic range (generic)"), self->op,
154                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
155 
156   // 08 EV preset
157   p.dynamic_range = 8.0f;
158   p.shadows_range = -6.0f;
159   dt_gui_presets_add_generic(_("08 EV dynamic range (generic)"), self->op,
160                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
161 }
162 
163 
legacy_params(dt_iop_module_t * self,const void * const old_params,const int old_version,void * new_params,const int new_version)164 int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
165                   const int new_version)
166 {
167   if(old_version == 1 && new_version == 2)
168   {
169     typedef struct dt_iop_profilegamma_params_v1_t
170     {
171       float linear;
172       float gamma;
173     } dt_iop_profilegamma_params_v1_t;
174 
175     dt_iop_profilegamma_params_v1_t *o = (dt_iop_profilegamma_params_v1_t *)old_params;
176     dt_iop_profilegamma_params_t *n = (dt_iop_profilegamma_params_t *)new_params;
177     dt_iop_profilegamma_params_t *d = (dt_iop_profilegamma_params_t *)self->default_params;
178 
179     *n = *d; // start with a fresh copy of default parameters
180 
181     n->linear = o->linear;
182     n->gamma = o->gamma;
183     n->mode = PROFILEGAMMA_GAMMA;
184     return 0;
185   }
186   return 1;
187 }
188 
189 
190 #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)191 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
192                const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
193 {
194   dt_iop_profilegamma_data_t *d = (dt_iop_profilegamma_data_t *)piece->data;
195   dt_iop_profilegamma_global_data_t *gd = (dt_iop_profilegamma_global_data_t *)self->global_data;
196 
197   cl_int err = -999;
198   const int devid = piece->pipe->devid;
199   const int width = roi_in->width;
200   const int height = roi_in->height;
201   cl_mem dev_table = NULL;
202   cl_mem dev_coeffs = NULL;
203 
204 
205   size_t sizes[3] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
206 
207   if(d->mode == PROFILEGAMMA_LOG)
208   {
209     const float dynamic_range = d->dynamic_range;
210     const float shadows_range = d->shadows_range;
211     const float grey = d->grey_point / 100.0f;
212     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma_log, 0, sizeof(cl_mem), (void *)&dev_in);
213     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma_log, 1, sizeof(cl_mem), (void *)&dev_out);
214     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma_log, 2, sizeof(int), (void *)&width);
215     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma_log, 3, sizeof(int), (void *)&height);
216     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma_log, 4, sizeof(float), (void *)&dynamic_range);
217     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma_log, 5, sizeof(float), (void *)&shadows_range);
218     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma_log, 6, sizeof(float), (void *)&grey);
219 
220     err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_profilegamma_log, sizes);
221     if(err != CL_SUCCESS) goto error;
222     return TRUE;
223   }
224   else if(d->mode == PROFILEGAMMA_GAMMA)
225   {
226     dev_table = dt_opencl_copy_host_to_device(devid, d->table, 256, 256, sizeof(float));
227     if(dev_table == NULL) goto error;
228 
229     dev_coeffs = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 3, d->unbounded_coeffs);
230     if(dev_coeffs == NULL) goto error;
231 
232     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma, 0, sizeof(cl_mem), (void *)&dev_in);
233     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma, 1, sizeof(cl_mem), (void *)&dev_out);
234     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma, 2, sizeof(int), (void *)&width);
235     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma, 3, sizeof(int), (void *)&height);
236     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma, 4, sizeof(cl_mem), (void *)&dev_table);
237     dt_opencl_set_kernel_arg(devid, gd->kernel_profilegamma, 5, sizeof(cl_mem), (void *)&dev_coeffs);
238 
239     err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_profilegamma, sizes);
240     if(err != CL_SUCCESS)
241     {
242       dt_opencl_release_mem_object(dev_table);
243       dt_opencl_release_mem_object(dev_coeffs);
244       goto error;
245     }
246 
247     dt_opencl_release_mem_object(dev_table);
248     dt_opencl_release_mem_object(dev_coeffs);
249     return TRUE;
250   }
251 
252 error:
253   dt_print(DT_DEBUG_OPENCL, "[opencl_profilegamma] couldn't enqueue kernel! %d\n", err);
254   return FALSE;
255 }
256 #endif
257 
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)258 void process(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid,
259              const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
260 {
261   dt_iop_profilegamma_data_t *data = (dt_iop_profilegamma_data_t *)piece->data;
262 
263   const int ch = piece->colors;
264 
265   switch(data->mode)
266   {
267     case PROFILEGAMMA_LOG:
268     {
269       const float grey = data->grey_point / 100.0f;
270 
271       /** The log2(x) -> -INF when x -> 0
272       * thus very low values (noise) will get even lower, resulting in noise negative amplification,
273       * which leads to pepper noise in shadows. To avoid that, we need to clip values that are noise for sure.
274       * Using 16 bits RAW data, the black value (known by rawspeed for every manufacturer) could be used as a threshold.
275       * However, at this point of the pixelpipe, the RAW levels have already been corrected and everything can happen with black levels
276       * in the exposure module. So we define the threshold as the first non-null 16 bit integer
277       */
278       const float noise = powf(2.0f, -16.0f);
279 
280 #ifdef _OPENMP
281 #pragma omp parallel for SIMD() default(none) \
282       dt_omp_firstprivate(ch, grey, ivoid, ovoid, roi_out, noise) \
283       shared(data) \
284       schedule(static)
285 #endif
286       for(size_t k = 0; k < (size_t)ch * roi_out->width * roi_out->height; k++)
287       {
288         float tmp = ((const float *)ivoid)[k] / grey;
289         if (tmp < noise) tmp = noise;
290         tmp = (fastlog2(tmp) - data->shadows_range) / (data->dynamic_range);
291 
292         if (tmp < noise)
293         {
294           ((float *)ovoid)[k] = noise;
295         }
296         else
297         {
298           ((float *)ovoid)[k] = tmp;
299         }
300       }
301       break;
302     }
303 
304     case PROFILEGAMMA_GAMMA:
305     {
306 #ifdef _OPENMP
307 #pragma omp parallel for default(none) \
308       dt_omp_firstprivate(ch, ivoid, ovoid, roi_out) \
309       shared(data) \
310       schedule(static)
311 #endif
312       for(int k = 0; k < roi_out->height; k++)
313       {
314         const float *in = ((float *)ivoid) + (size_t)ch * k * roi_out->width;
315         float *out = ((float *)ovoid) + (size_t)ch * k * roi_out->width;
316 
317         for(int j = 0; j < roi_out->width; j++, in += ch, out += ch)
318         {
319           for(int i = 0; i < 3; i++)
320           {
321             // use base curve for values < 1, else use extrapolation.
322             if(in[i] < 1.0f)
323               out[i] = data->table[CLAMP((int)(in[i] * 0x10000ul), 0, 0xffff)];
324             else
325               out[i] = dt_iop_eval_exp(data->unbounded_coeffs, in[i]);
326           }
327         }
328       }
329       break;
330     }
331   }
332 
333   if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK)
334     dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
335 }
336 
apply_auto_grey(dt_iop_module_t * self)337 static void apply_auto_grey(dt_iop_module_t *self)
338 {
339   if(darktable.gui->reset) return;
340   dt_iop_profilegamma_params_t *p = (dt_iop_profilegamma_params_t *)self->params;
341   dt_iop_profilegamma_gui_data_t *g = (dt_iop_profilegamma_gui_data_t *)self->gui_data;
342 
343   float grey = fmax(fmax(self->picked_color[0], self->picked_color[1]), self->picked_color[2]);
344   p->grey_point = 100.f * grey;
345 
346   ++darktable.gui->reset;
347   dt_bauhaus_slider_set(g->grey_point, p->grey_point);
348   --darktable.gui->reset;
349 
350   dt_dev_add_history_item(darktable.develop, self, TRUE);
351 }
352 
apply_auto_black(dt_iop_module_t * self)353 static void apply_auto_black(dt_iop_module_t *self)
354 {
355   if(darktable.gui->reset) return;
356   dt_iop_profilegamma_params_t *p = (dt_iop_profilegamma_params_t *)self->params;
357   dt_iop_profilegamma_gui_data_t *g = (dt_iop_profilegamma_gui_data_t *)self->gui_data;
358 
359   float noise = powf(2.0f, -16.0f);
360 
361   // Black
362   float black = fmax(fmax(self->picked_color_min[0], self->picked_color_min[1]), self->picked_color_min[2]);
363   float EVmin = Log2Thres(black / (p->grey_point / 100.0f), noise);
364   EVmin *= (1.0f + p->security_factor / 100.0f);
365 
366   p->shadows_range = EVmin;
367 
368   ++darktable.gui->reset;
369   dt_bauhaus_slider_set(g->shadows_range, p->shadows_range);
370   --darktable.gui->reset;
371 
372   dt_dev_add_history_item(darktable.develop, self, TRUE);
373 }
374 
apply_auto_dynamic_range(dt_iop_module_t * self)375 static void apply_auto_dynamic_range(dt_iop_module_t *self)
376 {
377   if(darktable.gui->reset) return;
378   dt_iop_profilegamma_params_t *p = (dt_iop_profilegamma_params_t *)self->params;
379   dt_iop_profilegamma_gui_data_t *g = (dt_iop_profilegamma_gui_data_t *)self->gui_data;
380 
381   float noise = powf(2.0f, -16.0f);
382 
383   // Black
384   float EVmin = p->shadows_range;
385 
386   // White
387   float white = fmax(fmax(self->picked_color_max[0], self->picked_color_max[1]), self->picked_color_max[2]);
388   float EVmax = Log2Thres(white / (p->grey_point / 100.0f), noise);
389   EVmax *= (1.0f + p->security_factor / 100.0f);
390 
391   p->dynamic_range = EVmax - EVmin;
392 
393   ++darktable.gui->reset;
394   dt_bauhaus_slider_set(g->dynamic_range, p->dynamic_range);
395   --darktable.gui->reset;
396 
397   dt_dev_add_history_item(darktable.develop, self, TRUE);
398 }
399 
apply_autotune(dt_iop_module_t * self)400 static void apply_autotune(dt_iop_module_t *self)
401 {
402   dt_iop_profilegamma_params_t *p = (dt_iop_profilegamma_params_t *)self->params;
403   dt_iop_profilegamma_gui_data_t *g = (dt_iop_profilegamma_gui_data_t *)self->gui_data;
404 
405   float noise = powf(2.0f, -16.0f);
406 
407   // Grey
408   float grey = fmax(fmax(self->picked_color[0], self->picked_color[1]), self->picked_color[2]);
409   p->grey_point = 100.f * grey;
410 
411   // Black
412   float black = fmax(fmax(self->picked_color_min[0], self->picked_color_min[1]), self->picked_color_min[2]);
413   float EVmin = Log2Thres(black / (p->grey_point / 100.0f), noise);
414   EVmin *= (1.0f + p->security_factor / 100.0f);
415 
416   // White
417   float white = fmax(fmax(self->picked_color_max[0], self->picked_color_max[1]), self->picked_color_max[2]);
418   float EVmax = Log2Thres(white / (p->grey_point / 100.0f), noise);
419   EVmax *= (1.0f + p->security_factor / 100.0f);
420 
421   p->shadows_range = EVmin;
422   p->dynamic_range = EVmax - EVmin;
423 
424   ++darktable.gui->reset;
425   dt_bauhaus_slider_set(g->grey_point, p->grey_point);
426   dt_bauhaus_slider_set(g->shadows_range, p->shadows_range);
427   dt_bauhaus_slider_set(g->dynamic_range, p->dynamic_range);
428   --darktable.gui->reset;
429 
430   dt_dev_add_history_item(darktable.develop, self, TRUE);
431 }
432 
433 
gui_changed(dt_iop_module_t * self,GtkWidget * w,void * previous)434 void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
435 {
436   dt_iop_profilegamma_gui_data_t *g = (dt_iop_profilegamma_gui_data_t *)self->gui_data;
437   dt_iop_profilegamma_params_t *p = (dt_iop_profilegamma_params_t *)self->params;
438 
439   if(w == g->mode)
440   {
441     if(p->mode == PROFILEGAMMA_LOG)
442     {
443       gtk_stack_set_visible_child_name(GTK_STACK(g->mode_stack), "log");
444     }
445     else
446     {
447       gtk_stack_set_visible_child_name(GTK_STACK(g->mode_stack), "gamma");
448     }
449   }
450   else if(w == g->security_factor)
451   {
452     float prev = *(float *)previous;
453     float ratio = (p->security_factor - prev) / (prev + 100.0f);
454 
455     float EVmin = p->shadows_range;
456     EVmin = EVmin + ratio * EVmin;
457 
458     float EVmax = p->dynamic_range + p->shadows_range;
459     EVmax = EVmax + ratio * EVmax;
460 
461     p->dynamic_range = EVmax - EVmin;
462     p->shadows_range = EVmin;
463 
464     ++darktable.gui->reset;
465     dt_bauhaus_slider_set_soft(g->dynamic_range, p->dynamic_range);
466     dt_bauhaus_slider_set_soft(g->shadows_range, p->shadows_range);
467     --darktable.gui->reset;
468   }
469 }
470 
color_picker_apply(dt_iop_module_t * self,GtkWidget * picker,dt_dev_pixelpipe_iop_t * piece)471 void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_iop_t *piece)
472 {
473   dt_iop_profilegamma_gui_data_t *g = (dt_iop_profilegamma_gui_data_t *)self->gui_data;
474   if     (picker == g->grey_point)
475     apply_auto_grey(self);
476   else if(picker == g->shadows_range)
477     apply_auto_black(self);
478   else if(picker == g->dynamic_range)
479     apply_auto_dynamic_range(self);
480   else if(picker == g->auto_button)
481     apply_autotune(self);
482   else
483     fprintf(stderr, "[profile_gamma] unknown color picker\n");
484 }
485 
commit_params(dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)486 void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
487                    dt_dev_pixelpipe_iop_t *piece)
488 {
489   dt_iop_profilegamma_params_t *p = (dt_iop_profilegamma_params_t *)p1;
490   dt_iop_profilegamma_data_t *d = (dt_iop_profilegamma_data_t *)piece->data;
491 
492   const float linear = p->linear;
493   const float gamma = p->gamma;
494 
495   d->linear = p->linear;
496   d->gamma = p->gamma;
497 
498   float a, b, c, g;
499   if(gamma == 1.0)
500   {
501 #ifdef _OPENMP
502 #pragma omp parallel for default(none) shared(d) schedule(static)
503 #endif
504     for(int k = 0; k < 0x10000; k++) d->table[k] = 1.0 * k / 0x10000;
505   }
506   else
507   {
508     if(linear == 0.0)
509     {
510 #ifdef _OPENMP
511 #pragma omp parallel for default(none) \
512       dt_omp_firstprivate(gamma) \
513       shared(d) \
514       schedule(static)
515 #endif
516       for(int k = 0; k < 0x10000; k++) d->table[k] = powf(1.00 * k / 0x10000, gamma);
517     }
518     else
519     {
520       if(linear < 1.0)
521       {
522         g = gamma * (1.0 - linear) / (1.0 - gamma * linear);
523         a = 1.0 / (1.0 + linear * (g - 1));
524         b = linear * (g - 1) * a;
525         c = powf(a * linear + b, g) / linear;
526       }
527       else
528       {
529         a = b = g = 0.0;
530         c = 1.0;
531       }
532 #ifdef _OPENMP
533 #pragma omp parallel for default(none) \
534       dt_omp_firstprivate(linear) \
535       shared(d, a, b, c, g) \
536       schedule(static)
537 #endif
538       for(int k = 0; k < 0x10000; k++)
539       {
540         float tmp;
541         if(k < 0x10000 * linear)
542           tmp = c * k / 0x10000;
543         else
544           tmp = powf(a * k / 0x10000 + b, g);
545         d->table[k] = tmp;
546       }
547     }
548   }
549 
550   // now the extrapolation stuff:
551   const float x[4] = { 0.7f, 0.8f, 0.9f, 1.0f };
552   const float y[4]
553       = { d->table[CLAMP((int)(x[0] * 0x10000ul), 0, 0xffff)],
554           d->table[CLAMP((int)(x[1] * 0x10000ul), 0, 0xffff)],
555           d->table[CLAMP((int)(x[2] * 0x10000ul), 0, 0xffff)],
556           d->table[CLAMP((int)(x[3] * 0x10000ul), 0, 0xffff)] };
557   dt_iop_estimate_exp(x, y, 4, d->unbounded_coeffs);
558 
559   d->dynamic_range = p->dynamic_range;
560   d->grey_point = p->grey_point;
561   d->shadows_range = p->shadows_range;
562   d->security_factor = p->security_factor;
563   d->mode = p->mode;
564 
565   //piece->process_cl_ready = 1;
566 
567   // no OpenCL for log yet.
568   //if(d->mode == PROFILEGAMMA_LOG) piece->process_cl_ready = 0;
569 }
570 
init_pipe(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)571 void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
572 {
573   piece->data = calloc(1, sizeof(dt_iop_profilegamma_data_t));
574 }
575 
cleanup_pipe(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)576 void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
577 {
578   free(piece->data);
579   piece->data = NULL;
580 }
581 
gui_reset(dt_iop_module_t * self)582 void gui_reset(dt_iop_module_t *self)
583 {
584   dt_iop_color_picker_reset(self, TRUE);
585 }
586 
gui_update(dt_iop_module_t * self)587 void gui_update(dt_iop_module_t *self)
588 {
589   dt_iop_profilegamma_gui_data_t *g = (dt_iop_profilegamma_gui_data_t *)self->gui_data;
590   dt_iop_profilegamma_params_t *p = (dt_iop_profilegamma_params_t *)self->params;
591 
592   dt_iop_color_picker_reset(self, TRUE);
593 
594   dt_bauhaus_combobox_set(g->mode, p->mode);
595   dt_bauhaus_slider_set_soft(g->linear, p->linear);
596   dt_bauhaus_slider_set_soft(g->gamma, p->gamma);
597   dt_bauhaus_slider_set_soft(g->dynamic_range, p->dynamic_range);
598   dt_bauhaus_slider_set_soft(g->grey_point, p->grey_point);
599   dt_bauhaus_slider_set_soft(g->shadows_range, p->shadows_range);
600   dt_bauhaus_slider_set_soft(g->security_factor, p->security_factor);
601 
602   gui_changed(self, g->mode, 0);
603 }
604 
init_global(dt_iop_module_so_t * module)605 void init_global(dt_iop_module_so_t *module)
606 {
607   const int program = 2; // basic.cl, from programs.conf
608   dt_iop_profilegamma_global_data_t *gd
609       = (dt_iop_profilegamma_global_data_t *)malloc(sizeof(dt_iop_profilegamma_global_data_t));
610 
611   module->data = gd;
612   gd->kernel_profilegamma = dt_opencl_create_kernel(program, "profilegamma");
613   gd->kernel_profilegamma_log = dt_opencl_create_kernel(program, "profilegamma_log");
614 }
615 
cleanup_global(dt_iop_module_so_t * module)616 void cleanup_global(dt_iop_module_so_t *module)
617 {
618   dt_iop_profilegamma_global_data_t *gd = (dt_iop_profilegamma_global_data_t *)module->data;
619   dt_opencl_free_kernel(gd->kernel_profilegamma);
620   dt_opencl_free_kernel(gd->kernel_profilegamma_log);
621   free(module->data);
622   module->data = NULL;
623 }
624 
625 
gui_init(dt_iop_module_t * self)626 void gui_init(dt_iop_module_t *self)
627 {
628   dt_iop_profilegamma_gui_data_t *g = IOP_GUI_ALLOC(profilegamma);
629 
630   // prepare the modes widgets stack
631   g->mode_stack = gtk_stack_new();
632   gtk_stack_set_homogeneous(GTK_STACK(g->mode_stack), FALSE);
633 
634   /**** GAMMA MODE ***/
635 
636   GtkWidget *vbox_gamma = self->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE));
637 
638   g->linear = dt_bauhaus_slider_from_params(self, N_("linear"));
639   dt_bauhaus_slider_set_digits(g->linear, 4);
640   gtk_widget_set_tooltip_text(g->linear, _("linear part"));
641 
642   g->gamma = dt_bauhaus_slider_from_params(self, N_("gamma"));
643   dt_bauhaus_slider_set_digits(g->gamma, 4);
644   gtk_widget_set_tooltip_text(g->gamma, _("gamma exponential factor"));
645 
646   gtk_stack_add_named(GTK_STACK(g->mode_stack), vbox_gamma, "gamma");
647 
648   /**** LOG MODE ****/
649 
650   GtkWidget *vbox_log = self->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE));
651 
652   g->grey_point
653       = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "grey_point"));
654   dt_bauhaus_slider_set_step(g->grey_point, 0.5);
655   dt_bauhaus_slider_set_format(g->grey_point, "%.2f %%");
656   gtk_widget_set_tooltip_text(g->grey_point, _("adjust to match the average luma of the subject"));
657 
658   g->shadows_range
659       = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "shadows_range"));
660   dt_bauhaus_slider_set_soft_max(g->shadows_range, 0.0);
661   dt_bauhaus_slider_set_format(g->shadows_range, "%.2f EV");
662   gtk_widget_set_tooltip_text(g->shadows_range, _("number of stops between middle gray and pure black\nthis is a reading a posemeter would give you on the scene"));
663 
664   g->dynamic_range
665       = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "dynamic_range"));
666   dt_bauhaus_slider_set_soft_range(g->dynamic_range, 0.5, 16.0);
667   dt_bauhaus_slider_set_format(g->dynamic_range, "%.2f EV");
668   gtk_widget_set_tooltip_text(g->dynamic_range, _("number of stops between pure black and pure white\nthis is a reading a posemeter would give you on the scene"));
669 
670   gtk_box_pack_start(GTK_BOX(vbox_log), dt_ui_section_label_new(_("optimize automatically")), FALSE, FALSE, 0);
671 
672   g->security_factor = dt_bauhaus_slider_from_params(self, "security_factor");
673   dt_bauhaus_slider_set_step(g->security_factor, 0.1);
674   dt_bauhaus_slider_set_format(g->security_factor, "%.2f %%");
675   gtk_widget_set_tooltip_text(g->security_factor, _("enlarge or shrink the computed dynamic range\nthis is useful when noise perturbates the measurements"));
676 
677   g->auto_button = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_combobox_new(self));
678   dt_bauhaus_widget_set_label(g->auto_button, NULL, N_("auto tune levels"));
679   gtk_widget_set_tooltip_text(g->auto_button, _("make an optimization with some guessing"));
680   gtk_box_pack_start(GTK_BOX(vbox_log), g->auto_button, TRUE, TRUE, 0);
681 
682   gtk_stack_add_named(GTK_STACK(g->mode_stack), vbox_log, "log");
683 
684   // start building top level widget
685   self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
686 
687   g->mode = dt_bauhaus_combobox_from_params(self, N_("mode"));
688   gtk_widget_set_tooltip_text(g->mode, _("tone mapping method"));
689 
690   gtk_box_pack_start(GTK_BOX(self->widget), g->mode_stack, TRUE, TRUE, 0);
691 }
692 
693 
694 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
695 // vim: shiftwidth=2 expandtab tabstop=2 cindent
696 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
697