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