1 /*
2     This file is part of darktable,
3     Copyright (C) 2019-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 
22 #include "bauhaus/bauhaus.h"
23 #include "common/iop_profile.h"
24 #include "common/colorspaces_inline_conversions.h"
25 #include "common/rgb_norms.h"
26 #include "develop/imageop.h"
27 #include "develop/imageop_math.h"
28 #include "develop/imageop_gui.h"
29 #include "dtgtk/drawingarea.h"
30 #include "gui/color_picker_proxy.h"
31 #include "gui/presets.h"
32 #include "gui/accelerators.h"
33 #include "libs/colorpicker.h"
34 
35 #define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(1)
36 #define DT_IOP_RGBCURVE_RES 256
37 #define DT_IOP_RGBCURVE_MAXNODES 20
38 #define DT_IOP_RGBCURVE_MIN_X_DISTANCE 0.0025f
39 // max iccprofile file name length
40 // must be in synch with filename in dt_colorspaces_color_profile_t in colorspaces.h
41 #define DT_IOP_COLOR_ICC_LEN 512
42 
43 DT_MODULE_INTROSPECTION(1, dt_iop_rgbcurve_params_t)
44 
45 typedef enum rgbcurve_channel_t
46 {
47   DT_IOP_RGBCURVE_R = 0,
48   DT_IOP_RGBCURVE_G = 1,
49   DT_IOP_RGBCURVE_B = 2,
50   DT_IOP_RGBCURVE_MAX_CHANNELS = 3
51 } rgbcurve_channel_t;
52 
53 typedef enum dt_iop_rgbcurve_autoscale_t
54 {
55   DT_S_SCALE_AUTOMATIC_RGB = 0, // $DESCRIPTION: "RGB, linked channels"
56   DT_S_SCALE_MANUAL_RGB = 1     // $DESCRIPTION: "RGB, independent channels"
57 } dt_iop_rgbcurve_autoscale_t;
58 
59 typedef struct dt_iop_rgbcurve_node_t
60 {
61   float x; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0
62   float y; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0
63 } dt_iop_rgbcurve_node_t;
64 
65 typedef struct dt_iop_rgbcurve_params_t
66 {
67   dt_iop_rgbcurve_node_t curve_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS]
68                                     [DT_IOP_RGBCURVE_MAXNODES]; // actual nodes for each curve
69   int curve_num_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS]; // $DEFAULT: 2 number of nodes per curve
70   int curve_type[DT_IOP_RGBCURVE_MAX_CHANNELS]; // $DEFAULT: MONOTONE_HERMITE (CATMULL_ROM, MONOTONE_HERMITE, CUBIC_SPLINE)
71   dt_iop_rgbcurve_autoscale_t curve_autoscale;  // $DEFAULT: DT_S_SCALE_AUTOMATIC_RGB $DESCRIPTION: "mode"
72   gboolean compensate_middle_grey; // $DEFAULT: 0  $DESCRIPTION: "compensate middle gray" scale the curve and histogram so middle gray is at .5
73   dt_iop_rgb_norms_t preserve_colors; // $DEFAULT: DT_RGB_NORM_LUMINANCE $DESCRIPTION: "preserve colors"
74 } dt_iop_rgbcurve_params_t;
75 
76 typedef struct dt_iop_rgbcurve_gui_data_t
77 {
78   dt_draw_curve_t *minmax_curve[DT_IOP_RGBCURVE_MAX_CHANNELS]; // curves for gui to draw
79   int minmax_curve_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS];
80   int minmax_curve_type[DT_IOP_RGBCURVE_MAX_CHANNELS];
81   GtkBox *hbox;
82   GtkDrawingArea *area;
83   GtkWidget *autoscale; // (DT_S_SCALE_MANUAL_RGB, DT_S_SCALE_AUTOMATIC_RGB)
84   GtkNotebook *channel_tabs;
85   GtkWidget *colorpicker;
86   GtkWidget *colorpicker_set_values;
87   GtkWidget *interpolator;
88   rgbcurve_channel_t channel;
89   double mouse_x, mouse_y;
90   int selected;
91   float draw_ys[DT_IOP_RGBCURVE_RES];
92   float draw_min_ys[DT_IOP_RGBCURVE_RES];
93   float draw_max_ys[DT_IOP_RGBCURVE_RES];
94   GtkWidget *chk_compensate_middle_grey;
95   GtkWidget *cmb_preserve_colors;
96   float zoom_factor;
97   float offset_x, offset_y;
98 } dt_iop_rgbcurve_gui_data_t;
99 
100 typedef struct dt_iop_rgbcurve_data_t
101 {
102   dt_iop_rgbcurve_params_t params;
103   dt_draw_curve_t *curve[DT_IOP_RGBCURVE_MAX_CHANNELS];    // curves for pipe piece and pixel processing
104   float table[DT_IOP_RGBCURVE_MAX_CHANNELS][0x10000];      // precomputed look-up tables for tone curve
105   float unbounded_coeffs[DT_IOP_RGBCURVE_MAX_CHANNELS][3]; // approximation for extrapolation
106   int curve_changed[DT_IOP_RGBCURVE_MAX_CHANNELS];         // curve type or number of nodes changed?
107   dt_colorspaces_color_profile_type_t type_work; // working color profile
108   char filename_work[DT_IOP_COLOR_ICC_LEN];
109 } dt_iop_rgbcurve_data_t;
110 
111 typedef float (*_curve_table_ptr)[0x10000];
112 typedef float (*_coeffs_table_ptr)[3];
113 
114 typedef struct dt_iop_rgbcurve_global_data_t
115 {
116   int kernel_rgbcurve;
117 } dt_iop_rgbcurve_global_data_t;
118 
name()119 const char *name()
120 {
121   return _("rgb curve");
122 }
123 
default_group()124 int default_group()
125 {
126   return IOP_GROUP_TONE | IOP_GROUP_GRADING;
127 }
128 
flags()129 int flags()
130 {
131   return IOP_FLAGS_SUPPORTS_BLENDING | IOP_FLAGS_ALLOW_TILING;
132 }
133 
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)134 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
135 {
136   return iop_cs_rgb;
137 }
138 
description(struct dt_iop_module_t * self)139 const char *description(struct dt_iop_module_t *self)
140 {
141   return dt_iop_set_description(self, _("alter an image’s tones using curves in RGB color space"),
142                                       _("corrective and creative"),
143                                       _("linear, RGB, display-referred"),
144                                       _("non-linear, RGB"),
145                                       _("linear, RGB, display-referred"));
146 }
147 
init_presets(dt_iop_module_so_t * self)148 void init_presets(dt_iop_module_so_t *self)
149 {
150   dt_iop_rgbcurve_params_t p;
151   memset(&p, 0, sizeof(p));
152   p.curve_num_nodes[DT_IOP_RGBCURVE_R] = 6;
153   p.curve_num_nodes[DT_IOP_RGBCURVE_G] = 7;
154   p.curve_num_nodes[DT_IOP_RGBCURVE_B] = 7;
155   p.curve_type[DT_IOP_RGBCURVE_R] = CUBIC_SPLINE;
156   p.curve_type[DT_IOP_RGBCURVE_G] = CUBIC_SPLINE;
157   p.curve_type[DT_IOP_RGBCURVE_B] = CUBIC_SPLINE;
158   p.curve_autoscale = DT_S_SCALE_AUTOMATIC_RGB;
159   p.compensate_middle_grey = 1;
160   p.preserve_colors = 1;
161 
162   float linear_ab[7] = { 0.0, 0.08, 0.3, 0.5, 0.7, 0.92, 1.0 };
163 
164   // linear a, b curves for presets
165   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_G][k].x = linear_ab[k];
166   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_G][k].y = linear_ab[k];
167   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_B][k].x = linear_ab[k];
168   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_B][k].y = linear_ab[k];
169 
170   // More useful low contrast curve (based on Samsung NX -2 Contrast)
171   p.curve_nodes[DT_IOP_RGBCURVE_R][0].x = 0.000000;
172   p.curve_nodes[DT_IOP_RGBCURVE_R][1].x = 0.003862;
173   p.curve_nodes[DT_IOP_RGBCURVE_R][2].x = 0.076613;
174   p.curve_nodes[DT_IOP_RGBCURVE_R][3].x = 0.169355;
175   p.curve_nodes[DT_IOP_RGBCURVE_R][4].x = 0.774194;
176   p.curve_nodes[DT_IOP_RGBCURVE_R][5].x = 1.000000;
177   p.curve_nodes[DT_IOP_RGBCURVE_R][0].y = 0.000000;
178   p.curve_nodes[DT_IOP_RGBCURVE_R][1].y = 0.007782;
179   p.curve_nodes[DT_IOP_RGBCURVE_R][2].y = 0.156182;
180   p.curve_nodes[DT_IOP_RGBCURVE_R][3].y = 0.290352;
181   p.curve_nodes[DT_IOP_RGBCURVE_R][4].y = 0.773852;
182   p.curve_nodes[DT_IOP_RGBCURVE_R][5].y = 1.000000;
183   dt_gui_presets_add_generic(_("contrast compression"), self->op,
184                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
185 
186   p.curve_num_nodes[DT_IOP_RGBCURVE_R] = 7;
187   float linear_L[7] = { 0.0, 0.08, 0.17, 0.50, 0.83, 0.92, 1.0 };
188 
189   // Linear - no contrast
190   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
191   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
192   dt_gui_presets_add_generic(_("gamma 1.0 (linear)"), self->op,
193                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
194 
195   // Linear contrast
196   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
197   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
198   p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.020;
199   p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.030;
200   p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.030;
201   p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.020;
202   dt_gui_presets_add_generic(_("contrast - med (linear)"), self->op,
203                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
204 
205   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
206   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
207   p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.040;
208   p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.060;
209   p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.060;
210   p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.040;
211   dt_gui_presets_add_generic(_("contrast - high (linear)"), self->op,
212                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
213 
214   // Gamma contrast
215   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
216   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
217   p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.020;
218   p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.030;
219   p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.030;
220   p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.020;
221   for(int k = 1; k < 6; k++)
222     p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].x, 2.2f);
223   for(int k = 1; k < 6; k++)
224     p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].y, 2.2f);
225   dt_gui_presets_add_generic(_("contrast - med (gamma 2.2)"), self->op,
226                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
227 
228   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
229   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
230   p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.040;
231   p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.060;
232   p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.060;
233   p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.040;
234   for(int k = 1; k < 6; k++)
235     p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].x, 2.2f);
236   for(int k = 1; k < 6; k++)
237     p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].y, 2.2f);
238   dt_gui_presets_add_generic(_("contrast - high (gamma 2.2)"), self->op,
239                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
240 
241   /** for pure power-like functions, we need more nodes close to the bounds**/
242 
243   p.curve_type[DT_IOP_RGBCURVE_R] = MONOTONE_HERMITE;
244 
245   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
246   for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
247 
248   // Gamma 2.0 - no contrast
249   for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(linear_L[k], 2.0f);
250   dt_gui_presets_add_generic(_("gamma 2.0"), self->op,
251                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
252 
253   // Gamma 0.5 - no contrast
254   for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(linear_L[k], 0.5f);
255   dt_gui_presets_add_generic(_("gamma 0.5"), self->op,
256                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
257 
258   // Log2 - no contrast
259   for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = logf(linear_L[k] + 1.0f) / logf(2.0f);
260   dt_gui_presets_add_generic(_("logarithm (base 2)"), self->op,
261                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
262 
263   // Exp2 - no contrast
264   for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(2.0f, linear_L[k]) - 1.0f;
265   dt_gui_presets_add_generic(_("exponential (base 2)"), self->op,
266                              self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
267 }
268 
_curve_to_mouse(const float x,const float zoom_factor,const float offset)269 static float _curve_to_mouse(const float x, const float zoom_factor, const float offset)
270 {
271   return (x - offset) * zoom_factor;
272 }
273 
_mouse_to_curve(const float x,const float zoom_factor,const float offset)274 static float _mouse_to_curve(const float x, const float zoom_factor, const float offset)
275 {
276   return (x / zoom_factor) + offset;
277 }
278 
picker_scale(const float * const in,float * out,dt_iop_rgbcurve_params_t * p,const dt_iop_order_iccprofile_info_t * const work_profile)279 static void picker_scale(const float *const in, float *out, dt_iop_rgbcurve_params_t *p,
280                          const dt_iop_order_iccprofile_info_t *const work_profile)
281 {
282   switch(p->curve_autoscale)
283   {
284     case DT_S_SCALE_MANUAL_RGB:
285       if(p->compensate_middle_grey && work_profile)
286       {
287         for(int c = 0; c < 3; c++) out[c] = dt_ioppr_compensate_middle_grey(in[c], work_profile);
288       }
289       else
290       {
291         for(int c = 0; c < 3; c++) out[c] = in[c];
292       }
293       break;
294     case DT_S_SCALE_AUTOMATIC_RGB:
295     {
296       const float val
297           = (work_profile) ? dt_ioppr_get_rgb_matrix_luminance(in,
298                                                                work_profile->matrix_in,
299                                                                work_profile->lut_in,
300                                                                work_profile->unbounded_coeffs_in,
301                                                                work_profile->lutsize,
302                                                                work_profile->nonlinearlut)
303                            : dt_camera_rgb_luminance(in);
304       if(p->compensate_middle_grey && work_profile)
305       {
306         out[0] = dt_ioppr_compensate_middle_grey(val, work_profile);
307       }
308       else
309       {
310         out[0] = val;
311       }
312       out[1] = out[2] = 0.f;
313     }
314     break;
315   }
316 
317   for(int c = 0; c < 3; c++) out[c] = CLAMP(out[c], 0.0f, 1.0f);
318 }
319 
_rgbcurve_show_hide_controls(dt_iop_rgbcurve_params_t * p,dt_iop_rgbcurve_gui_data_t * g)320 static void _rgbcurve_show_hide_controls(dt_iop_rgbcurve_params_t *p, dt_iop_rgbcurve_gui_data_t *g)
321 {
322   gtk_notebook_set_show_tabs(g->channel_tabs, p->curve_autoscale == DT_S_SCALE_MANUAL_RGB);
323 
324   gtk_widget_set_visible(g->cmb_preserve_colors, p->curve_autoscale == DT_S_SCALE_AUTOMATIC_RGB);
325 }
326 
_is_identity(dt_iop_rgbcurve_params_t * p,rgbcurve_channel_t channel)327 static gboolean _is_identity(dt_iop_rgbcurve_params_t *p, rgbcurve_channel_t channel)
328 {
329   for(int k=0; k<p->curve_num_nodes[channel]; k++)
330     if(p->curve_nodes[channel][k].x != p->curve_nodes[channel][k].y) return FALSE;
331 
332   return TRUE;
333 }
334 
gui_changed(dt_iop_module_t * self,GtkWidget * w,void * previous)335 void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
336 {
337   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
338   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
339 
340   if(w == g->autoscale)
341   {
342     g->channel = DT_IOP_RGBCURVE_R;
343     gtk_notebook_set_current_page(GTK_NOTEBOOK(g->channel_tabs), DT_IOP_RGBCURVE_R);
344 
345     _rgbcurve_show_hide_controls(p, g);
346 
347     // switching to manual scale, if G and B not touched yet, just make them identical to global setting (R)
348     if(p->curve_autoscale == DT_S_SCALE_MANUAL_RGB
349       && _is_identity(p, DT_IOP_RGBCURVE_G)
350       && _is_identity(p, DT_IOP_RGBCURVE_B))
351     {
352       for(int k=0; k<DT_IOP_RGBCURVE_MAXNODES; k++)
353         p->curve_nodes[DT_IOP_RGBCURVE_G][k]
354           = p->curve_nodes[DT_IOP_RGBCURVE_B][k] = p->curve_nodes[DT_IOP_RGBCURVE_R][k];
355 
356       p->curve_num_nodes[DT_IOP_RGBCURVE_G] = p->curve_num_nodes[DT_IOP_RGBCURVE_B]
357         = p->curve_num_nodes[DT_IOP_RGBCURVE_R];
358       p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
359         = p->curve_type[DT_IOP_RGBCURVE_R];
360     }
361   }
362   else if(w == g->chk_compensate_middle_grey)
363   {
364     const dt_iop_order_iccprofile_info_t *const work_profile
365         = dt_ioppr_get_iop_work_profile_info(self, self->dev->iop);
366     if(work_profile == NULL) return;
367 
368     for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
369     {
370       for(int k = 0; k < p->curve_num_nodes[ch]; k++)
371       {
372         if(p->compensate_middle_grey)
373         {
374           // we transform the curve nodes from the image colorspace to lab
375           p->curve_nodes[ch][k].x = dt_ioppr_compensate_middle_grey(p->curve_nodes[ch][k].x, work_profile);
376           p->curve_nodes[ch][k].y = dt_ioppr_compensate_middle_grey(p->curve_nodes[ch][k].y, work_profile);
377         }
378         else
379         {
380           // we transform the curve nodes from lab to the image colorspace
381           p->curve_nodes[ch][k].x = dt_ioppr_uncompensate_middle_grey(p->curve_nodes[ch][k].x, work_profile);
382           p->curve_nodes[ch][k].y = dt_ioppr_uncompensate_middle_grey(p->curve_nodes[ch][k].y, work_profile);
383         }
384       }
385     }
386 
387     self->histogram_middle_grey = p->compensate_middle_grey;
388   }
389 }
390 
interpolator_callback(GtkWidget * widget,dt_iop_module_t * self)391 static void interpolator_callback(GtkWidget *widget, dt_iop_module_t *self)
392 {
393   if(darktable.gui->reset) return;
394   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
395   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
396 
397   const int combo = dt_bauhaus_combobox_get(widget);
398 
399   if(combo == 0)
400     p->curve_type[DT_IOP_RGBCURVE_R] = p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
401         = CUBIC_SPLINE;
402   else if(combo == 1)
403     p->curve_type[DT_IOP_RGBCURVE_R] = p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
404         = CATMULL_ROM;
405   else if(combo == 2)
406     p->curve_type[DT_IOP_RGBCURVE_R] = p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
407         = MONOTONE_HERMITE;
408 
409   dt_dev_add_history_item(darktable.develop, self, TRUE);
410   gtk_widget_queue_draw(GTK_WIDGET(g->area));
411 }
412 
tab_switch_callback(GtkNotebook * notebook,GtkWidget * page,guint page_num,gpointer user_data)413 static void tab_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
414 {
415   if(darktable.gui->reset) return;
416   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
417   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
418 
419   g->channel = (rgbcurve_channel_t)page_num;
420 
421   gtk_widget_queue_draw(self->widget);
422 }
423 
_area_resized_callback(GtkWidget * widget,GdkEvent * event,gpointer user_data)424 static gboolean _area_resized_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
425 {
426   GtkRequisition r;
427   GtkAllocation allocation;
428   gtk_widget_get_allocation(widget, &allocation);
429   r.width = allocation.width;
430   r.height = allocation.width;
431   gtk_widget_get_preferred_size(widget, &r, NULL);
432   return TRUE;
433 }
434 
_add_node(dt_iop_rgbcurve_node_t * curve_nodes,int * nodes,float x,float y)435 static inline int _add_node(dt_iop_rgbcurve_node_t *curve_nodes, int *nodes, float x, float y)
436 {
437   int selected = -1;
438   if(curve_nodes[0].x > x)
439     selected = 0;
440   else
441   {
442     for(int k = 1; k < *nodes; k++)
443     {
444       if(curve_nodes[k].x > x)
445       {
446         selected = k;
447         break;
448       }
449     }
450   }
451   if(selected == -1) selected = *nodes;
452   for(int i = *nodes; i > selected; i--)
453   {
454     curve_nodes[i].x = curve_nodes[i - 1].x;
455     curve_nodes[i].y = curve_nodes[i - 1].y;
456   }
457   // found a new point
458   curve_nodes[selected].x = x;
459   curve_nodes[selected].y = y;
460   (*nodes)++;
461   return selected;
462 }
463 
_add_node_from_picker(dt_iop_rgbcurve_params_t * p,const float * const in,const float increment,const int ch,const dt_iop_order_iccprofile_info_t * const work_profile)464 static inline int _add_node_from_picker(dt_iop_rgbcurve_params_t *p, const float *const in, const float increment,
465                                         const int ch, const dt_iop_order_iccprofile_info_t *const work_profile)
466 {
467   float x = 0.f;
468   float y = 0.f;
469   float val = 0.f;
470 
471   if(p->curve_autoscale == DT_S_SCALE_AUTOMATIC_RGB)
472     val = (work_profile) ? dt_ioppr_get_rgb_matrix_luminance(in,
473                                                              work_profile->matrix_in,
474                                                              work_profile->lut_in,
475                                                              work_profile->unbounded_coeffs_in,
476                                                              work_profile->lutsize,
477                                                              work_profile->nonlinearlut)
478                          : dt_camera_rgb_luminance(in);
479   else
480     val = in[ch];
481 
482   if(p->compensate_middle_grey && work_profile)
483     y = x = dt_ioppr_compensate_middle_grey(val, work_profile);
484   else
485     y = x = val;
486 
487   x -= increment;
488   y += increment;
489 
490   CLAMP(x, 0.f, 1.f);
491   CLAMP(y, 0.f, 1.f);
492 
493   return _add_node(p->curve_nodes[ch], &p->curve_num_nodes[ch], x, y);
494 }
495 
color_picker_apply(dt_iop_module_t * self,GtkWidget * picker,dt_dev_pixelpipe_iop_t * piece)496 void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_iop_t *piece)
497 {
498   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
499   if(picker == g->colorpicker_set_values)
500   {
501     dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
502     dt_iop_rgbcurve_params_t *d = (dt_iop_rgbcurve_params_t *)self->default_params;
503 
504     const int ch = g->channel;
505     const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(piece->pipe);
506 
507     // reset current curve
508     p->curve_num_nodes[ch] = d->curve_num_nodes[ch];
509     p->curve_type[ch] = d->curve_type[ch];
510     for(int k = 0; k < DT_IOP_RGBCURVE_MAXNODES; k++)
511     {
512       p->curve_nodes[ch][k].x = d->curve_nodes[ch][k].x;
513       p->curve_nodes[ch][k].y = d->curve_nodes[ch][k].y;
514     }
515 
516     const GdkModifierType state = dt_key_modifier_state();
517     int picker_set_upper_lower; // flat=0, lower=-1, upper=1
518     if(dt_modifier_is(state, GDK_CONTROL_MASK))
519       picker_set_upper_lower = 1;
520     else if(dt_modifier_is(state, GDK_SHIFT_MASK))
521       picker_set_upper_lower = -1;
522     else
523       picker_set_upper_lower = 0;
524 
525     // now add 4 nodes: min, avg, center, max
526     const float increment = 0.05f * picker_set_upper_lower;
527 
528     _add_node_from_picker(p, self->picked_color_min, 0.f, ch, work_profile);
529     _add_node_from_picker(p, self->picked_color, increment, ch, work_profile);
530     _add_node_from_picker(p, self->picked_color_max, 0.f, ch, work_profile);
531 
532     if(p->curve_num_nodes[ch] == 5)
533       _add_node(p->curve_nodes[ch], &p->curve_num_nodes[ch],
534                 p->curve_nodes[ch][1].x - increment + (p->curve_nodes[ch][3].x - p->curve_nodes[ch][1].x) / 2.f,
535                 p->curve_nodes[ch][1].y + increment + (p->curve_nodes[ch][3].y - p->curve_nodes[ch][1].y) / 2.f);
536 
537     dt_dev_add_history_item(darktable.develop, self, TRUE);
538   }
539 
540   dt_control_queue_redraw_widget(self->widget);
541 }
542 
_sanity_check(const float x,const int selected,const int nodes,const dt_iop_rgbcurve_node_t * curve)543 static gboolean _sanity_check(const float x, const int selected, const int nodes,
544                               const dt_iop_rgbcurve_node_t *curve)
545 {
546   gboolean point_valid = TRUE;
547 
548   // check if it is not too close to other node
549   const float min_dist = DT_IOP_RGBCURVE_MIN_X_DISTANCE; // in curve coordinates
550   if((selected > 0 && x - curve[selected - 1].x <= min_dist)
551      || (selected < nodes - 1 && curve[selected + 1].x - x <= min_dist))
552     point_valid = FALSE;
553 
554   // for all points, x coordinate of point must be strictly larger than
555   // the x coordinate of the previous point
556   if((selected > 0 && (curve[selected - 1].x >= x)) || (selected < nodes - 1 && (curve[selected + 1].x <= x)))
557   {
558     point_valid = FALSE;
559   }
560 
561   return point_valid;
562 }
563 
_move_point_internal(dt_iop_module_t * self,GtkWidget * widget,float dx,float dy,guint state)564 static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
565 {
566   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
567   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
568 
569   const int ch = g->channel;
570   dt_iop_rgbcurve_node_t *curve = p->curve_nodes[ch];
571 
572   float multiplier;
573 
574   if(dt_modifier_is(state, GDK_SHIFT_MASK))
575   {
576     multiplier = dt_conf_get_float("darkroom/ui/scale_rough_step_multiplier");
577   }
578   else if(dt_modifier_is(state, GDK_CONTROL_MASK))
579   {
580     multiplier = dt_conf_get_float("darkroom/ui/scale_precise_step_multiplier");
581   }
582   else
583   {
584     multiplier = dt_conf_get_float("darkroom/ui/scale_step_multiplier");
585   }
586 
587   dx *= multiplier;
588   dy *= multiplier;
589 
590   const float new_x = CLAMP(curve[g->selected].x + dx, 0.0f, 1.0f);
591   const float new_y = CLAMP(curve[g->selected].y + dy, 0.0f, 1.0f);
592 
593   gtk_widget_queue_draw(widget);
594 
595   if(_sanity_check(new_x, g->selected, p->curve_num_nodes[ch], p->curve_nodes[ch]))
596   {
597     curve[g->selected].x = new_x;
598     curve[g->selected].y = new_y;
599 
600     dt_iop_queue_history_update(self, FALSE);
601   }
602 
603   return TRUE;
604 }
605 
606 #define RGBCURVE_DEFAULT_STEP (0.001f)
607 
_area_scrolled_callback(GtkWidget * widget,GdkEventScroll * event,dt_iop_module_t * self)608 static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
609 {
610   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
611   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
612 
613   gdouble delta_y;
614 
615   if(dt_gui_ignore_scroll(event)) return FALSE;
616 
617   if(darktable.develop->darkroom_skip_mouse_events)
618   {
619     if(dt_gui_get_scroll_deltas(event, NULL, &delta_y))
620     {
621       GtkAllocation allocation;
622       gtk_widget_get_allocation(widget, &allocation);
623 
624       const float mx = g->mouse_x;
625       const float my = g->mouse_y;
626       const float linx = _mouse_to_curve(mx, g->zoom_factor, g->offset_x),
627                   liny = _mouse_to_curve(my, g->zoom_factor, g->offset_y);
628 
629       g->zoom_factor *= 1.0 - 0.1 * delta_y;
630       if(g->zoom_factor < 1.f) g->zoom_factor = 1.f;
631 
632       g->offset_x = linx - (mx / g->zoom_factor);
633       g->offset_y = liny - (my / g->zoom_factor);
634 
635       g->offset_x = CLAMP(g->offset_x, 0.f, (g->zoom_factor - 1.f) / g->zoom_factor);
636       g->offset_y = CLAMP(g->offset_y, 0.f, (g->zoom_factor - 1.f) / g->zoom_factor);
637 
638       gtk_widget_queue_draw(self->widget);
639     }
640 
641     return TRUE;
642   }
643 
644   // if autoscale is on: do not modify g and b curves
645   if((p->curve_autoscale != DT_S_SCALE_MANUAL_RGB) && g->channel != DT_IOP_RGBCURVE_R) return TRUE;
646 
647   if(g->selected < 0) return TRUE;
648 
649   dt_iop_color_picker_reset(self, TRUE);
650 
651   if(dt_gui_get_scroll_delta(event, &delta_y))
652   {
653     delta_y *= -RGBCURVE_DEFAULT_STEP;
654     return _move_point_internal(self, widget, 0.0, delta_y, event->state);
655   }
656 
657   return TRUE;
658 }
659 
_area_key_press_callback(GtkWidget * widget,GdkEventKey * event,dt_iop_module_t * self)660 static gboolean _area_key_press_callback(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *self)
661 {
662   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
663   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
664 
665   if(darktable.develop->darkroom_skip_mouse_events) return FALSE;
666 
667   // if autoscale is on: do not modify g and b curves
668   if((p->curve_autoscale != DT_S_SCALE_MANUAL_RGB) && g->channel != DT_IOP_RGBCURVE_R) return TRUE;
669 
670   if(g->selected < 0) return FALSE;
671 
672   int handled = 0;
673   float dx = 0.0f, dy = 0.0f;
674   if(event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up)
675   {
676     handled = 1;
677     dy = RGBCURVE_DEFAULT_STEP;
678   }
679   else if(event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down)
680   {
681     handled = 1;
682     dy = -RGBCURVE_DEFAULT_STEP;
683   }
684   else if(event->keyval == GDK_KEY_Right || event->keyval == GDK_KEY_KP_Right)
685   {
686     handled = 1;
687     dx = RGBCURVE_DEFAULT_STEP;
688   }
689   else if(event->keyval == GDK_KEY_Left || event->keyval == GDK_KEY_KP_Left)
690   {
691     handled = 1;
692     dx = -RGBCURVE_DEFAULT_STEP;
693   }
694 
695   if(!handled) return FALSE;
696 
697   dt_iop_color_picker_reset(self, TRUE);
698   return _move_point_internal(self, widget, dx, dy, event->state);
699 }
700 
701 #undef RGBCURVE_DEFAULT_STEP
702 
_area_enter_notify_callback(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)703 static gboolean _area_enter_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
704 {
705   gtk_widget_queue_draw(widget);
706   return TRUE;
707 }
708 
_area_leave_notify_callback(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)709 static gboolean _area_leave_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
710 {
711   gtk_widget_queue_draw(widget);
712   return TRUE;
713 }
714 
_area_draw_callback(GtkWidget * widget,cairo_t * crf,dt_iop_module_t * self)715 static gboolean _area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
716 {
717   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
718   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
719   dt_develop_t *dev = darktable.develop;
720 
721   const int ch = g->channel;
722   const int nodes = p->curve_num_nodes[ch];
723   const int autoscale = p->curve_autoscale;
724   dt_iop_rgbcurve_node_t *curve_nodes = p->curve_nodes[ch];
725 
726   if(g->minmax_curve_type[ch] != p->curve_type[ch] || g->minmax_curve_nodes[ch] != p->curve_num_nodes[ch])
727   {
728     dt_draw_curve_destroy(g->minmax_curve[ch]);
729     g->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->curve_type[ch]);
730     g->minmax_curve_nodes[ch] = p->curve_num_nodes[ch];
731     g->minmax_curve_type[ch] = p->curve_type[ch];
732     for(int k = 0; k < p->curve_num_nodes[ch]; k++)
733       (void)dt_draw_curve_add_point(g->minmax_curve[ch], p->curve_nodes[ch][k].x, p->curve_nodes[ch][k].y);
734   }
735   else
736   {
737     for(int k = 0; k < p->curve_num_nodes[ch]; k++)
738       dt_draw_curve_set_point(g->minmax_curve[ch], k, p->curve_nodes[ch][k].x, p->curve_nodes[ch][k].y);
739   }
740   dt_draw_curve_t *minmax_curve = g->minmax_curve[ch];
741   dt_draw_curve_calc_values(minmax_curve, 0.0, 1.0, DT_IOP_RGBCURVE_RES, NULL, g->draw_ys);
742 
743   float unbounded_coeffs[3];
744   const float xm = curve_nodes[nodes - 1].x;
745   {
746     const float x[4] = { 0.7f * xm, 0.8f * xm, 0.9f * xm, 1.0f * xm };
747     const float y[4] = { g->draw_ys[CLAMP((int)(x[0] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)],
748                          g->draw_ys[CLAMP((int)(x[1] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)],
749                          g->draw_ys[CLAMP((int)(x[2] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)],
750                          g->draw_ys[CLAMP((int)(x[3] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)] };
751     dt_iop_estimate_exp(x, y, 4, unbounded_coeffs);
752   }
753 
754   const int inset = DT_GUI_CURVE_EDITOR_INSET;
755   GtkAllocation allocation;
756   gtk_widget_get_allocation(widget, &allocation);
757   int width = allocation.width, height = allocation.height;
758   cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
759   cairo_t *cr = cairo_create(cst);
760 
761   // clear bg
762   cairo_set_source_rgb(cr, .2, .2, .2);
763   cairo_paint(cr);
764 
765   cairo_translate(cr, inset, inset);
766   width -= 2 * inset;
767   height -= 2 * inset;
768   char text[256];
769 
770   cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
771   cairo_set_source_rgb(cr, .1, .1, .1);
772   cairo_rectangle(cr, 0, 0, width, height);
773   cairo_stroke(cr);
774 
775   cairo_set_source_rgb(cr, .3, .3, .3);
776   cairo_rectangle(cr, 0, 0, width, height);
777   cairo_fill(cr);
778 
779   // draw grid
780   cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
781   cairo_set_source_rgb(cr, .1, .1, .1);
782 
783   cairo_translate(cr, 0, height);
784 
785   dt_draw_grid_zoomed(cr, 4, 0.f, 0.f, 1.f, 1.f, width, height, g->zoom_factor, g->offset_x, g->offset_y);
786 
787   const double dashed[] = { 4.0, 4.0 };
788   const int len = sizeof(dashed) / sizeof(dashed[0]);
789   cairo_set_dash(cr, dashed, len, 0);
790   dt_draw_grid_zoomed(cr, 8, 0.f, 0.f, 1.f, 1.f, width, height, g->zoom_factor, g->offset_x, g->offset_y);
791   cairo_set_dash(cr, dashed, 0, 0);
792 
793   // draw identity line
794   cairo_move_to(cr, _curve_to_mouse(0.f, g->zoom_factor, g->offset_x) * width,
795                 _curve_to_mouse(0.f, g->zoom_factor, g->offset_y) * -height);
796   cairo_line_to(cr, _curve_to_mouse(1.f, g->zoom_factor, g->offset_x) * width,
797                 _curve_to_mouse(1.f, g->zoom_factor, g->offset_y) * -height);
798   cairo_stroke(cr);
799 
800   // if autoscale is on: do not display g and b curves
801   if((autoscale != DT_S_SCALE_MANUAL_RGB) && ch != DT_IOP_RGBCURVE_R) goto finally;
802 
803   // draw nodes positions
804   cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
805   cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
806 
807   for(int k = 0; k < nodes; k++)
808   {
809     const float x = _curve_to_mouse(curve_nodes[k].x, g->zoom_factor, g->offset_x),
810                 y = _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y);
811 
812     cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(3), 0, 2. * M_PI);
813     cairo_stroke(cr);
814   }
815 
816   // draw selected cursor
817   cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
818 
819   if(g->selected >= 0)
820   {
821     cairo_set_source_rgb(cr, .9, .9, .9);
822     const float x = _curve_to_mouse(curve_nodes[g->selected].x, g->zoom_factor, g->offset_x),
823                 y = _curve_to_mouse(curve_nodes[g->selected].y, g->zoom_factor, g->offset_y);
824 
825     cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
826     cairo_stroke(cr);
827   }
828 
829   // draw histogram in background
830   // only if module is enabled
831   if(self->enabled)
832   {
833     const uint32_t *hist = self->histogram;
834     const gboolean is_linear = darktable.lib->proxy.histogram.is_linear;
835     float hist_max;
836 
837     if(autoscale == DT_S_SCALE_AUTOMATIC_RGB)
838       hist_max = fmaxf(self->histogram_max[DT_IOP_RGBCURVE_R], fmaxf(self->histogram_max[DT_IOP_RGBCURVE_G],self->histogram_max[DT_IOP_RGBCURVE_B]));
839     else
840       hist_max = self->histogram_max[ch];
841 
842     if (!is_linear)
843       hist_max = logf(1.0 + hist_max);
844 
845     if(hist && hist_max > 0.0f)
846     {
847       cairo_push_group_with_content(cr, CAIRO_CONTENT_COLOR);
848       cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
849 
850       if(autoscale == DT_S_SCALE_AUTOMATIC_RGB)
851       {
852         cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
853         for(int k=DT_IOP_RGBCURVE_R; k<DT_IOP_RGBCURVE_MAX_CHANNELS; k++)
854         {
855           set_color(cr, darktable.bauhaus->graph_colors[k]);
856           dt_draw_histogram_8_zoomed(cr, hist, 4, k, g->zoom_factor, g->offset_x * 255.0, g->offset_y * hist_max,
857                                      is_linear);
858         }
859       }
860       else if(autoscale == DT_S_SCALE_MANUAL_RGB)
861       {
862         set_color(cr, darktable.bauhaus->graph_colors[ch]);
863         dt_draw_histogram_8_zoomed(cr, hist, 4, ch, g->zoom_factor, g->offset_x * 255.0, g->offset_y * hist_max,
864                                    is_linear);
865       }
866 
867       cairo_pop_group_to_source(cr);
868       cairo_paint_with_alpha(cr, 0.2);
869     }
870 
871     if(self->request_color_pick != DT_REQUEST_COLORPICK_OFF)
872     {
873       const dt_iop_order_iccprofile_info_t *const work_profile
874           = dt_ioppr_get_iop_work_profile_info(self, self->dev->iop);
875 
876       dt_aligned_pixel_t picker_mean, picker_min, picker_max;
877 
878       // the global live samples ...
879       GSList *samples = darktable.lib->proxy.colorpicker.live_samples;
880       if(samples)
881       {
882         const dt_iop_order_iccprofile_info_t *const histogram_profile = dt_ioppr_get_histogram_profile_info(dev);
883         if(work_profile && histogram_profile)
884         {
885           for(; samples; samples = g_slist_next(samples))
886           {
887             dt_colorpicker_sample_t *sample = samples->data;
888 
889             // this functions need a 4c image
890             for(int k = 0; k < 3; k++)
891             {
892               picker_mean[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MEAN][k];
893               picker_min[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MIN][k];
894               picker_max[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MAX][k];
895             }
896             picker_mean[3] = picker_min[3] = picker_max[3] = 1.f;
897 
898             dt_ioppr_transform_image_colorspace_rgb(picker_mean, picker_mean, 1, 1, histogram_profile,
899                                                     work_profile, "rgb curve");
900             dt_ioppr_transform_image_colorspace_rgb(picker_min, picker_min, 1, 1, histogram_profile, work_profile,
901                                                     "rgb curve");
902             dt_ioppr_transform_image_colorspace_rgb(picker_max, picker_max, 1, 1, histogram_profile, work_profile,
903                                                     "rgb curve");
904 
905             picker_scale(picker_mean, picker_mean, p, work_profile);
906             picker_scale(picker_min, picker_min, p, work_profile);
907             picker_scale(picker_max, picker_max, p, work_profile);
908 
909             // Convert abcissa to log coordinates if needed
910             picker_min[ch] = _curve_to_mouse(picker_min[ch], g->zoom_factor, g->offset_x);
911             picker_max[ch] = _curve_to_mouse(picker_max[ch], g->zoom_factor, g->offset_x);
912             picker_mean[ch] = _curve_to_mouse(picker_mean[ch], g->zoom_factor, g->offset_x);
913 
914             cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.15);
915             cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
916                             -height);
917             cairo_fill(cr);
918             cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.5);
919             cairo_move_to(cr, width * picker_mean[ch], 0);
920             cairo_line_to(cr, width * picker_mean[ch], -height);
921             cairo_stroke(cr);
922           }
923       }
924       }
925 
926       // ... and the local sample
927       if(self->picked_color_max[ch] >= 0.0f)
928       {
929         PangoLayout *layout;
930         PangoRectangle ink;
931         PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
932         pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
933         pango_font_description_set_absolute_size(desc, PANGO_SCALE);
934         layout = pango_cairo_create_layout(cr);
935         pango_layout_set_font_description(layout, desc);
936 
937         picker_scale(self->picked_color, picker_mean, p, work_profile);
938         picker_scale(self->picked_color_min, picker_min, p, work_profile);
939         picker_scale(self->picked_color_max, picker_max, p, work_profile);
940 
941         // scale conservatively to 100% of width:
942         snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
943         pango_layout_set_text(layout, text, -1);
944         pango_layout_get_pixel_extents(layout, &ink, NULL);
945         pango_font_description_set_absolute_size(desc, width * 1.0 / ink.width * PANGO_SCALE);
946         pango_layout_set_font_description(layout, desc);
947 
948         picker_min[ch] = _curve_to_mouse(picker_min[ch], g->zoom_factor, g->offset_x);
949         picker_max[ch] = _curve_to_mouse(picker_max[ch], g->zoom_factor, g->offset_x);
950         picker_mean[ch] = _curve_to_mouse(picker_mean[ch], g->zoom_factor, g->offset_x);
951 
952         cairo_set_source_rgba(cr, 0.7, 0.5, 0.5, 0.33);
953         cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
954                         -height);
955         cairo_fill(cr);
956         cairo_set_source_rgba(cr, 0.9, 0.7, 0.7, 0.5);
957         cairo_move_to(cr, width * picker_mean[ch], 0);
958         cairo_line_to(cr, width * picker_mean[ch], -height);
959         cairo_stroke(cr);
960 
961         picker_scale(self->picked_color, picker_mean, p, work_profile);
962         picker_scale(self->picked_output_color, picker_min, p, work_profile);
963         snprintf(text, sizeof(text), "%.1f → %.1f", picker_mean[ch] * 255.f, picker_min[ch] * 255.f);
964 
965         cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
966         cairo_set_font_size(cr, DT_PIXEL_APPLY_DPI(0.04) * height);
967         pango_layout_set_text(layout, text, -1);
968         pango_layout_get_pixel_extents(layout, &ink, NULL);
969         cairo_move_to(cr, 0.02f * width, -0.94 * height - ink.height - ink.y);
970         pango_cairo_show_layout(cr, layout);
971         cairo_stroke(cr);
972         pango_font_description_free(desc);
973         g_object_unref(layout);
974       }
975     }
976   }
977 
978   // draw zoom info
979   if(darktable.develop->darkroom_skip_mouse_events)
980   {
981     PangoLayout *layout;
982     PangoRectangle ink;
983     PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
984     pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
985     pango_font_description_set_absolute_size(desc, PANGO_SCALE);
986     layout = pango_cairo_create_layout(cr);
987     pango_layout_set_font_description(layout, desc);
988 
989     // scale conservatively to 100% of width:
990     snprintf(text, sizeof(text), "zoom: 100 x: 100 y: 100");
991     pango_layout_set_text(layout, text, -1);
992     pango_layout_get_pixel_extents(layout, &ink, NULL);
993     pango_font_description_set_absolute_size(desc, width * 1.0 / ink.width * PANGO_SCALE);
994     pango_layout_set_font_description(layout, desc);
995 
996     snprintf(text, sizeof(text), "zoom: %i x: %i y: %i", (int)((g->zoom_factor - 1.f) * 100.f),
997              (int)(g->offset_x * 100.f), (int)(g->offset_y * 100.f));
998 
999     cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 0.5);
1000     pango_layout_set_text(layout, text, -1);
1001     pango_layout_get_pixel_extents(layout, &ink, NULL);
1002     cairo_move_to(cr, 0.98f * width - ink.width - ink.x, -0.02 * height - ink.height - ink.y);
1003     pango_cairo_show_layout(cr, layout);
1004     cairo_stroke(cr);
1005     pango_font_description_free(desc);
1006     g_object_unref(layout);
1007   }
1008   else if(g->selected >= 0)
1009   {
1010     // draw information about current selected node
1011     PangoLayout *layout;
1012     PangoRectangle ink;
1013     PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
1014     pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
1015     pango_font_description_set_absolute_size(desc, PANGO_SCALE);
1016     layout = pango_cairo_create_layout(cr);
1017     pango_layout_set_font_description(layout, desc);
1018 
1019     // scale conservatively to 100% of width:
1020     snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
1021     pango_layout_set_text(layout, text, -1);
1022     pango_layout_get_pixel_extents(layout, &ink, NULL);
1023     pango_font_description_set_absolute_size(desc, width * 1.0 / ink.width * PANGO_SCALE);
1024     pango_layout_set_font_description(layout, desc);
1025 
1026     const float min_scale_value = 0.0f;
1027     const float max_scale_value = 255.0f;
1028 
1029     const float x_node_value = curve_nodes[g->selected].x * (max_scale_value - min_scale_value) + min_scale_value;
1030     const float y_node_value = curve_nodes[g->selected].y * (max_scale_value - min_scale_value) + min_scale_value;
1031     const float d_node_value = y_node_value - x_node_value;
1032     snprintf(text, sizeof(text), "%.1f / %.1f ( %+.1f)", x_node_value, y_node_value, d_node_value);
1033 
1034     cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
1035     pango_layout_set_text(layout, text, -1);
1036     pango_layout_get_pixel_extents(layout, &ink, NULL);
1037     cairo_move_to(cr, 0.98f * width - ink.width - ink.x, -0.02 * height - ink.height - ink.y);
1038     pango_cairo_show_layout(cr, layout);
1039     cairo_stroke(cr);
1040     pango_font_description_free(desc);
1041     g_object_unref(layout);
1042 
1043     // enlarge selected node
1044     cairo_set_source_rgb(cr, .9, .9, .9);
1045     const float x = _curve_to_mouse(curve_nodes[g->selected].x, g->zoom_factor, g->offset_x),
1046                 y = _curve_to_mouse(curve_nodes[g->selected].y, g->zoom_factor, g->offset_y);
1047 
1048     cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
1049     cairo_stroke(cr);
1050   }
1051 
1052   // draw curve
1053   cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1054   cairo_set_source_rgb(cr, .9, .9, .9);
1055 
1056   const float y_offset = _curve_to_mouse(g->draw_ys[0], g->zoom_factor, g->offset_y);
1057   cairo_move_to(cr, 0, -height * y_offset);
1058 
1059   for(int k = 1; k < DT_IOP_RGBCURVE_RES; k++)
1060   {
1061     const float xx = k / (DT_IOP_RGBCURVE_RES - 1.0f);
1062     float yy;
1063 
1064     if(xx > xm)
1065     {
1066       yy = dt_iop_eval_exp(unbounded_coeffs, xx);
1067     }
1068     else
1069     {
1070       yy = g->draw_ys[k];
1071     }
1072 
1073     const float x = _curve_to_mouse(xx, g->zoom_factor, g->offset_x),
1074                 y = _curve_to_mouse(yy, g->zoom_factor, g->offset_y);
1075 
1076     cairo_line_to(cr, x * width, -height * y);
1077   }
1078   cairo_stroke(cr);
1079 
1080 finally:
1081   cairo_destroy(cr);
1082   cairo_set_source_surface(crf, cst, 0, 0);
1083   cairo_paint(crf);
1084   cairo_surface_destroy(cst);
1085   return TRUE;
1086 }
1087 
_area_motion_notify_callback(GtkWidget * widget,GdkEventMotion * event,dt_iop_module_t * self)1088 static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
1089 {
1090   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
1091   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
1092 
1093   const int inset = DT_GUI_CURVE_EDITOR_INSET;
1094 
1095   // drag the draw area
1096   if(darktable.develop->darkroom_skip_mouse_events)
1097   {
1098     GtkAllocation allocation;
1099     gtk_widget_get_allocation(widget, &allocation);
1100     const int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
1101 
1102     const float mx = g->mouse_x;
1103     const float my = g->mouse_y;
1104 
1105     g->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1106     g->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1107 
1108     if(event->state & GDK_BUTTON1_MASK)
1109     {
1110       g->offset_x += (mx - g->mouse_x) / g->zoom_factor;
1111       g->offset_y += (my - g->mouse_y) / g->zoom_factor;
1112 
1113       g->offset_x = CLAMP(g->offset_x, 0.f, (g->zoom_factor - 1.f) / g->zoom_factor);
1114       g->offset_y = CLAMP(g->offset_y, 0.f, (g->zoom_factor - 1.f) / g->zoom_factor);
1115 
1116       gtk_widget_queue_draw(self->widget);
1117     }
1118     return TRUE;
1119   }
1120 
1121   const int ch = g->channel;
1122   const int nodes = p->curve_num_nodes[ch];
1123   dt_iop_rgbcurve_node_t *curve_nodes = p->curve_nodes[ch];
1124 
1125   // if autoscale is on: do not modify g and b curves
1126   if((p->curve_autoscale != DT_S_SCALE_MANUAL_RGB) && g->channel != DT_IOP_RGBCURVE_R) goto finally;
1127 
1128   GtkAllocation allocation;
1129   gtk_widget_get_allocation(widget, &allocation);
1130   const int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
1131 
1132   const double old_m_x = g->mouse_x;
1133   const double old_m_y = g->mouse_y;
1134 
1135   g->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1136   g->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1137 
1138   const float mx = g->mouse_x;
1139   const float my = g->mouse_y;
1140   const float linx = _mouse_to_curve(mx, g->zoom_factor, g->offset_x),
1141               liny = _mouse_to_curve(my, g->zoom_factor, g->offset_y);
1142 
1143   if(event->state & GDK_BUTTON1_MASK)
1144   {
1145     // got a vertex selected:
1146     if(g->selected >= 0)
1147     {
1148       // this is used to translate mause position in loglogscale to make this behavior unified with linear scale.
1149       const float translate_mouse_x
1150           = old_m_x - _curve_to_mouse(curve_nodes[g->selected].x, g->zoom_factor, g->offset_x);
1151       const float translate_mouse_y
1152           = old_m_y - _curve_to_mouse(curve_nodes[g->selected].y, g->zoom_factor, g->offset_y);
1153       // dx & dy are in linear coordinates
1154       const float dx = _mouse_to_curve(g->mouse_x - translate_mouse_x, g->zoom_factor, g->offset_x)
1155                        - _mouse_to_curve(old_m_x - translate_mouse_x, g->zoom_factor, g->offset_x);
1156       const float dy = _mouse_to_curve(g->mouse_y - translate_mouse_y, g->zoom_factor, g->offset_y)
1157                        - _mouse_to_curve(old_m_y - translate_mouse_y, g->zoom_factor, g->offset_y);
1158 
1159       dt_iop_color_picker_reset(self, TRUE);
1160       return _move_point_internal(self, widget, dx, dy, event->state);
1161     }
1162     else if(nodes < DT_IOP_RGBCURVE_MAXNODES && g->selected >= -1)
1163     {
1164       dt_iop_color_picker_reset(self, TRUE);
1165       // no vertex was close, create a new one!
1166       g->selected = _add_node(curve_nodes, &p->curve_num_nodes[ch], linx, liny);
1167       dt_dev_add_history_item(darktable.develop, self, TRUE);
1168     }
1169   }
1170   else
1171   {
1172     // minimum area around the node to select it:
1173     float min = .04f * .04f; // comparing against square
1174     int nearest = -1;
1175     for(int k = 0; k < nodes; k++)
1176     {
1177       const float dist = (my - _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y))
1178                              * (my - _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y))
1179                          + (mx - _curve_to_mouse(curve_nodes[k].x, g->zoom_factor, g->offset_x))
1180                                * (mx - _curve_to_mouse(curve_nodes[k].x, g->zoom_factor, g->offset_x));
1181       if(dist < min)
1182       {
1183         min = dist;
1184         nearest = k;
1185       }
1186     }
1187     g->selected = nearest;
1188   }
1189 finally:
1190   if(g->selected >= 0) gtk_widget_grab_focus(widget);
1191   gtk_widget_queue_draw(widget);
1192   return TRUE;
1193 }
1194 
_area_button_press_callback(GtkWidget * widget,GdkEventButton * event,dt_iop_module_t * self)1195 static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1196 {
1197   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
1198   dt_iop_rgbcurve_params_t *d = (dt_iop_rgbcurve_params_t *)self->default_params;
1199   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
1200 
1201   if(darktable.develop->darkroom_skip_mouse_events) return TRUE;
1202 
1203   const int ch = g->channel;
1204   const int autoscale = p->curve_autoscale;
1205   const int nodes = p->curve_num_nodes[ch];
1206   dt_iop_rgbcurve_node_t *curve_nodes = p->curve_nodes[ch];
1207 
1208   if(event->button == 1)
1209   {
1210     if(event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, GDK_CONTROL_MASK)
1211        && nodes < DT_IOP_RGBCURVE_MAXNODES && g->selected == -1)
1212     {
1213       // if we are not on a node -> add a new node at the current x of the pointer and y of the curve at that x
1214       const int inset = DT_GUI_CURVE_EDITOR_INSET;
1215       GtkAllocation allocation;
1216       gtk_widget_get_allocation(widget, &allocation);
1217       const int width = allocation.width - 2 * inset;
1218       const int height = allocation.height - 2 * inset;
1219 
1220       g->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1221       g->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1222 
1223       const float mx = g->mouse_x;
1224       const float linx = _mouse_to_curve(mx, g->zoom_factor, g->offset_x);
1225 
1226       // don't add a node too close to others in x direction, it can crash dt
1227       int selected = -1;
1228       if(curve_nodes[0].x > mx)
1229         selected = 0;
1230       else
1231       {
1232         for(int k = 1; k < nodes; k++)
1233         {
1234           if(curve_nodes[k].x > mx)
1235           {
1236             selected = k;
1237             break;
1238           }
1239         }
1240       }
1241       if(selected == -1) selected = nodes;
1242 
1243         // evaluate the curve at the current x position
1244         const float y = dt_draw_curve_calc_value(g->minmax_curve[ch], linx);
1245 
1246         if(y >= 0.0f && y <= 1.0f) // never add something outside the viewport, you couldn't change it afterwards
1247         {
1248           // create a new node
1249           selected = _add_node(curve_nodes, &p->curve_num_nodes[ch], linx, y);
1250 
1251           // maybe set the new one as being selected
1252           const float min = .04f * .04f; // comparing against square
1253           for(int k = 0; k < nodes; k++)
1254           {
1255             const float other_y = _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y);
1256             const float dist = (y - other_y) * (y - other_y);
1257             if(dist < min) g->selected = selected;
1258           }
1259 
1260           dt_iop_color_picker_reset(self, TRUE);
1261           dt_dev_add_history_item(darktable.develop, self, TRUE);
1262           gtk_widget_queue_draw(self->widget);
1263         }
1264 
1265       return TRUE;
1266     }
1267     else if(event->type == GDK_2BUTTON_PRESS)
1268     {
1269       // reset current curve
1270       // if autoscale is on: allow only reset of L curve
1271       if(!((autoscale != DT_S_SCALE_MANUAL_RGB) && ch != DT_IOP_RGBCURVE_R))
1272       {
1273         p->curve_num_nodes[ch] = d->curve_num_nodes[ch];
1274         p->curve_type[ch] = d->curve_type[ch];
1275         for(int k = 0; k < d->curve_num_nodes[ch]; k++)
1276         {
1277           p->curve_nodes[ch][k].x = d->curve_nodes[ch][k].x;
1278           p->curve_nodes[ch][k].y = d->curve_nodes[ch][k].y;
1279         }
1280         g->selected = -2; // avoid motion notify re-inserting immediately.
1281         dt_bauhaus_combobox_set(g->interpolator, p->curve_type[DT_IOP_RGBCURVE_R]);
1282         dt_iop_color_picker_reset(self, TRUE);
1283         dt_dev_add_history_item(darktable.develop, self, TRUE);
1284         gtk_widget_queue_draw(self->widget);
1285       }
1286       else
1287       {
1288         if(ch != DT_IOP_RGBCURVE_R)
1289         {
1290           p->curve_autoscale = DT_S_SCALE_MANUAL_RGB;
1291           g->selected = -2; // avoid motion notify re-inserting immediately.
1292           dt_bauhaus_combobox_set(g->autoscale, 1);
1293           dt_iop_color_picker_reset(self, TRUE);
1294           dt_dev_add_history_item(darktable.develop, self, TRUE);
1295           gtk_widget_queue_draw(self->widget);
1296         }
1297       }
1298       return TRUE;
1299     }
1300   }
1301   else if(event->button == 3 && g->selected >= 0)
1302   {
1303     if(g->selected == 0 || g->selected == nodes - 1)
1304     {
1305       const float reset_value = g->selected == 0 ? 0.f : 1.f;
1306       curve_nodes[g->selected].y = curve_nodes[g->selected].x = reset_value;
1307       dt_iop_color_picker_reset(self, TRUE);
1308       dt_dev_add_history_item(darktable.develop, self, TRUE);
1309       gtk_widget_queue_draw(self->widget);
1310       return TRUE;
1311     }
1312 
1313     for(int k = g->selected; k < nodes - 1; k++)
1314     {
1315       curve_nodes[k].x = curve_nodes[k + 1].x;
1316       curve_nodes[k].y = curve_nodes[k + 1].y;
1317     }
1318     curve_nodes[nodes - 1].x = curve_nodes[nodes - 1].y = 0;
1319     g->selected = -2; // avoid re-insertion of that point immediately after this
1320     p->curve_num_nodes[ch]--;
1321     dt_iop_color_picker_reset(self, TRUE);
1322     dt_dev_add_history_item(darktable.develop, self, TRUE);
1323     gtk_widget_queue_draw(self->widget);
1324     return TRUE;
1325   }
1326   return FALSE;
1327 }
1328 
gui_reset(struct dt_iop_module_t * self)1329 void gui_reset(struct dt_iop_module_t *self)
1330 {
1331   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
1332   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
1333 
1334   g->channel = DT_IOP_RGBCURVE_R;
1335   g->selected = -1;
1336   g->offset_x = g->offset_y = 0.f;
1337   g->zoom_factor = 1.f;
1338 
1339   dt_bauhaus_combobox_set(g->interpolator, p->curve_type[DT_IOP_RGBCURVE_R]);
1340 
1341   gtk_widget_queue_draw(self->widget);
1342 }
1343 
change_image(struct dt_iop_module_t * self)1344 void change_image(struct dt_iop_module_t *self)
1345 {
1346   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
1347   if(g)
1348   {
1349     if(!g->channel)
1350       g->channel = DT_IOP_RGBCURVE_R;
1351     g->mouse_x = g->mouse_y = -1.0;
1352     g->selected = -1;
1353     g->offset_x = g->offset_y = 0.f;
1354     g->zoom_factor = 1.f;
1355   }
1356 }
1357 
gui_init(struct dt_iop_module_t * self)1358 void gui_init(struct dt_iop_module_t *self)
1359 {
1360   dt_iop_rgbcurve_gui_data_t *g = IOP_GUI_ALLOC(rgbcurve);
1361   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->default_params;
1362 
1363   for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1364   {
1365     g->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->curve_type[ch]);
1366     g->minmax_curve_nodes[ch] = p->curve_num_nodes[ch];
1367     g->minmax_curve_type[ch] = p->curve_type[ch];
1368     for(int k = 0; k < p->curve_num_nodes[ch]; k++)
1369       (void)dt_draw_curve_add_point(g->minmax_curve[ch], p->curve_nodes[ch][k].x, p->curve_nodes[ch][k].y);
1370   }
1371 
1372   g->channel = DT_IOP_RGBCURVE_R;
1373   self->timeout_handle = 0;
1374   change_image(self);
1375 
1376   g->autoscale = dt_bauhaus_combobox_from_params(self, "curve_autoscale");
1377   gtk_widget_set_tooltip_text(g->autoscale, _("choose between linked and independent channels."));
1378 
1379   GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1380 
1381   g->channel_tabs = GTK_NOTEBOOK(gtk_notebook_new());
1382   dt_action_define_iop(self, NULL, N_("channel"), GTK_WIDGET(g->channel_tabs), &dt_action_def_tabs_rgb);
1383   dt_ui_notebook_page(g->channel_tabs, N_("R"), _("curve nodes for r channel"));
1384   dt_ui_notebook_page(g->channel_tabs, N_("G"), _("curve nodes for g channel"));
1385   dt_ui_notebook_page(g->channel_tabs, N_("B"), _("curve nodes for b channel"));
1386   g_signal_connect(G_OBJECT(g->channel_tabs), "switch_page", G_CALLBACK(tab_switch_callback), self);
1387   gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(g->channel_tabs), TRUE, TRUE, 0);
1388   gtk_box_pack_start(GTK_BOX(hbox), gtk_grid_new(), TRUE, TRUE, 0);
1389 
1390   // color pickers
1391   g->colorpicker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT_AREA, hbox);
1392   gtk_widget_set_tooltip_text(g->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
1393   gtk_widget_set_name(g->colorpicker, "keep-active");
1394   g->colorpicker_set_values = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, hbox);
1395   dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->colorpicker_set_values),
1396                                dtgtk_cairo_paint_colorpicker_set_values,
1397                                CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, NULL);
1398   gtk_widget_set_size_request(g->colorpicker_set_values, DT_PIXEL_APPLY_DPI(14), DT_PIXEL_APPLY_DPI(14));
1399   gtk_widget_set_tooltip_text(g->colorpicker_set_values, _("create a curve based on an area from the image\n"
1400                                                            "drag to create a flat curve\n"
1401                                                            "ctrl+drag to create a positive curve\n"
1402                                                            "shift+drag to create a negative curve"));
1403 
1404   GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1405   gtk_box_pack_start(GTK_BOX(self->widget), vbox, FALSE, FALSE, 0);
1406   gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0);
1407 
1408   g->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(1.0));
1409   g_object_set_data(G_OBJECT(g->area), "iop-instance", self);
1410   dt_action_define_iop(self, NULL, N_("curve"), GTK_WIDGET(g->area), NULL);
1411   gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(g->area), TRUE, TRUE, 0);
1412 
1413   // FIXME: that tooltip goes in the way of the numbers when you hover a node to get a reading
1414   // gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("double click to reset curve"));
1415 
1416   gtk_widget_add_events(GTK_WIDGET(g->area), GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask
1417                                            | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1418                                            | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1419   gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
1420   g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(_area_draw_callback), self);
1421   g_signal_connect(G_OBJECT(g->area), "button-press-event", G_CALLBACK(_area_button_press_callback), self);
1422   g_signal_connect(G_OBJECT(g->area), "motion-notify-event", G_CALLBACK(_area_motion_notify_callback), self);
1423   g_signal_connect(G_OBJECT(g->area), "leave-notify-event", G_CALLBACK(_area_leave_notify_callback), self);
1424   g_signal_connect(G_OBJECT(g->area), "enter-notify-event", G_CALLBACK(_area_enter_notify_callback), self);
1425   g_signal_connect(G_OBJECT(g->area), "configure-event", G_CALLBACK(_area_resized_callback), self);
1426   g_signal_connect(G_OBJECT(g->area), "scroll-event", G_CALLBACK(_area_scrolled_callback), self);
1427   g_signal_connect(G_OBJECT(g->area), "key-press-event", G_CALLBACK(_area_key_press_callback), self);
1428 
1429   /* From src/common/curve_tools.h :
1430     #define CUBIC_SPLINE 0
1431     #define CATMULL_ROM 1
1432     #define MONOTONE_HERMITE 2
1433   */
1434   g->interpolator = dt_bauhaus_combobox_new(self);
1435   dt_bauhaus_widget_set_label(g->interpolator, NULL, N_("interpolation method"));
1436   dt_bauhaus_combobox_add(g->interpolator, _("cubic spline"));
1437   dt_bauhaus_combobox_add(g->interpolator, _("centripetal spline"));
1438   dt_bauhaus_combobox_add(g->interpolator, _("monotonic spline"));
1439   gtk_box_pack_start(GTK_BOX(self->widget), g->interpolator, TRUE, TRUE, 0);
1440   gtk_widget_set_tooltip_text(g->interpolator,
1441       _("change this method if you see oscillations or cusps in the curve\n"
1442         "- cubic spline is better to produce smooth curves but oscillates when nodes are too close\n"
1443         "- centripetal is better to avoids cusps and oscillations with close nodes but is less smooth\n"
1444         "- monotonic is better for accuracy of pure analytical functions (log, gamma, exp)\n"));
1445   g_signal_connect(G_OBJECT(g->interpolator), "value-changed", G_CALLBACK(interpolator_callback), self);
1446 
1447   g->chk_compensate_middle_grey = dt_bauhaus_toggle_from_params(self, "compensate_middle_grey");
1448   gtk_widget_set_tooltip_text(g->chk_compensate_middle_grey, _("compensate middle gray"));
1449 
1450   g->cmb_preserve_colors = dt_bauhaus_combobox_from_params(self, "preserve_colors");
1451   gtk_widget_set_tooltip_text(g->cmb_preserve_colors, _("method to preserve colors when applying contrast"));
1452 }
1453 
gui_update(struct dt_iop_module_t * self)1454 void gui_update(struct dt_iop_module_t *self)
1455 {
1456   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
1457   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)self->params;
1458 
1459   dt_bauhaus_combobox_set(g->autoscale, p->curve_autoscale);
1460   dt_bauhaus_combobox_set(g->interpolator, p->curve_type[DT_IOP_RGBCURVE_R]);
1461   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->chk_compensate_middle_grey), p->compensate_middle_grey);
1462   dt_bauhaus_combobox_set(g->cmb_preserve_colors, p->preserve_colors);
1463 
1464   dt_iop_cancel_history_update(self);
1465 
1466   _rgbcurve_show_hide_controls(p, g);
1467 
1468   // that's all, gui curve is read directly from params during expose event.
1469   gtk_widget_queue_draw(self->widget);
1470 }
1471 
gui_cleanup(struct dt_iop_module_t * self)1472 void gui_cleanup(struct dt_iop_module_t *self)
1473 {
1474   dt_iop_rgbcurve_gui_data_t *g = (dt_iop_rgbcurve_gui_data_t *)self->gui_data;
1475 
1476   for(int k = 0; k < DT_IOP_RGBCURVE_MAX_CHANNELS; k++) dt_draw_curve_destroy(g->minmax_curve[k]);
1477 
1478   dt_iop_cancel_history_update(self);
1479 
1480   IOP_GUI_FREE;
1481 }
1482 
init_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1483 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
1484 {
1485   // create part of the pixelpipe
1486   dt_iop_rgbcurve_data_t *d = (dt_iop_rgbcurve_data_t *)malloc(sizeof(dt_iop_rgbcurve_data_t));
1487   dt_iop_rgbcurve_params_t *default_params = (dt_iop_rgbcurve_params_t *)self->default_params;
1488   piece->data = (void *)d;
1489   memcpy(&d->params, default_params, sizeof(dt_iop_rgbcurve_params_t));
1490 
1491   for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1492   {
1493     d->curve[ch] = dt_draw_curve_new(0.0, 1.0, default_params->curve_type[ch]);
1494     d->params.curve_num_nodes[ch] = default_params->curve_num_nodes[ch];
1495     d->params.curve_type[ch] = default_params->curve_type[ch];
1496     for(int k = 0; k < default_params->curve_num_nodes[ch]; k++)
1497       (void)dt_draw_curve_add_point(d->curve[ch], default_params->curve_nodes[ch][k].x,
1498                                     default_params->curve_nodes[ch][k].y);
1499   }
1500 
1501   for(int k = 0; k < 0x10000; k++) d->table[DT_IOP_RGBCURVE_R][k] = k / 0x10000; // identity for r
1502   for(int k = 0; k < 0x10000; k++) d->table[DT_IOP_RGBCURVE_G][k] = k / 0x10000; // identity for g
1503   for(int k = 0; k < 0x10000; k++) d->table[DT_IOP_RGBCURVE_B][k] = k / 0x10000; // identity for b
1504 }
1505 
cleanup_pipe(struct dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1506 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
1507 {
1508   // clean up everything again.
1509   dt_iop_rgbcurve_data_t *d = (dt_iop_rgbcurve_data_t *)(piece->data);
1510   for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++) dt_draw_curve_destroy(d->curve[ch]);
1511   free(piece->data);
1512   piece->data = NULL;
1513 }
1514 
init(dt_iop_module_t * module)1515 void init(dt_iop_module_t *module)
1516 {
1517   dt_iop_default_init(module);
1518 
1519   module->request_histogram |= (DT_REQUEST_ON);
1520 
1521   dt_iop_rgbcurve_params_t *d = module->default_params;
1522 
1523   d->curve_nodes[0][1].x = d->curve_nodes[0][1].y =
1524   d->curve_nodes[1][1].x = d->curve_nodes[1][1].y =
1525   d->curve_nodes[2][1].x = d->curve_nodes[2][1].y = 1.0;
1526 
1527   module->histogram_middle_grey = d->compensate_middle_grey;
1528 }
1529 
init_global(dt_iop_module_so_t * module)1530 void init_global(dt_iop_module_so_t *module)
1531 {
1532   const int program = 25; // rgbcurve.cl, from programs.conf
1533   dt_iop_rgbcurve_global_data_t *gd
1534       = (dt_iop_rgbcurve_global_data_t *)malloc(sizeof(dt_iop_rgbcurve_global_data_t));
1535   module->data = gd;
1536 
1537   gd->kernel_rgbcurve = dt_opencl_create_kernel(program, "rgbcurve");
1538 }
1539 
cleanup_global(dt_iop_module_so_t * module)1540 void cleanup_global(dt_iop_module_so_t *module)
1541 {
1542   dt_iop_rgbcurve_global_data_t *gd = (dt_iop_rgbcurve_global_data_t *)module->data;
1543   dt_opencl_free_kernel(gd->kernel_rgbcurve);
1544   free(module->data);
1545   module->data = NULL;
1546 }
1547 
1548 // this will be called from process*()
1549 // it must be executed only if profile info has changed
_generate_curve_lut(dt_dev_pixelpipe_t * pipe,dt_iop_rgbcurve_data_t * d)1550 static void _generate_curve_lut(dt_dev_pixelpipe_t *pipe, dt_iop_rgbcurve_data_t *d)
1551 {
1552   const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(pipe);
1553 
1554   dt_iop_rgbcurve_node_t curve_nodes[3][DT_IOP_RGBCURVE_MAXNODES];
1555 
1556   if(work_profile)
1557   {
1558     if(d->type_work == work_profile->type && strcmp(d->filename_work, work_profile->filename) == 0) return;
1559   }
1560 
1561   if(work_profile && d->params.compensate_middle_grey)
1562   {
1563     d->type_work = work_profile->type;
1564     g_strlcpy(d->filename_work, work_profile->filename, sizeof(d->filename_work));
1565 
1566     for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1567     {
1568       for(int k = 0; k < d->params.curve_num_nodes[ch]; k++)
1569       {
1570         curve_nodes[ch][k].x = dt_ioppr_uncompensate_middle_grey(d->params.curve_nodes[ch][k].x, work_profile);
1571         curve_nodes[ch][k].y = dt_ioppr_uncompensate_middle_grey(d->params.curve_nodes[ch][k].y, work_profile);
1572       }
1573     }
1574   }
1575   else
1576   {
1577     for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1578     {
1579       memcpy(curve_nodes[ch], d->params.curve_nodes[ch], sizeof(dt_iop_rgbcurve_node_t) * DT_IOP_RGBCURVE_MAXNODES);
1580     }
1581   }
1582 
1583   for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1584   {
1585     // take care of possible change of curve type or number of nodes (not yet implemented in UI)
1586     if(d->curve_changed[ch])
1587     {
1588       dt_draw_curve_destroy(d->curve[ch]);
1589       d->curve[ch] = dt_draw_curve_new(0.0, 1.0, d->params.curve_type[ch]);
1590       for(int k = 0; k < d->params.curve_num_nodes[ch]; k++)
1591         (void)dt_draw_curve_add_point(d->curve[ch], curve_nodes[ch][k].x, curve_nodes[ch][k].y);
1592     }
1593     else
1594     {
1595       for(int k = 0; k < d->params.curve_num_nodes[ch]; k++)
1596         dt_draw_curve_set_point(d->curve[ch], k, curve_nodes[ch][k].x, curve_nodes[ch][k].y);
1597     }
1598 
1599     dt_draw_curve_calc_values(d->curve[ch], 0.0f, 1.0f, 0x10000, NULL, d->table[ch]);
1600   }
1601 
1602   // extrapolation for each curve (right hand side only):
1603   for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1604   {
1605     const float xm_L = curve_nodes[ch][d->params.curve_num_nodes[ch] - 1].x;
1606     const float x_L[4] = { 0.7f * xm_L, 0.8f * xm_L, 0.9f * xm_L, 1.0f * xm_L };
1607     const float y_L[4] = { d->table[ch][CLAMP((int)(x_L[0] * 0x10000ul), 0, 0xffff)],
1608                            d->table[ch][CLAMP((int)(x_L[1] * 0x10000ul), 0, 0xffff)],
1609                            d->table[ch][CLAMP((int)(x_L[2] * 0x10000ul), 0, 0xffff)],
1610                            d->table[ch][CLAMP((int)(x_L[3] * 0x10000ul), 0, 0xffff)] };
1611     dt_iop_estimate_exp(x_L, y_L, 4, d->unbounded_coeffs[ch]);
1612   }
1613 }
1614 
commit_params(struct dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)1615 void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
1616                    dt_dev_pixelpipe_iop_t *piece)
1617 {
1618   dt_iop_rgbcurve_data_t *d = (dt_iop_rgbcurve_data_t *)(piece->data);
1619   dt_iop_rgbcurve_params_t *p = (dt_iop_rgbcurve_params_t *)p1;
1620 
1621   if((pipe->type & DT_DEV_PIXELPIPE_PREVIEW) == DT_DEV_PIXELPIPE_PREVIEW)
1622     piece->request_histogram |= (DT_REQUEST_ON);
1623   else
1624     piece->request_histogram &= ~(DT_REQUEST_ON);
1625 
1626   for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1627     d->curve_changed[ch]
1628         = (d->params.curve_type[ch] != p->curve_type[ch] || d->params.curve_nodes[ch] != p->curve_nodes[ch]);
1629 
1630   memcpy(&d->params, p, sizeof(dt_iop_rgbcurve_params_t));
1631 
1632   // working color profile
1633   d->type_work = DT_COLORSPACE_NONE;
1634   d->filename_work[0] = '\0';
1635 }
1636 
1637 #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)1638 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
1639                const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
1640 {
1641   const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(piece->pipe);
1642 
1643   dt_iop_rgbcurve_data_t *d = (dt_iop_rgbcurve_data_t *)piece->data;
1644   dt_iop_rgbcurve_global_data_t *gd = (dt_iop_rgbcurve_global_data_t *)self->global_data;
1645 
1646   _generate_curve_lut(piece->pipe, d);
1647 
1648   cl_int err = CL_SUCCESS;
1649 
1650   cl_mem dev_r = NULL;
1651   cl_mem dev_g = NULL;
1652   cl_mem dev_b = NULL;
1653   cl_mem dev_coeffs_r = NULL;
1654   cl_mem dev_coeffs_g = NULL;
1655   cl_mem dev_coeffs_b = NULL;
1656 
1657   cl_mem dev_profile_info = NULL;
1658   cl_mem dev_profile_lut = NULL;
1659   dt_colorspaces_iccprofile_info_cl_t *profile_info_cl;
1660   cl_float *profile_lut_cl = NULL;
1661 
1662   const int use_work_profile = (work_profile == NULL) ? 0 : 1;
1663 
1664   const int devid = piece->pipe->devid;
1665   const int width = roi_in->width;
1666   const int height = roi_in->height;
1667   const int autoscale = d->params.curve_autoscale;
1668   const int preserve_colors = d->params.preserve_colors;
1669 
1670   err = dt_ioppr_build_iccprofile_params_cl(work_profile, devid, &profile_info_cl, &profile_lut_cl,
1671                                             &dev_profile_info, &dev_profile_lut);
1672   if(err != CL_SUCCESS) goto cleanup;
1673 
1674   dev_r = dt_opencl_copy_host_to_device(devid, d->table[DT_IOP_RGBCURVE_R], 256, 256, sizeof(float));
1675   if(dev_r == NULL)
1676   {
1677     fprintf(stderr, "[rgbcurve process_cl] error allocating memory 1\n");
1678     err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1679     goto cleanup;
1680   }
1681 
1682   dev_g = dt_opencl_copy_host_to_device(devid, d->table[DT_IOP_RGBCURVE_G], 256, 256, sizeof(float));
1683   if(dev_g == NULL)
1684   {
1685     fprintf(stderr, "[rgbcurve process_cl] error allocating memory 2\n");
1686     err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1687     goto cleanup;
1688   }
1689 
1690   dev_b = dt_opencl_copy_host_to_device(devid, d->table[DT_IOP_RGBCURVE_B], 256, 256, sizeof(float));
1691   if(dev_b == NULL)
1692   {
1693     fprintf(stderr, "[rgbcurve process_cl] error allocating memory 3\n");
1694     err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1695     goto cleanup;
1696   }
1697 
1698   dev_coeffs_r = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 3, d->unbounded_coeffs[0]);
1699   if(dev_coeffs_r == NULL)
1700   {
1701     fprintf(stderr, "[rgbcurve process_cl] error allocating memory 4\n");
1702     err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1703     goto cleanup;
1704   }
1705 
1706   dev_coeffs_g = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 3, d->unbounded_coeffs[1]);
1707   if(dev_coeffs_g == NULL)
1708   {
1709     fprintf(stderr, "[rgbcurve process_cl] error allocating memory 5\n");
1710     err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1711     goto cleanup;
1712   }
1713 
1714   dev_coeffs_b = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * 12, d->unbounded_coeffs[2]);
1715   if(dev_coeffs_b == NULL)
1716   {
1717     fprintf(stderr, "[rgbcurve process_cl] error allocating memory 6\n");
1718     err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1719     goto cleanup;
1720   }
1721 
1722   size_t sizes[] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
1723   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 0, sizeof(cl_mem), (void *)&dev_in);
1724   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 1, sizeof(cl_mem), (void *)&dev_out);
1725   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 2, sizeof(int), (void *)&width);
1726   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 3, sizeof(int), (void *)&height);
1727   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 4, sizeof(cl_mem), (void *)&dev_r);
1728   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 5, sizeof(cl_mem), (void *)&dev_g);
1729   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 6, sizeof(cl_mem), (void *)&dev_b);
1730   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 7, sizeof(cl_mem), (void *)&dev_coeffs_r);
1731   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 8, sizeof(cl_mem), (void *)&dev_coeffs_g);
1732   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 9, sizeof(cl_mem), (void *)&dev_coeffs_b);
1733   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 10, sizeof(int), (void *)&autoscale);
1734   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 11, sizeof(int), (void *)&preserve_colors);
1735   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 12, sizeof(cl_mem), (void *)&dev_profile_info);
1736   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 13, sizeof(cl_mem), (void *)&dev_profile_lut);
1737   dt_opencl_set_kernel_arg(devid, gd->kernel_rgbcurve, 14, sizeof(int), (void *)&use_work_profile);
1738   err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_rgbcurve, sizes);
1739   if(err != CL_SUCCESS)
1740   {
1741     fprintf(stderr, "[rgbcurve process_cl] error %i enqueue kernel\n", err);
1742     goto cleanup;
1743   }
1744 
1745 cleanup:
1746   if(dev_r) dt_opencl_release_mem_object(dev_r);
1747   if(dev_g) dt_opencl_release_mem_object(dev_g);
1748   if(dev_b) dt_opencl_release_mem_object(dev_b);
1749   if(dev_coeffs_r) dt_opencl_release_mem_object(dev_coeffs_r);
1750   if(dev_coeffs_g) dt_opencl_release_mem_object(dev_coeffs_g);
1751   if(dev_coeffs_b) dt_opencl_release_mem_object(dev_coeffs_b);
1752   dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
1753 
1754   if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_rgbcurve] couldn't enqueue kernel! %d\n", err);
1755 
1756   return (err == CL_SUCCESS) ? TRUE : FALSE;
1757 }
1758 #endif
1759 
process(struct dt_iop_module_t * self,dt_dev_pixelpipe_iop_t * piece,const void * const ivoid,void * const ovoid,const dt_iop_roi_t * const roi_in,const dt_iop_roi_t * const roi_out)1760 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
1761              void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
1762 {
1763   const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(piece->pipe);
1764 
1765   const float *const restrict in = (float*)ivoid;
1766   float *const restrict out = (float*)ovoid;
1767   if (!dt_iop_have_required_input_format(4 /*we need full-color pixels*/, self, piece->colors,
1768                                          in, out, roi_in, roi_out))
1769     return; // image has been copied through to output and module's trouble flag has been updated
1770 
1771   dt_iop_rgbcurve_data_t *const restrict d = (dt_iop_rgbcurve_data_t *)(piece->data);
1772 
1773   _generate_curve_lut(piece->pipe, d);
1774 
1775   const float xm_L = 1.0f / d->unbounded_coeffs[DT_IOP_RGBCURVE_R][0];
1776   const float xm_g = 1.0f / d->unbounded_coeffs[DT_IOP_RGBCURVE_G][0];
1777   const float xm_b = 1.0f / d->unbounded_coeffs[DT_IOP_RGBCURVE_B][0];
1778 
1779   const int width = roi_out->width;
1780   const int height = roi_out->height;
1781   const size_t npixels = (size_t)width * height;
1782   const int autoscale = d->params.curve_autoscale;
1783   const _curve_table_ptr restrict table = d->table;
1784   const _coeffs_table_ptr restrict unbounded_coeffs = d->unbounded_coeffs;
1785 
1786 #ifdef _OPENMP
1787 #pragma omp parallel for default(none) \
1788   dt_omp_firstprivate(autoscale, npixels, work_profile, xm_b, xm_g, xm_L) \
1789   dt_omp_sharedconst(in, out, table, unbounded_coeffs, d) \
1790   schedule(static)
1791 #endif
1792   for(int y = 0; y < 4*npixels; y += 4)
1793   {
1794     if(autoscale == DT_S_SCALE_MANUAL_RGB)
1795     {
1796       out[y+0] = (in[y+0] < xm_L) ? table[DT_IOP_RGBCURVE_R][CLAMP((int)(in[y+0] * 0x10000ul), 0, 0xffff)]
1797                                   : dt_iop_eval_exp(unbounded_coeffs[DT_IOP_RGBCURVE_R], in[y+0]);
1798       out[y+1] = (in[y+1] < xm_g) ? table[DT_IOP_RGBCURVE_G][CLAMP((int)(in[y+1] * 0x10000ul), 0, 0xffff)]
1799                                   : dt_iop_eval_exp(unbounded_coeffs[DT_IOP_RGBCURVE_G], in[y+1]);
1800       out[y+2] = (in[y+2] < xm_b) ? table[DT_IOP_RGBCURVE_B][CLAMP((int)(in[y+2] * 0x10000ul), 0, 0xffff)]
1801                                   : dt_iop_eval_exp(unbounded_coeffs[DT_IOP_RGBCURVE_B], in[y+2]);
1802     }
1803     else if(autoscale == DT_S_SCALE_AUTOMATIC_RGB)
1804     {
1805       if(d->params.preserve_colors == DT_RGB_NORM_NONE)
1806       {
1807         for(int c = 0; c < 3; c++)
1808         {
1809           out[y+c] = (in[y+c] < xm_L) ? table[DT_IOP_RGBCURVE_R][CLAMP((int)(in[y+c] * 0x10000ul), 0, 0xffff)]
1810             : dt_iop_eval_exp(unbounded_coeffs[DT_IOP_RGBCURVE_R], in[y+c]);
1811         }
1812       }
1813       else
1814       {
1815         float ratio = 1.f;
1816         const float lum = dt_rgb_norm(in + y, d->params.preserve_colors, work_profile);
1817         if(lum > 0.f)
1818         {
1819           const float curve_lum = (lum < xm_L)
1820             ? table[DT_IOP_RGBCURVE_R][CLAMP((int)(lum * 0x10000ul), 0, 0xffff)]
1821             : dt_iop_eval_exp(unbounded_coeffs[DT_IOP_RGBCURVE_R], lum);
1822           ratio = curve_lum / lum;
1823         }
1824         for(size_t c = 0; c < 3; c++)
1825         {
1826           out[y+c] = (ratio * in[y+c]);
1827         }
1828       }
1829     }
1830     out[y+3] = in[y+3];
1831   }
1832 }
1833 
1834 #undef DT_GUI_CURVE_EDITOR_INSET
1835 #undef DT_IOP_RGBCURVE_RES
1836 #undef DT_IOP_RGBCURVE_MAXNODES
1837 #undef DT_IOP_RGBCURVE_MIN_X_DISTANCE
1838 #undef DT_IOP_COLOR_ICC_LEN
1839 
1840 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1841 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1842 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1843