Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
rgbcurve.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2019 Andreas Schneider.
4 Copyright (C) 2019, 2022-2026 Aurélien PIERRE.
5 Copyright (C) 2019 Bill Ferguson.
6 Copyright (C) 2019-2022 Diederik Ter Rahe.
7 Copyright (C) 2019 Edgardo Hoszowski.
8 Copyright (C) 2019 emeikei.
9 Copyright (C) 2019 Jacques Le Clerc.
10 Copyright (C) 2019 parafin.
11 Copyright (C) 2019-2022 Pascal Obry.
12 Copyright (C) 2019 Philippe Weyland.
13 Copyright (C) 2019 Tobias Ellinghaus.
14 Copyright (C) 2019 Ulrich Pegelow.
15 Copyright (C) 2020, 2022 Aldric Renaudin.
16 Copyright (C) 2020-2021 Chris Elston.
17 Copyright (C) 2020-2021 Dan Torop.
18 Copyright (C) 2020 Hubert Kowalski.
19 Copyright (C) 2020 Marco.
20 Copyright (C) 2020-2021 Ralf Brown.
21 Copyright (C) 2021 lhietal.
22 Copyright (C) 2021 luzpaz.
23 Copyright (C) 2021 Marco Carrarini.
24 Copyright (C) 2022 Hanno Schwalm.
25 Copyright (C) 2022 Martin Bařinka.
26 Copyright (C) 2022 Philipp Lutz.
27 Copyright (C) 2023 Luca Zulberti.
28
29 Ansel is free software: you can redistribute it and/or modify
30 it under the terms of the GNU General Public License as published by
31 the Free Software Foundation, either version 3 of the License, or
32 (at your option) any later version.
33
34 Ansel is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 GNU General Public License for more details.
38
39 You should have received a copy of the GNU General Public License
40 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
41*/
42#ifdef HAVE_CONFIG_H
43#include "config.h"
44#endif
45
46#include "bauhaus/bauhaus.h"
47#include "common/iop_profile.h"
49#include "common/rgb_norms.h"
50#include "develop/imageop.h"
52#include "develop/imageop_gui.h"
53#include "dtgtk/drawingarea.h"
55#include "gui/gtk.h"
56#include "gui/gdkkeys.h"
57#include "gui/presets.h"
58#include "gui/draw.h"
59#include "control/control.h"
60
61#include "libs/colorpicker.h"
62
63#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(1)
64#define DT_IOP_RGBCURVE_RES 256
65#define DT_IOP_RGBCURVE_MAXNODES 20
66#define DT_IOP_RGBCURVE_MIN_X_DISTANCE 0.0025f
67// max iccprofile file name length
68// must be in synch with filename in dt_colorspaces_color_profile_t in colorspaces.h
69#define DT_IOP_COLOR_ICC_LEN 512
70
72
80
82{
83 DT_S_SCALE_AUTOMATIC_RGB = 0, // $DESCRIPTION: "RGB, linked channels"
84 DT_S_SCALE_MANUAL_RGB = 1 // $DESCRIPTION: "RGB, independent channels"
86
88{
89 float x; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0
90 float y; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0
92
94{
96 [DT_IOP_RGBCURVE_MAXNODES]; // actual nodes for each curve
97 int curve_num_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS]; // $DEFAULT: 2 number of nodes per curve
98 int curve_type[DT_IOP_RGBCURVE_MAX_CHANNELS]; // $DEFAULT: MONOTONE_HERMITE (CATMULL_ROM, MONOTONE_HERMITE, CUBIC_SPLINE)
99 dt_iop_rgbcurve_autoscale_t curve_autoscale; // $DEFAULT: DT_S_SCALE_AUTOMATIC_RGB $DESCRIPTION: "mode"
100 gboolean compensate_middle_grey; // $DEFAULT: 0 $DESCRIPTION: "compensate middle gray" scale the curve and histogram so middle gray is at .5
101 dt_iop_rgb_norms_t preserve_colors; // $DEFAULT: DT_RGB_NORM_LUMINANCE $DESCRIPTION: "preserve colors"
103
127
129{
131 dt_draw_curve_t *curve[DT_IOP_RGBCURVE_MAX_CHANNELS]; // curves for pipe piece and pixel processing
132 float table[DT_IOP_RGBCURVE_MAX_CHANNELS][0x10000]; // precomputed look-up tables for tone curve
133 float unbounded_coeffs[DT_IOP_RGBCURVE_MAX_CHANNELS][3]; // approximation for extrapolation
134 int curve_changed[DT_IOP_RGBCURVE_MAX_CHANNELS]; // curve type or number of nodes changed?
138
139typedef float (*_curve_table_ptr)[0x10000];
140typedef float (*_coeffs_table_ptr)[3];
141
142const char *name()
143{
144 return _("curve");
145}
146
148{
149 return IOP_GROUP_COLOR;
150}
151
156
158{
159 return IOP_CS_RGB;
160}
161
162const char **description(struct dt_iop_module_t *self)
163{
164 return dt_iop_set_description(self, _("alter an image's tones using curves in RGB color space"),
165 _("corrective and creative"),
166 _("linear, RGB, display-referred"),
167 _("non-linear, RGB"),
168 _("linear, RGB, display-referred"));
169}
170
172{
174 memset(&p, 0, sizeof(p));
175 p.curve_num_nodes[DT_IOP_RGBCURVE_R] = 6;
176 p.curve_num_nodes[DT_IOP_RGBCURVE_G] = 7;
177 p.curve_num_nodes[DT_IOP_RGBCURVE_B] = 7;
178 p.curve_type[DT_IOP_RGBCURVE_R] = CUBIC_SPLINE;
179 p.curve_type[DT_IOP_RGBCURVE_G] = CUBIC_SPLINE;
180 p.curve_type[DT_IOP_RGBCURVE_B] = CUBIC_SPLINE;
181 p.curve_autoscale = DT_S_SCALE_AUTOMATIC_RGB;
182 p.compensate_middle_grey = 1;
183 p.preserve_colors = 1;
184
185 float linear_ab[7] = { 0.0, 0.08, 0.3, 0.5, 0.7, 0.92, 1.0 };
186
187 // linear a, b curves for presets
188 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_G][k].x = linear_ab[k];
189 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_G][k].y = linear_ab[k];
190 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_B][k].x = linear_ab[k];
191 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_B][k].y = linear_ab[k];
192
193 // More useful low contrast curve (based on Samsung NX -2 Contrast)
194 p.curve_nodes[DT_IOP_RGBCURVE_R][0].x = 0.000000;
195 p.curve_nodes[DT_IOP_RGBCURVE_R][1].x = 0.003862;
196 p.curve_nodes[DT_IOP_RGBCURVE_R][2].x = 0.076613;
197 p.curve_nodes[DT_IOP_RGBCURVE_R][3].x = 0.169355;
198 p.curve_nodes[DT_IOP_RGBCURVE_R][4].x = 0.774194;
199 p.curve_nodes[DT_IOP_RGBCURVE_R][5].x = 1.000000;
200 p.curve_nodes[DT_IOP_RGBCURVE_R][0].y = 0.000000;
201 p.curve_nodes[DT_IOP_RGBCURVE_R][1].y = 0.007782;
202 p.curve_nodes[DT_IOP_RGBCURVE_R][2].y = 0.156182;
203 p.curve_nodes[DT_IOP_RGBCURVE_R][3].y = 0.290352;
204 p.curve_nodes[DT_IOP_RGBCURVE_R][4].y = 0.773852;
205 p.curve_nodes[DT_IOP_RGBCURVE_R][5].y = 1.000000;
206 dt_gui_presets_add_generic(_("contrast compression"), self->op,
207 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
208
209 p.curve_num_nodes[DT_IOP_RGBCURVE_R] = 7;
210 float linear_L[7] = { 0.0, 0.08, 0.17, 0.50, 0.83, 0.92, 1.0 };
211
212 // Linear - no contrast
213 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
214 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
215 dt_gui_presets_add_generic(_("gamma 1.0 (linear)"), self->op,
216 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
217
218 // Linear contrast
219 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
220 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
221 p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.020;
222 p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.030;
223 p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.030;
224 p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.020;
225 dt_gui_presets_add_generic(_("contrast - med (linear)"), 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 dt_gui_presets_add_generic(_("contrast - high (linear)"), self->op,
235 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
236
237 // Gamma contrast
238 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
239 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
240 p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.020;
241 p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.030;
242 p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.030;
243 p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.020;
244 for(int k = 1; k < 6; k++)
245 p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].x, 2.2f);
246 for(int k = 1; k < 6; k++)
247 p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].y, 2.2f);
248 dt_gui_presets_add_generic(_("contrast - med (gamma 2.2)"), self->op,
249 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
250
251 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
252 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
253 p.curve_nodes[DT_IOP_RGBCURVE_R][1].y -= 0.040;
254 p.curve_nodes[DT_IOP_RGBCURVE_R][2].y -= 0.060;
255 p.curve_nodes[DT_IOP_RGBCURVE_R][4].y += 0.060;
256 p.curve_nodes[DT_IOP_RGBCURVE_R][5].y += 0.040;
257 for(int k = 1; k < 6; k++)
258 p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].x, 2.2f);
259 for(int k = 1; k < 6; k++)
260 p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(p.curve_nodes[DT_IOP_RGBCURVE_R][k].y, 2.2f);
261 dt_gui_presets_add_generic(_("contrast - high (gamma 2.2)"), self->op,
262 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
263
267
268 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].x = linear_L[k];
269 for(int k = 0; k < 7; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = linear_L[k];
270
271 // Gamma 2.0 - no contrast
272 for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(linear_L[k], 2.0f);
273 dt_gui_presets_add_generic(_("gamma 2.0"), self->op,
274 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
275
276 // Gamma 0.5 - no contrast
277 for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(linear_L[k], 0.5f);
278 dt_gui_presets_add_generic(_("gamma 0.5"), self->op,
279 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
280
281 // Log2 - no contrast
282 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);
283 dt_gui_presets_add_generic(_("logarithm (base 2)"), self->op,
284 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
285
286 // Exp2 - no contrast
287 for(int k = 1; k < 6; k++) p.curve_nodes[DT_IOP_RGBCURVE_R][k].y = powf(2.0f, linear_L[k]) - 1.0f;
288 dt_gui_presets_add_generic(_("exponential (base 2)"), self->op,
289 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
290}
291
292static float _curve_to_mouse(const float x, const float zoom_factor, const float offset)
293{
294 return (x - offset) * zoom_factor;
295}
296
297static float _mouse_to_curve(const float x, const float zoom_factor, const float offset)
298{
299 return (x / zoom_factor) + offset;
300}
301
302static void picker_scale(const float *const in, float *out, dt_iop_rgbcurve_params_t *p,
303 const dt_iop_order_iccprofile_info_t *const work_profile)
304{
305 switch(p->curve_autoscale)
306 {
308 if(p->compensate_middle_grey && !IS_NULL_PTR(work_profile))
309 {
310 for(int c = 0; c < 3; c++) out[c] = dt_ioppr_compensate_middle_grey(in[c], work_profile);
311 }
312 else
313 {
314 for(int c = 0; c < 3; c++) out[c] = in[c];
315 }
316 break;
318 {
319 const float val
320 = (work_profile) ? dt_ioppr_get_rgb_matrix_luminance(in,
321 work_profile->matrix_in,
322 work_profile->lut_in,
323 work_profile->unbounded_coeffs_in,
324 work_profile->lutsize,
325 work_profile->nonlinearlut)
327 if(p->compensate_middle_grey && !IS_NULL_PTR(work_profile))
328 {
329 out[0] = dt_ioppr_compensate_middle_grey(val, work_profile);
330 }
331 else
332 {
333 out[0] = val;
334 }
335 out[1] = out[2] = 0.f;
336 }
337 break;
338 }
339
340 for(int c = 0; c < 3; c++) out[c] = CLAMP(out[c], 0.0f, 1.0f);
341}
342
344{
345 gtk_notebook_set_show_tabs(g->channel_tabs, p->curve_autoscale == DT_S_SCALE_MANUAL_RGB);
346
347 gtk_widget_set_visible(g->cmb_preserve_colors, p->curve_autoscale == DT_S_SCALE_AUTOMATIC_RGB);
348}
349
351{
352 for(int k=0; k<p->curve_num_nodes[channel]; k++)
353 if(p->curve_nodes[channel][k].x != p->curve_nodes[channel][k].y) return FALSE;
354
355 return TRUE;
356}
357
358void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
359{
362
363 if(w == g->autoscale)
364 {
365 g->channel = DT_IOP_RGBCURVE_R;
366 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->channel_tabs), DT_IOP_RGBCURVE_R);
367
369
370 // switching to manual scale, if G and B not touched yet, just make them identical to global setting (R)
371 if(p->curve_autoscale == DT_S_SCALE_MANUAL_RGB
374 {
375 for(int k=0; k<DT_IOP_RGBCURVE_MAXNODES; k++)
376 p->curve_nodes[DT_IOP_RGBCURVE_G][k]
377 = p->curve_nodes[DT_IOP_RGBCURVE_B][k] = p->curve_nodes[DT_IOP_RGBCURVE_R][k];
378
379 p->curve_num_nodes[DT_IOP_RGBCURVE_G] = p->curve_num_nodes[DT_IOP_RGBCURVE_B]
380 = p->curve_num_nodes[DT_IOP_RGBCURVE_R];
381 p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
382 = p->curve_type[DT_IOP_RGBCURVE_R];
383 }
384 }
385 else if(w == g->chk_compensate_middle_grey)
386 {
387 const dt_iop_order_iccprofile_info_t *const work_profile
389 if(IS_NULL_PTR(work_profile)) return;
390
391 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
392 {
393 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
394 {
395 if(p->compensate_middle_grey)
396 {
397 // we transform the curve nodes from the image colorspace to lab
398 p->curve_nodes[ch][k].x = dt_ioppr_compensate_middle_grey(p->curve_nodes[ch][k].x, work_profile);
399 p->curve_nodes[ch][k].y = dt_ioppr_compensate_middle_grey(p->curve_nodes[ch][k].y, work_profile);
400 }
401 else
402 {
403 // we transform the curve nodes from lab to the image colorspace
404 p->curve_nodes[ch][k].x = dt_ioppr_uncompensate_middle_grey(p->curve_nodes[ch][k].x, work_profile);
405 p->curve_nodes[ch][k].y = dt_ioppr_uncompensate_middle_grey(p->curve_nodes[ch][k].y, work_profile);
406 }
407 }
408 }
409
410 self->histogram_middle_grey = p->compensate_middle_grey;
411 }
412}
413
415{
416 if(darktable.gui->reset) return;
419
420 const int combo = dt_bauhaus_combobox_get(widget);
421
422 if(combo == 0)
423 p->curve_type[DT_IOP_RGBCURVE_R] = p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
424 = CUBIC_SPLINE;
425 else if(combo == 1)
426 p->curve_type[DT_IOP_RGBCURVE_R] = p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
427 = CATMULL_ROM;
428 else if(combo == 2)
429 p->curve_type[DT_IOP_RGBCURVE_R] = p->curve_type[DT_IOP_RGBCURVE_G] = p->curve_type[DT_IOP_RGBCURVE_B]
431
433 gtk_widget_queue_draw(GTK_WIDGET(g->area));
434}
435
436static void tab_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
437{
438 if(darktable.gui->reset) return;
439 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
441
442 g->channel = (rgbcurve_channel_t)page_num;
443
444 gtk_widget_queue_draw(self->widget);
445}
446
447static gboolean _area_resized_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
448{
449 GtkRequisition r;
450 GtkAllocation allocation;
451 gtk_widget_get_allocation(widget, &allocation);
452 r.width = allocation.width;
453 r.height = allocation.width;
454 gtk_widget_get_preferred_size(widget, &r, NULL);
455 return TRUE;
456}
457
458static inline int _add_node(dt_iop_rgbcurve_node_t *curve_nodes, int *nodes, float x, float y)
459{
460 int selected = -1;
461 if(curve_nodes[0].x > x)
462 selected = 0;
463 else
464 {
465 for(int k = 1; k < *nodes; k++)
466 {
467 if(curve_nodes[k].x > x)
468 {
469 selected = k;
470 break;
471 }
472 }
473 }
474 if(selected == -1) selected = *nodes;
475 for(int i = *nodes; i > selected; i--)
476 {
477 curve_nodes[i].x = curve_nodes[i - 1].x;
478 curve_nodes[i].y = curve_nodes[i - 1].y;
479 }
480 // found a new point
481 curve_nodes[selected].x = x;
482 curve_nodes[selected].y = y;
483 (*nodes)++;
484 return selected;
485}
486
487static inline int _add_node_from_picker(dt_iop_rgbcurve_params_t *p, const float *const in, const float increment,
488 const int ch, const dt_iop_order_iccprofile_info_t *const work_profile)
489{
490 float x = 0.f;
491 float y = 0.f;
492 float val = 0.f;
493
494 if(p->curve_autoscale == DT_S_SCALE_AUTOMATIC_RGB)
495 val = (work_profile) ? dt_ioppr_get_rgb_matrix_luminance(in,
496 work_profile->matrix_in,
497 work_profile->lut_in,
498 work_profile->unbounded_coeffs_in,
499 work_profile->lutsize,
500 work_profile->nonlinearlut)
502 else
503 val = in[ch];
504
505 if(p->compensate_middle_grey && !IS_NULL_PTR(work_profile))
506 y = x = dt_ioppr_compensate_middle_grey(val, work_profile);
507 else
508 y = x = val;
509
510 x -= increment;
511 y += increment;
512
513 CLAMP(x, 0.f, 1.f);
514 CLAMP(y, 0.f, 1.f);
515
516 return _add_node(p->curve_nodes[ch], &p->curve_num_nodes[ch], x, y);
517}
518
520{
521 (void)piece;
523 if(picker == g->colorpicker_set_values)
524 {
527
528 const int ch = g->channel;
530
531 // reset current curve
532 p->curve_num_nodes[ch] = d->curve_num_nodes[ch];
533 p->curve_type[ch] = d->curve_type[ch];
534 for(int k = 0; k < DT_IOP_RGBCURVE_MAXNODES; k++)
535 {
536 p->curve_nodes[ch][k].x = d->curve_nodes[ch][k].x;
537 p->curve_nodes[ch][k].y = d->curve_nodes[ch][k].y;
538 }
539
540 const GdkModifierType state = dt_key_modifier_state();
541 int picker_set_upper_lower; // flat=0, lower=-1, upper=1
542 if(dt_modifier_is(state, GDK_CONTROL_MASK))
543 picker_set_upper_lower = 1;
544 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
545 picker_set_upper_lower = -1;
546 else
547 picker_set_upper_lower = 0;
548
549 // now add 4 nodes: min, avg, center, max
550 const float increment = 0.05f * picker_set_upper_lower;
551
552 _add_node_from_picker(p, self->picked_color_min, 0.f, ch, work_profile);
553 _add_node_from_picker(p, self->picked_color, increment, ch, work_profile);
554 _add_node_from_picker(p, self->picked_color_max, 0.f, ch, work_profile);
555
556 if(p->curve_num_nodes[ch] == 5)
557 _add_node(p->curve_nodes[ch], &p->curve_num_nodes[ch],
558 p->curve_nodes[ch][1].x - increment + (p->curve_nodes[ch][3].x - p->curve_nodes[ch][1].x) / 2.f,
559 p->curve_nodes[ch][1].y + increment + (p->curve_nodes[ch][3].y - p->curve_nodes[ch][1].y) / 2.f);
560
562 }
563
565}
566
567static gboolean _sanity_check(const float x, const int selected, const int nodes,
568 const dt_iop_rgbcurve_node_t *curve)
569{
570 gboolean point_valid = TRUE;
571
572 // check if it is not too close to other node
573 const float min_dist = DT_IOP_RGBCURVE_MIN_X_DISTANCE; // in curve coordinates
574 if((selected > 0 && x - curve[selected - 1].x <= min_dist)
575 || (selected < nodes - 1 && curve[selected + 1].x - x <= min_dist))
576 point_valid = FALSE;
577
578 // for all points, x coordinate of point must be strictly larger than
579 // the x coordinate of the previous point
580 if((selected > 0 && (curve[selected - 1].x >= x)) || (selected < nodes - 1 && (curve[selected + 1].x <= x)))
581 {
582 point_valid = FALSE;
583 }
584
585 return point_valid;
586}
587
588static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
589{
592
593 const int ch = g->channel;
594 dt_iop_rgbcurve_node_t *curve = p->curve_nodes[ch];
595
596 const float new_x = CLAMP(curve[g->selected].x + dx, 0.0f, 1.0f);
597 const float new_y = CLAMP(curve[g->selected].y + dy, 0.0f, 1.0f);
598
599 gtk_widget_queue_draw(widget);
600
601 if(_sanity_check(new_x, g->selected, p->curve_num_nodes[ch], p->curve_nodes[ch]))
602 {
603 curve[g->selected].x = new_x;
604 curve[g->selected].y = new_y;
605
607 }
608
609 return TRUE;
610}
611
612#define RGBCURVE_DEFAULT_STEP (0.001f)
613
614static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
615{
618
619 gdouble delta_y;
620
621 // if autoscale is on: do not modify g and b curves
622 if((p->curve_autoscale != DT_S_SCALE_MANUAL_RGB) && g->channel != DT_IOP_RGBCURVE_R) return TRUE;
623
624 if(g->selected < 0) return TRUE;
625
627
628 if(dt_gui_get_scroll_delta(event, &delta_y))
629 {
630 delta_y *= -RGBCURVE_DEFAULT_STEP;
631 return _move_point_internal(self, widget, 0.0, delta_y, event->state);
632 }
633
634 return TRUE;
635}
636
637static gboolean _area_key_press_callback(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *self)
638{
641
642 // if autoscale is on: do not modify g and b curves
643 if((p->curve_autoscale != DT_S_SCALE_MANUAL_RGB) && g->channel != DT_IOP_RGBCURVE_R) return TRUE;
644
645 if(g->selected < 0) return FALSE;
646
647 guint key = dt_keys_mainpad_alternatives(event->keyval);
648 int handled = 0;
649 float dx = 0.0f, dy = 0.0f;
650 if(key == GDK_KEY_Up)
651 {
652 handled = 1;
654 }
655 else if(key == GDK_KEY_Down)
656 {
657 handled = 1;
659 }
660 else if(key == GDK_KEY_Right)
661 {
662 handled = 1;
664 }
665 else if(key == GDK_KEY_Left)
666 {
667 handled = 1;
669 }
670
671 if(!handled) return FALSE;
672
674 return _move_point_internal(self, widget, dx, dy, event->state);
675}
676
677#undef RGBCURVE_DEFAULT_STEP
678
679static gboolean _area_enter_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
680{
681 gtk_widget_queue_draw(widget);
682 return TRUE;
683}
684
685static gboolean _area_leave_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
686{
687 gtk_widget_queue_draw(widget);
688 return TRUE;
689}
690
691static gboolean _area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
692{
696
697 const int ch = g->channel;
698 const int nodes = p->curve_num_nodes[ch];
699 const int autoscale = p->curve_autoscale;
700 dt_iop_rgbcurve_node_t *curve_nodes = p->curve_nodes[ch];
701
702 if(g->minmax_curve_type[ch] != p->curve_type[ch] || g->minmax_curve_nodes[ch] != p->curve_num_nodes[ch])
703 {
704 dt_draw_curve_destroy(g->minmax_curve[ch]);
705 g->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->curve_type[ch]);
706 g->minmax_curve_nodes[ch] = p->curve_num_nodes[ch];
707 g->minmax_curve_type[ch] = p->curve_type[ch];
708 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
709 (void)dt_draw_curve_add_point(g->minmax_curve[ch], p->curve_nodes[ch][k].x, p->curve_nodes[ch][k].y);
710 }
711 else
712 {
713 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
714 dt_draw_curve_set_point(g->minmax_curve[ch], k, p->curve_nodes[ch][k].x, p->curve_nodes[ch][k].y);
715 }
716 dt_draw_curve_t *minmax_curve = g->minmax_curve[ch];
717 dt_draw_curve_calc_values(minmax_curve, 0.0, 1.0, DT_IOP_RGBCURVE_RES, NULL, g->draw_ys);
718
719 float unbounded_coeffs[3];
720 const float xm = curve_nodes[nodes - 1].x;
721 {
722 const float x[4] = { 0.7f * xm, 0.8f * xm, 0.9f * xm, 1.0f * xm };
723 const float y[4] = { g->draw_ys[CLAMP((int)(x[0] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)],
724 g->draw_ys[CLAMP((int)(x[1] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)],
725 g->draw_ys[CLAMP((int)(x[2] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)],
726 g->draw_ys[CLAMP((int)(x[3] * DT_IOP_RGBCURVE_RES), 0, DT_IOP_RGBCURVE_RES - 1)] };
728 }
729
730 const int inset = DT_GUI_CURVE_EDITOR_INSET;
731 GtkAllocation allocation;
732 gtk_widget_get_allocation(widget, &allocation);
733 int width = allocation.width, height = allocation.height;
734 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
735 cairo_t *cr = cairo_create(cst);
736
737 // clear bg
738 cairo_set_source_rgb(cr, .2, .2, .2);
739 cairo_paint(cr);
740
741 cairo_translate(cr, inset, inset);
742 width -= 2 * inset;
743 height -= 2 * inset;
744 char text[256];
745
746 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
747 cairo_set_source_rgb(cr, .1, .1, .1);
748 cairo_rectangle(cr, 0, 0, width, height);
749 cairo_stroke(cr);
750
751 cairo_set_source_rgb(cr, .3, .3, .3);
752 cairo_rectangle(cr, 0, 0, width, height);
753 cairo_fill(cr);
754
755 // draw grid
756 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
757 cairo_set_source_rgb(cr, .1, .1, .1);
758
759 cairo_translate(cr, 0, height);
760
761 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);
762
763 const double dashed[] = { 4.0, 4.0 };
764 const int len = sizeof(dashed) / sizeof(dashed[0]);
765 cairo_set_dash(cr, dashed, len, 0);
766 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);
767 cairo_set_dash(cr, dashed, 0, 0);
768
769 // draw identity line
770 cairo_move_to(cr, _curve_to_mouse(0.f, g->zoom_factor, g->offset_x) * width,
771 _curve_to_mouse(0.f, g->zoom_factor, g->offset_y) * -height);
772 cairo_line_to(cr, _curve_to_mouse(1.f, g->zoom_factor, g->offset_x) * width,
773 _curve_to_mouse(1.f, g->zoom_factor, g->offset_y) * -height);
774 cairo_stroke(cr);
775
776 // if autoscale is on: do not display g and b curves
777 if((autoscale != DT_S_SCALE_MANUAL_RGB) && ch != DT_IOP_RGBCURVE_R) goto finally;
778
779 // draw nodes positions
780 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
781 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
782
783 for(int k = 0; k < nodes; k++)
784 {
785 const float x = _curve_to_mouse(curve_nodes[k].x, g->zoom_factor, g->offset_x),
786 y = _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y);
787
788 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(3), 0, 2. * M_PI);
789 cairo_stroke(cr);
790 }
791
792 // draw selected cursor
793 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
794
795 if(g->selected >= 0)
796 {
797 cairo_set_source_rgb(cr, .9, .9, .9);
798 const float x = _curve_to_mouse(curve_nodes[g->selected].x, g->zoom_factor, g->offset_x),
799 y = _curve_to_mouse(curve_nodes[g->selected].y, g->zoom_factor, g->offset_y);
800
801 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
802 cairo_stroke(cr);
803 }
804
805 // draw histogram in background
806 // only if module is enabled
807 if(self->enabled)
808 {
809 const uint32_t *hist = self->histogram;
810 const gboolean is_linear = FALSE;
811 float hist_max;
812
813 if(autoscale == DT_S_SCALE_AUTOMATIC_RGB)
815 else
816 hist_max = self->histogram_max[ch];
817
818 if (!is_linear)
819 hist_max = logf(1.0 + hist_max);
820
821 if(!IS_NULL_PTR(hist) && hist_max > 0.0f)
822 {
823 cairo_push_group_with_content(cr, CAIRO_CONTENT_COLOR);
824 cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
825
826 if(autoscale == DT_S_SCALE_AUTOMATIC_RGB)
827 {
828 cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
830 {
832 dt_draw_histogram_8_zoomed(cr, hist, 4, k, g->zoom_factor, g->offset_x * 255.0, g->offset_y * hist_max,
833 is_linear);
834 }
835 }
836 else if(autoscale == DT_S_SCALE_MANUAL_RGB)
837 {
839 dt_draw_histogram_8_zoomed(cr, hist, 4, ch, g->zoom_factor, g->offset_x * 255.0, g->offset_y * hist_max,
840 is_linear);
841 }
842
843 cairo_pop_group_to_source(cr);
844 cairo_paint_with_alpha(cr, 0.2);
845 }
846
848 {
849 const dt_iop_order_iccprofile_info_t *const work_profile
851
852 dt_aligned_pixel_t picker_mean, picker_min, picker_max;
853
854 // the global live samples ...
855 GSList *samples = darktable.develop->color_picker.samples;
856 if(samples)
857 {
859 if(!IS_NULL_PTR(work_profile) && !IS_NULL_PTR(display_profile))
860 {
861 for(; samples; samples = g_slist_next(samples))
862 {
863 dt_colorpicker_sample_t *sample = samples->data;
864
865 // this functions need a 4c image
866 for(int k = 0; k < 3; k++)
867 {
868 picker_mean[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MEAN][k];
869 picker_min[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MIN][k];
870 picker_max[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MAX][k];
871 }
872 picker_mean[3] = picker_min[3] = picker_max[3] = 1.f;
873
874 dt_ioppr_transform_image_colorspace_rgb(picker_mean, picker_mean, 1, 1, display_profile,
875 work_profile, "rgb curve");
876 dt_ioppr_transform_image_colorspace_rgb(picker_min, picker_min, 1, 1, display_profile, work_profile,
877 "rgb curve");
878 dt_ioppr_transform_image_colorspace_rgb(picker_max, picker_max, 1, 1, display_profile, work_profile,
879 "rgb curve");
880
881 picker_scale(picker_mean, picker_mean, p, work_profile);
882 picker_scale(picker_min, picker_min, p, work_profile);
883 picker_scale(picker_max, picker_max, p, work_profile);
884
885 // Convert abcissa to log coordinates if needed
886 picker_min[ch] = _curve_to_mouse(picker_min[ch], g->zoom_factor, g->offset_x);
887 picker_max[ch] = _curve_to_mouse(picker_max[ch], g->zoom_factor, g->offset_x);
888 picker_mean[ch] = _curve_to_mouse(picker_mean[ch], g->zoom_factor, g->offset_x);
889
890 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.15);
891 cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
892 -height);
893 cairo_fill(cr);
894 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.5);
895 cairo_move_to(cr, width * picker_mean[ch], 0);
896 cairo_line_to(cr, width * picker_mean[ch], -height);
897 cairo_stroke(cr);
898 }
899 }
900 }
901
902 // ... and the local sample
903 if(self->picked_color_max[ch] >= 0.0f)
904 {
905 PangoLayout *layout;
906 PangoRectangle ink;
907 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
908 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
909 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
910 layout = pango_cairo_create_layout(cr);
911 pango_layout_set_font_description(layout, desc);
912
913 picker_scale(self->picked_color, picker_mean, p, work_profile);
914 picker_scale(self->picked_color_min, picker_min, p, work_profile);
915 picker_scale(self->picked_color_max, picker_max, p, work_profile);
916
917 // scale conservatively to 100% of width:
918 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
919 pango_layout_set_text(layout, text, -1);
920 pango_layout_get_pixel_extents(layout, &ink, NULL);
921 pango_font_description_set_absolute_size(desc, width * 1.0 / ink.width * PANGO_SCALE);
922 pango_layout_set_font_description(layout, desc);
923
924 picker_min[ch] = _curve_to_mouse(picker_min[ch], g->zoom_factor, g->offset_x);
925 picker_max[ch] = _curve_to_mouse(picker_max[ch], g->zoom_factor, g->offset_x);
926 picker_mean[ch] = _curve_to_mouse(picker_mean[ch], g->zoom_factor, g->offset_x);
927
928 cairo_set_source_rgba(cr, 0.7, 0.5, 0.5, 0.33);
929 cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
930 -height);
931 cairo_fill(cr);
932 cairo_set_source_rgba(cr, 0.9, 0.7, 0.7, 0.5);
933 cairo_move_to(cr, width * picker_mean[ch], 0);
934 cairo_line_to(cr, width * picker_mean[ch], -height);
935 cairo_stroke(cr);
936
937 picker_scale(self->picked_color, picker_mean, p, work_profile);
938 picker_scale(self->picked_output_color, picker_min, p, work_profile);
939 snprintf(text, sizeof(text), "%.1f \342\206\222 %.1f", picker_mean[ch] * 255.f, picker_min[ch] * 255.f);
940
941 cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
942 cairo_set_font_size(cr, DT_PIXEL_APPLY_DPI(0.04) * height);
943 pango_layout_set_text(layout, text, -1);
944 pango_layout_get_pixel_extents(layout, &ink, NULL);
945 cairo_move_to(cr, 0.02f * width, -0.94 * height - ink.height - ink.y);
946 pango_cairo_show_layout(cr, layout);
947 cairo_stroke(cr);
948 pango_font_description_free(desc);
949 g_object_unref(layout);
950 }
951 }
952 }
953
954 // draw zoom info
955 if(g->selected >= 0)
956 {
957 // draw information about current selected node
958 PangoLayout *layout;
959 PangoRectangle ink;
960 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
961 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
962 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
963 layout = pango_cairo_create_layout(cr);
964 pango_layout_set_font_description(layout, desc);
965
966 // scale conservatively to 100% of width:
967 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
968 pango_layout_set_text(layout, text, -1);
969 pango_layout_get_pixel_extents(layout, &ink, NULL);
970 pango_font_description_set_absolute_size(desc, width * 1.0 / ink.width * PANGO_SCALE);
971 pango_layout_set_font_description(layout, desc);
972
973 const float min_scale_value = 0.0f;
974 const float max_scale_value = 255.0f;
975
976 const float x_node_value = curve_nodes[g->selected].x * (max_scale_value - min_scale_value) + min_scale_value;
977 const float y_node_value = curve_nodes[g->selected].y * (max_scale_value - min_scale_value) + min_scale_value;
978 const float d_node_value = y_node_value - x_node_value;
979 snprintf(text, sizeof(text), "%.1f / %.1f ( %+.1f)", x_node_value, y_node_value, d_node_value);
980
981 cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
982 pango_layout_set_text(layout, text, -1);
983 pango_layout_get_pixel_extents(layout, &ink, NULL);
984 cairo_move_to(cr, 0.98f * width - ink.width - ink.x, -0.02 * height - ink.height - ink.y);
985 pango_cairo_show_layout(cr, layout);
986 cairo_stroke(cr);
987 pango_font_description_free(desc);
988 g_object_unref(layout);
989
990 // enlarge selected node
991 cairo_set_source_rgb(cr, .9, .9, .9);
992 const float x = _curve_to_mouse(curve_nodes[g->selected].x, g->zoom_factor, g->offset_x),
993 y = _curve_to_mouse(curve_nodes[g->selected].y, g->zoom_factor, g->offset_y);
994
995 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
996 cairo_stroke(cr);
997 }
998
999 // draw curve
1000 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1001 cairo_set_source_rgb(cr, .9, .9, .9);
1002
1003 const float y_offset = _curve_to_mouse(g->draw_ys[0], g->zoom_factor, g->offset_y);
1004 cairo_move_to(cr, 0, -height * y_offset);
1005
1006 for(int k = 1; k < DT_IOP_RGBCURVE_RES; k++)
1007 {
1008 const float xx = k / (DT_IOP_RGBCURVE_RES - 1.0f);
1009 float yy;
1010
1011 if(xx > xm)
1012 {
1014 }
1015 else
1016 {
1017 yy = g->draw_ys[k];
1018 }
1019
1020 const float x = _curve_to_mouse(xx, g->zoom_factor, g->offset_x),
1021 y = _curve_to_mouse(yy, g->zoom_factor, g->offset_y);
1022
1023 cairo_line_to(cr, x * width, -height * y);
1024 }
1025 cairo_stroke(cr);
1026
1027finally:
1028 cairo_destroy(cr);
1029 cairo_set_source_surface(crf, cst, 0, 0);
1030 cairo_paint(crf);
1031 cairo_surface_destroy(cst);
1032 return TRUE;
1033}
1034
1035static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
1036{
1039
1040 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1041
1042 const int ch = g->channel;
1043 const int nodes = p->curve_num_nodes[ch];
1044 dt_iop_rgbcurve_node_t *curve_nodes = p->curve_nodes[ch];
1045
1046 // if autoscale is on: do not modify g and b curves
1047 if((p->curve_autoscale != DT_S_SCALE_MANUAL_RGB) && g->channel != DT_IOP_RGBCURVE_R) goto finally;
1048
1049 GtkAllocation allocation;
1050 gtk_widget_get_allocation(widget, &allocation);
1051 const int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
1052
1053 const double old_m_x = g->mouse_x;
1054 const double old_m_y = g->mouse_y;
1055
1056 g->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1057 g->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1058
1059 const float mx = g->mouse_x;
1060 const float my = g->mouse_y;
1061 const float linx = _mouse_to_curve(mx, g->zoom_factor, g->offset_x),
1062 liny = _mouse_to_curve(my, g->zoom_factor, g->offset_y);
1063
1064 if(event->state & GDK_BUTTON1_MASK)
1065 {
1066 // got a vertex selected:
1067 if(g->selected >= 0)
1068 {
1069 // this is used to translate mause position in loglogscale to make this behavior unified with linear scale.
1070 const float translate_mouse_x
1071 = old_m_x - _curve_to_mouse(curve_nodes[g->selected].x, g->zoom_factor, g->offset_x);
1072 const float translate_mouse_y
1073 = old_m_y - _curve_to_mouse(curve_nodes[g->selected].y, g->zoom_factor, g->offset_y);
1074 // dx & dy are in linear coordinates
1075 const float dx = _mouse_to_curve(g->mouse_x - translate_mouse_x, g->zoom_factor, g->offset_x)
1076 - _mouse_to_curve(old_m_x - translate_mouse_x, g->zoom_factor, g->offset_x);
1077 const float dy = _mouse_to_curve(g->mouse_y - translate_mouse_y, g->zoom_factor, g->offset_y)
1078 - _mouse_to_curve(old_m_y - translate_mouse_y, g->zoom_factor, g->offset_y);
1079
1081 return _move_point_internal(self, widget, dx, dy, event->state);
1082 }
1083 else if(nodes < DT_IOP_RGBCURVE_MAXNODES && g->selected >= -1)
1084 {
1086 // no vertex was close, create a new one!
1087 g->selected = _add_node(curve_nodes, &p->curve_num_nodes[ch], linx, liny);
1089 }
1090 }
1091 else
1092 {
1093 // minimum area around the node to select it:
1094 float min = .04f * .04f; // comparing against square
1095 int nearest = -1;
1096 for(int k = 0; k < nodes; k++)
1097 {
1098 const float dist = (my - _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y))
1099 * (my - _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y))
1100 + (mx - _curve_to_mouse(curve_nodes[k].x, g->zoom_factor, g->offset_x))
1101 * (mx - _curve_to_mouse(curve_nodes[k].x, g->zoom_factor, g->offset_x));
1102 if(dist < min)
1103 {
1104 min = dist;
1105 nearest = k;
1106 }
1107 }
1108 g->selected = nearest;
1109 }
1110finally:
1111 if(g->selected >= 0) gtk_widget_grab_focus(widget);
1112 gtk_widget_queue_draw(widget);
1113 return TRUE;
1114}
1115
1116static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1117{
1121
1122 const int ch = g->channel;
1123 const int autoscale = p->curve_autoscale;
1124 const int nodes = p->curve_num_nodes[ch];
1125 dt_iop_rgbcurve_node_t *curve_nodes = p->curve_nodes[ch];
1126
1127 if(event->button == 1)
1128 {
1129 if(event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, GDK_CONTROL_MASK)
1130 && nodes < DT_IOP_RGBCURVE_MAXNODES && g->selected == -1)
1131 {
1132 // 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
1133 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1134 GtkAllocation allocation;
1135 gtk_widget_get_allocation(widget, &allocation);
1136 const int width = allocation.width - 2 * inset;
1137 const int height = allocation.height - 2 * inset;
1138
1139 g->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1140 g->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1141
1142 const float mx = g->mouse_x;
1143 const float linx = _mouse_to_curve(mx, g->zoom_factor, g->offset_x);
1144
1145 // don't add a node too close to others in x direction, it can crash dt
1146 int selected = -1;
1147 if(curve_nodes[0].x > mx)
1148 selected = 0;
1149 else
1150 {
1151 for(int k = 1; k < nodes; k++)
1152 {
1153 if(curve_nodes[k].x > mx)
1154 {
1155 selected = k;
1156 break;
1157 }
1158 }
1159 }
1160 if(selected == -1) selected = nodes;
1161
1162 // evaluate the curve at the current x position
1163 const float y = dt_draw_curve_calc_value(g->minmax_curve[ch], linx);
1164
1165 if(y >= 0.0f && y <= 1.0f) // never add something outside the viewport, you couldn't change it afterwards
1166 {
1167 // create a new node
1168 selected = _add_node(curve_nodes, &p->curve_num_nodes[ch], linx, y);
1169
1170 // maybe set the new one as being selected
1171 const float min = .04f * .04f; // comparing against square
1172 for(int k = 0; k < nodes; k++)
1173 {
1174 const float other_y = _curve_to_mouse(curve_nodes[k].y, g->zoom_factor, g->offset_y);
1175 const float dist = (y - other_y) * (y - other_y);
1176 if(dist < min) g->selected = selected;
1177 }
1178
1181 gtk_widget_queue_draw(self->widget);
1182 }
1183
1184 return TRUE;
1185 }
1186 else if(event->type == GDK_2BUTTON_PRESS)
1187 {
1188 // reset current curve
1189 // if autoscale is on: allow only reset of L curve
1190 if(!((autoscale != DT_S_SCALE_MANUAL_RGB) && ch != DT_IOP_RGBCURVE_R))
1191 {
1192 p->curve_num_nodes[ch] = d->curve_num_nodes[ch];
1193 p->curve_type[ch] = d->curve_type[ch];
1194 for(int k = 0; k < d->curve_num_nodes[ch]; k++)
1195 {
1196 p->curve_nodes[ch][k].x = d->curve_nodes[ch][k].x;
1197 p->curve_nodes[ch][k].y = d->curve_nodes[ch][k].y;
1198 }
1199 g->selected = -2; // avoid motion notify re-inserting immediately.
1200 dt_bauhaus_combobox_set(g->interpolator, p->curve_type[DT_IOP_RGBCURVE_R]);
1203 gtk_widget_queue_draw(self->widget);
1204 }
1205 else
1206 {
1207 if(ch != DT_IOP_RGBCURVE_R)
1208 {
1209 p->curve_autoscale = DT_S_SCALE_MANUAL_RGB;
1210 g->selected = -2; // avoid motion notify re-inserting immediately.
1211 dt_bauhaus_combobox_set(g->autoscale, 1);
1214 gtk_widget_queue_draw(self->widget);
1215 }
1216 }
1217 return TRUE;
1218 }
1219 }
1220 else if(event->button == 3 && g->selected >= 0)
1221 {
1222 if(g->selected == 0 || g->selected == nodes - 1)
1223 {
1224 const float reset_value = g->selected == 0 ? 0.f : 1.f;
1225 curve_nodes[g->selected].y = curve_nodes[g->selected].x = reset_value;
1228 gtk_widget_queue_draw(self->widget);
1229 return TRUE;
1230 }
1231
1232 for(int k = g->selected; k < nodes - 1; k++)
1233 {
1234 curve_nodes[k].x = curve_nodes[k + 1].x;
1235 curve_nodes[k].y = curve_nodes[k + 1].y;
1236 }
1237 curve_nodes[nodes - 1].x = curve_nodes[nodes - 1].y = 0;
1238 g->selected = -2; // avoid re-insertion of that point immediately after this
1239 p->curve_num_nodes[ch]--;
1242 gtk_widget_queue_draw(self->widget);
1243 return TRUE;
1244 }
1245 return FALSE;
1246}
1247
1248void gui_reset(struct dt_iop_module_t *self)
1249{
1252
1253 g->channel = DT_IOP_RGBCURVE_R;
1254 g->selected = -1;
1255 g->offset_x = g->offset_y = 0.f;
1256 g->zoom_factor = 1.f;
1257
1258 dt_bauhaus_combobox_set(g->interpolator, p->curve_type[DT_IOP_RGBCURVE_R]);
1259
1260 gtk_widget_queue_draw(self->widget);
1261}
1262
1264{
1266 if(!IS_NULL_PTR(g))
1267 {
1268 if(!g->channel)
1269 g->channel = DT_IOP_RGBCURVE_R;
1270 g->mouse_x = g->mouse_y = -1.0;
1271 g->selected = -1;
1272 g->offset_x = g->offset_y = 0.f;
1273 g->zoom_factor = 1.f;
1274 }
1275}
1276
1277void gui_init(struct dt_iop_module_t *self)
1278{
1281
1282 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1283 {
1284 g->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->curve_type[ch]);
1285 g->minmax_curve_nodes[ch] = p->curve_num_nodes[ch];
1286 g->minmax_curve_type[ch] = p->curve_type[ch];
1287 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
1288 (void)dt_draw_curve_add_point(g->minmax_curve[ch], p->curve_nodes[ch][k].x, p->curve_nodes[ch][k].y);
1289 }
1290
1291 g->channel = DT_IOP_RGBCURVE_R;
1292 self->timeout_handle = 0;
1293 change_image(self);
1294
1295 g->autoscale = dt_bauhaus_combobox_from_params(self, "curve_autoscale");
1296 gtk_widget_set_tooltip_text(g->autoscale, _("choose between linked and independent channels."));
1297
1298 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1299
1300 g->channel_tabs = GTK_NOTEBOOK(gtk_notebook_new());
1301 dt_ui_notebook_page(g->channel_tabs, N_("R"), _("curve nodes for r channel"));
1302 dt_ui_notebook_page(g->channel_tabs, N_("G"), _("curve nodes for g channel"));
1303 dt_ui_notebook_page(g->channel_tabs, N_("B"), _("curve nodes for b channel"));
1304 g_signal_connect(G_OBJECT(g->channel_tabs), "switch_page", G_CALLBACK(tab_switch_callback), self);
1305 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(g->channel_tabs), TRUE, TRUE, 0);
1306 gtk_box_pack_start(GTK_BOX(hbox), gtk_grid_new(), TRUE, TRUE, 0);
1307
1308 // color pickers
1309 g->colorpicker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT_AREA, hbox);
1310 gtk_widget_set_tooltip_text(g->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
1311 g->colorpicker_set_values = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, hbox);
1312 dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->colorpicker_set_values),
1314
1315 gtk_widget_set_size_request(g->colorpicker_set_values, DT_PIXEL_APPLY_DPI(14), DT_PIXEL_APPLY_DPI(14));
1316 gtk_widget_set_tooltip_text(g->colorpicker_set_values, _("create a curve based on an area from the image\n"
1317 "drag to create a flat curve\n"
1318 "ctrl+drag to create a positive curve\n"
1319 "shift+drag to create a negative curve"));
1320
1321 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1322 gtk_box_pack_start(GTK_BOX(self->widget), vbox, FALSE, FALSE, 0);
1323 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0);
1324
1325 g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
1326 gtk_widget_set_hexpand(GTK_WIDGET(g->area), TRUE);
1327 g_object_set_data(G_OBJECT(g->area), "iop-instance", self);
1328 gtk_box_pack_start(GTK_BOX(vbox),
1329 dt_ui_resizable_drawing_area(GTK_WIDGET(g->area),
1330 "plugins/darkroom/rgbcurve/graphheight", 280, 100),
1331 FALSE, FALSE, 0);
1332
1333 // FIXME: that tooltip goes in the way of the numbers when you hover a node to get a reading
1334 // gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("double click to reset curve"));
1335
1336 gtk_widget_add_events(GTK_WIDGET(g->area), GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask
1337 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1338 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1339 gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
1340 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(_area_draw_callback), self);
1341 g_signal_connect(G_OBJECT(g->area), "button-press-event", G_CALLBACK(_area_button_press_callback), self);
1342 g_signal_connect(G_OBJECT(g->area), "motion-notify-event", G_CALLBACK(_area_motion_notify_callback), self);
1343 g_signal_connect(G_OBJECT(g->area), "leave-notify-event", G_CALLBACK(_area_leave_notify_callback), self);
1344 g_signal_connect(G_OBJECT(g->area), "enter-notify-event", G_CALLBACK(_area_enter_notify_callback), self);
1345 g_signal_connect(G_OBJECT(g->area), "configure-event", G_CALLBACK(_area_resized_callback), self);
1346 g_signal_connect(G_OBJECT(g->area), "scroll-event", G_CALLBACK(_area_scrolled_callback), self);
1347 g_signal_connect(G_OBJECT(g->area), "key-press-event", G_CALLBACK(_area_key_press_callback), self);
1348
1349 /* From src/common/curve_tools.h :
1350 #define CUBIC_SPLINE 0
1351 #define CATMULL_ROM 1
1352 #define MONOTONE_HERMITE 2
1353 */
1355 dt_bauhaus_widget_set_label(g->interpolator, N_("interpolation method"));
1356 dt_bauhaus_combobox_add(g->interpolator, _("cubic spline"));
1357 dt_bauhaus_combobox_add(g->interpolator, _("centripetal spline"));
1358 dt_bauhaus_combobox_add(g->interpolator, _("monotonic spline"));
1359 gtk_box_pack_start(GTK_BOX(self->widget), g->interpolator, TRUE, TRUE, 0);
1360 gtk_widget_set_tooltip_text(g->interpolator,
1361 _("change this method if you see oscillations or cusps in the curve\n"
1362 "- cubic spline is better to produce smooth curves but oscillates when nodes are too close\n"
1363 "- centripetal is better to avoids cusps and oscillations with close nodes but is less smooth\n"
1364 "- monotonic is better for accuracy of pure analytical functions (log, gamma, exp)\n"));
1365 g_signal_connect(G_OBJECT(g->interpolator), "value-changed", G_CALLBACK(interpolator_callback), self);
1366
1367 g->chk_compensate_middle_grey = dt_bauhaus_toggle_from_params(self, "compensate_middle_grey");
1368 gtk_widget_set_tooltip_text(g->chk_compensate_middle_grey, _("compensate middle gray"));
1369
1370 g->cmb_preserve_colors = dt_bauhaus_combobox_from_params(self, "preserve_colors");
1371 gtk_widget_set_tooltip_text(g->cmb_preserve_colors, _("method to preserve colors when applying contrast"));
1372}
1373
1374void gui_update(struct dt_iop_module_t *self)
1375{
1378
1379 dt_bauhaus_combobox_set(g->autoscale, p->curve_autoscale);
1380 dt_bauhaus_combobox_set(g->interpolator, p->curve_type[DT_IOP_RGBCURVE_R]);
1381 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->chk_compensate_middle_grey), p->compensate_middle_grey);
1382 dt_bauhaus_combobox_set(g->cmb_preserve_colors, p->preserve_colors);
1383
1385
1387
1388 // that's all, gui curve is read directly from params during expose event.
1389 gtk_widget_queue_draw(self->widget);
1390}
1391
1393{
1395
1396 for(int k = 0; k < DT_IOP_RGBCURVE_MAX_CHANNELS; k++) dt_draw_curve_destroy(g->minmax_curve[k]);
1397
1399
1401}
1402
1404{
1405 // create part of the pixelpipe
1408 piece->data = (void *)d;
1409 piece->data_size = sizeof(dt_iop_rgbcurve_data_t);
1410 memcpy(&d->params, default_params, sizeof(dt_iop_rgbcurve_params_t));
1411
1412 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1413 {
1414 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, default_params->curve_type[ch]);
1415 d->params.curve_num_nodes[ch] = default_params->curve_num_nodes[ch];
1416 d->params.curve_type[ch] = default_params->curve_type[ch];
1417 for(int k = 0; k < default_params->curve_num_nodes[ch]; k++)
1418 (void)dt_draw_curve_add_point(d->curve[ch], default_params->curve_nodes[ch][k].x,
1419 default_params->curve_nodes[ch][k].y);
1420 }
1421
1422 for(int k = 0; k < 0x10000; k++) d->table[DT_IOP_RGBCURVE_R][k] = k / 0x10000; // identity for r
1423 for(int k = 0; k < 0x10000; k++) d->table[DT_IOP_RGBCURVE_G][k] = k / 0x10000; // identity for g
1424 for(int k = 0; k < 0x10000; k++) d->table[DT_IOP_RGBCURVE_B][k] = k / 0x10000; // identity for b
1425}
1426
1428{
1429 // clean up everything again.
1431 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++) dt_draw_curve_destroy(d->curve[ch]);
1432 dt_free_align(piece->data);
1433 piece->data = NULL;
1434}
1435
1437{
1438 dt_iop_default_init(module);
1439
1440 module->request_histogram |= (DT_REQUEST_ON);
1441
1442 dt_iop_rgbcurve_params_t *d = module->default_params;
1443
1444 d->curve_nodes[0][1].x = d->curve_nodes[0][1].y =
1445 d->curve_nodes[1][1].x = d->curve_nodes[1][1].y =
1446 d->curve_nodes[2][1].x = d->curve_nodes[2][1].y = 1.0;
1447
1448 module->histogram_middle_grey = d->compensate_middle_grey;
1449}
1450
1451// this will be called from process*()
1452// it must be executed only if profile info has changed
1455{
1457
1459
1460 if(!IS_NULL_PTR(work_profile))
1461 {
1462 if(d->type_work == work_profile->type && strcmp(d->filename_work, work_profile->filename) == 0) return;
1463 }
1464
1465 if(!IS_NULL_PTR(work_profile) && d->params.compensate_middle_grey)
1466 {
1467 d->type_work = work_profile->type;
1468 g_strlcpy(d->filename_work, work_profile->filename, sizeof(d->filename_work));
1469
1470 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1471 {
1472 for(int k = 0; k < d->params.curve_num_nodes[ch]; k++)
1473 {
1474 curve_nodes[ch][k].x = dt_ioppr_uncompensate_middle_grey(d->params.curve_nodes[ch][k].x, work_profile);
1475 curve_nodes[ch][k].y = dt_ioppr_uncompensate_middle_grey(d->params.curve_nodes[ch][k].y, work_profile);
1476 }
1477 }
1478 }
1479 else
1480 {
1481 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1482 {
1483 memcpy(curve_nodes[ch], d->params.curve_nodes[ch], sizeof(dt_iop_rgbcurve_node_t) * DT_IOP_RGBCURVE_MAXNODES);
1484 }
1485 }
1486
1487 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1488 {
1489 // take care of possible change of curve type or number of nodes (not yet implemented in UI)
1490 if(d->curve_changed[ch])
1491 {
1492 dt_draw_curve_destroy(d->curve[ch]);
1493 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, d->params.curve_type[ch]);
1494 for(int k = 0; k < d->params.curve_num_nodes[ch]; k++)
1495 (void)dt_draw_curve_add_point(d->curve[ch], curve_nodes[ch][k].x, curve_nodes[ch][k].y);
1496 }
1497 else
1498 {
1499 for(int k = 0; k < d->params.curve_num_nodes[ch]; k++)
1500 dt_draw_curve_set_point(d->curve[ch], k, curve_nodes[ch][k].x, curve_nodes[ch][k].y);
1501 }
1502
1503 dt_draw_curve_calc_values(d->curve[ch], 0.0f, 1.0f, 0x10000, NULL, d->table[ch]);
1504 }
1505
1506 // extrapolation for each curve (right hand side only):
1507 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1508 {
1509 const float xm_L = curve_nodes[ch][d->params.curve_num_nodes[ch] - 1].x;
1510 const float x_L[4] = { 0.7f * xm_L, 0.8f * xm_L, 0.9f * xm_L, 1.0f * xm_L };
1511 const float y_L[4] = { d->table[ch][CLAMP((int)(x_L[0] * 0x10000ul), 0, 0xffff)],
1512 d->table[ch][CLAMP((int)(x_L[1] * 0x10000ul), 0, 0xffff)],
1513 d->table[ch][CLAMP((int)(x_L[2] * 0x10000ul), 0, 0xffff)],
1514 d->table[ch][CLAMP((int)(x_L[3] * 0x10000ul), 0, 0xffff)] };
1515 dt_iop_estimate_exp(x_L, y_L, 4, d->unbounded_coeffs[ch]);
1516 }
1517}
1518
1521{
1524
1525 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, NULL))
1526 piece->request_histogram |= (DT_REQUEST_ON);
1527 else
1528 piece->request_histogram &= ~(DT_REQUEST_ON);
1529
1530 for(int ch = 0; ch < DT_IOP_RGBCURVE_MAX_CHANNELS; ch++)
1531 d->curve_changed[ch]
1532 = (d->params.curve_type[ch] != p->curve_type[ch] || d->params.curve_nodes[ch] != p->curve_nodes[ch]);
1533
1534 memcpy(&d->params, p, sizeof(dt_iop_rgbcurve_params_t));
1535
1536 // working color profile
1537 d->type_work = DT_COLORSPACE_NONE;
1538 d->filename_work[0] = '\0';
1539}
1540
1541
1543int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
1544 void *const ovoid)
1545{
1546 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1548
1549 const float *const restrict in = (float*)ivoid;
1550 float *const restrict out = (float*)ovoid;
1551
1552 dt_iop_rgbcurve_data_t *const restrict d = (dt_iop_rgbcurve_data_t *)(piece->data);
1553
1554 _generate_curve_lut(pipe, d);
1555
1556 const float xm_L = 1.0f / d->unbounded_coeffs[DT_IOP_RGBCURVE_R][0];
1557 const float xm_g = 1.0f / d->unbounded_coeffs[DT_IOP_RGBCURVE_G][0];
1558 const float xm_b = 1.0f / d->unbounded_coeffs[DT_IOP_RGBCURVE_B][0];
1559
1560 const int width = roi_out->width;
1561 const int height = roi_out->height;
1562 const size_t npixels = (size_t)width * height;
1563 const int autoscale = d->params.curve_autoscale;
1564 const _curve_table_ptr restrict table = d->table;
1565 const _coeffs_table_ptr restrict unbounded_coeffs = d->unbounded_coeffs;
1567 for(int y = 0; y < 4*npixels; y += 4)
1568 {
1569 if(autoscale == DT_S_SCALE_MANUAL_RGB)
1570 {
1571 out[y+0] = (in[y+0] < xm_L) ? table[DT_IOP_RGBCURVE_R][CLAMP((int)(in[y+0] * 0x10000ul), 0, 0xffff)]
1573 out[y+1] = (in[y+1] < xm_g) ? table[DT_IOP_RGBCURVE_G][CLAMP((int)(in[y+1] * 0x10000ul), 0, 0xffff)]
1575 out[y+2] = (in[y+2] < xm_b) ? table[DT_IOP_RGBCURVE_B][CLAMP((int)(in[y+2] * 0x10000ul), 0, 0xffff)]
1577 }
1578 else if(autoscale == DT_S_SCALE_AUTOMATIC_RGB)
1579 {
1580 if(d->params.preserve_colors == DT_RGB_NORM_NONE)
1581 {
1582 for(int c = 0; c < 3; c++)
1583 {
1584 out[y+c] = (in[y+c] < xm_L) ? table[DT_IOP_RGBCURVE_R][CLAMP((int)(in[y+c] * 0x10000ul), 0, 0xffff)]
1586 }
1587 }
1588 else
1589 {
1590 float ratio = 1.f;
1591 const float lum = dt_rgb_norm(in + y, d->params.preserve_colors, work_profile);
1592 if(lum > 0.f)
1593 {
1594 const float curve_lum = (lum < xm_L)
1595 ? table[DT_IOP_RGBCURVE_R][CLAMP((int)(lum * 0x10000ul), 0, 0xffff)]
1597 ratio = curve_lum / lum;
1598 }
1599 for(size_t c = 0; c < 3; c++)
1600 {
1601 out[y+c] = (ratio * in[y+c]);
1602 }
1603 }
1604 }
1605 out[y+3] = in[y+3];
1606 }
1607
1608 return 0;
1609}
1610
1611#undef DT_GUI_CURVE_EDITOR_INSET
1612#undef DT_IOP_RGBCURVE_RES
1613#undef DT_IOP_RGBCURVE_MAXNODES
1614#undef DT_IOP_RGBCURVE_MIN_X_DISTANCE
1615#undef DT_IOP_COLOR_ICC_LEN
1616
1617// clang-format off
1618// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1619// vim: shiftwidth=2 expandtab tabstop=2 cindent
1620// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1621// clang-format on
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1842
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2016
static void set_color(cairo_t *cr, GdkRGBA color)
Definition bauhaus.h:446
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ DEVELOP_BLEND_CS_RGB_DISPLAY
Definition blend.h:59
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static float dt_camera_rgb_luminance(const float4 rgb)
@ IOP_CS_RGB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_AREA
@ DT_COLOR_PICKER_POINT_AREA
@ DT_LIB_COLORPICKER_STATISTIC_MAX
Definition colorpicker.h:45
@ DT_LIB_COLORPICKER_STATISTIC_MIN
Definition colorpicker.h:44
@ DT_LIB_COLORPICKER_STATISTIC_MEAN
Definition colorpicker.h:43
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
@ DT_COLORSPACE_NONE
Definition colorspaces.h:82
static const float const float const float min
const dt_colormatrix_t dt_aligned_pixel_t out
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * key
void dt_control_queue_redraw_widget(GtkWidget *widget)
threadsafe request of redraw of specific widget. Use this function if you need to redraw a specific w...
Definition control.c:906
#define CATMULL_ROM
Definition curve_tools.h:34
#define CUBIC_SPLINE
Definition curve_tools.h:33
#define MONOTONE_HERMITE
Definition curve_tools.h:35
darktable_t darktable
Definition darktable.c:181
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
static float dt_rgb_norm(const float4 in, const int norm, const int work_profile, constant dt_colorspaces_iccprofile_info_cl_t *profile_info, read_only image2d_t lut)
@ DT_RGB_NORM_NONE
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:41
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:367
static void dt_draw_curve_calc_values(dt_draw_curve_t *c, const float min, const float max, const int res, float *x, float *y)
Definition draw.h:309
static void dt_draw_histogram_8_zoomed(cairo_t *cr, const uint32_t *hist, int32_t channels, int32_t channel, const float zoom_factor, const float zoom_offset_x, const float zoom_offset_y, gboolean linear)
Definition draw.h:383
static float dt_draw_curve_calc_value(dt_draw_curve_t *c, const float x)
Definition draw.h:345
static void dt_draw_curve_destroy(dt_draw_curve_t *c)
Definition draw.h:282
static void dt_draw_curve_set_point(dt_draw_curve_t *c, const int num, const float x, const float y)
Definition draw.h:288
static int dt_draw_curve_add_point(dt_draw_curve_t *c, const float x, const float y)
Definition draw.h:364
static dt_draw_curve_t * dt_draw_curve_new(const float min, const float max, unsigned int type)
Definition draw.h:266
static void dt_draw_grid_zoomed(cairo_t *cr, const int num, const float left, const float top, const float right, const float bottom, const float width, const float height, const float zoom_factor, const float zoom_offset_x, const float zoom_offset_y)
Definition draw.h:164
void dtgtk_cairo_paint_colorpicker(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ CPF_ALTER
Definition dtgtk/paint.h:70
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
Definition gtk.c:302
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
Definition gtk.c:2836
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition gtk.c:2259
GdkModifierType dt_key_modifier_state()
Definition gtk.c:2186
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
void dt_gui_presets_add_generic(const char *name, dt_dev_operation_t op, const int32_t version, const void *params, const int32_t params_size, const int32_t enabled, const dt_develop_blend_colorspace_t blend_cst)
#define DT_GUI_MODULE(x)
void dt_gui_throttle_cancel(gpointer source)
void dt_gui_throttle_queue(gpointer source, dt_gui_throttle_callback_t callback, gpointer user_data)
void dt_iop_throttled_history_update(gpointer data)
Definition imageop.c:3135
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:316
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:196
#define IOP_GUI_FREE
Definition imageop.h:602
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:169
@ IOP_GROUP_COLOR
Definition imageop.h:139
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void *const ovoid
static void dt_iop_estimate_exp(const float *const x, const float *const y, const int num, float *coeff)
static float dt_iop_eval_exp(const float *const coeff, const float x)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_output_profile_info(const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
static const float x
static dt_aligned_pixel_t float *const const float unbounded_coeffs[3][3]
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define M_PI
Definition math.h:45
float dt_aligned_pixel_t[4]
@ DT_REQUEST_ON
Definition pixelpipe.h:48
static gboolean _area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
Definition rgbcurve.c:691
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:1519
void init(dt_iop_module_t *module)
Definition rgbcurve.c:1436
const char ** description(struct dt_iop_module_t *self)
Definition rgbcurve.c:162
int default_group()
Definition rgbcurve.c:147
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition rgbcurve.c:1543
static void interpolator_callback(GtkWidget *widget, dt_iop_module_t *self)
Definition rgbcurve.c:414
static void _rgbcurve_show_hide_controls(dt_iop_rgbcurve_params_t *p, dt_iop_rgbcurve_gui_data_t *g)
Definition rgbcurve.c:343
static gboolean _area_key_press_callback(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *self)
Definition rgbcurve.c:637
#define DT_IOP_RGBCURVE_RES
Definition rgbcurve.c:64
static __DT_CLONE_TARGETS__ void _generate_curve_lut(const dt_dev_pixelpipe_t *pipe, dt_iop_rgbcurve_data_t *d)
Definition rgbcurve.c:1454
#define DT_GUI_CURVE_EDITOR_INSET
Definition rgbcurve.c:63
static gboolean _area_leave_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition rgbcurve.c:685
#define DT_IOP_RGBCURVE_MIN_X_DISTANCE
Definition rgbcurve.c:66
static void picker_scale(const float *const in, float *out, dt_iop_rgbcurve_params_t *p, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition rgbcurve.c:302
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:1403
rgbcurve_channel_t
Definition rgbcurve.c:74
@ DT_IOP_RGBCURVE_MAX_CHANNELS
Definition rgbcurve.c:78
@ DT_IOP_RGBCURVE_G
Definition rgbcurve.c:76
@ DT_IOP_RGBCURVE_B
Definition rgbcurve.c:77
@ DT_IOP_RGBCURVE_R
Definition rgbcurve.c:75
static float _mouse_to_curve(const float x, const float zoom_factor, const float offset)
Definition rgbcurve.c:297
static int _add_node(dt_iop_rgbcurve_node_t *curve_nodes, int *nodes, float x, float y)
Definition rgbcurve.c:458
const char * name()
Definition rgbcurve.c:142
#define DT_IOP_COLOR_ICC_LEN
Definition rgbcurve.c:69
void gui_reset(struct dt_iop_module_t *self)
Definition rgbcurve.c:1248
static gboolean _sanity_check(const float x, const int selected, const int nodes, const dt_iop_rgbcurve_node_t *curve)
Definition rgbcurve.c:567
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition rgbcurve.c:1374
void change_image(struct dt_iop_module_t *self)
Definition rgbcurve.c:1263
float(* _coeffs_table_ptr)[3]
Definition rgbcurve.c:140
void gui_init(struct dt_iop_module_t *self)
Definition rgbcurve.c:1277
dt_iop_rgbcurve_autoscale_t
Definition rgbcurve.c:82
@ DT_S_SCALE_AUTOMATIC_RGB
Definition rgbcurve.c:83
@ DT_S_SCALE_MANUAL_RGB
Definition rgbcurve.c:84
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition rgbcurve.c:358
static gboolean _area_enter_notify_callback(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition rgbcurve.c:679
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:157
static void tab_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
Definition rgbcurve.c:436
int flags()
Definition rgbcurve.c:152
void gui_cleanup(struct dt_iop_module_t *self)
Definition rgbcurve.c:1392
static gboolean _is_identity(dt_iop_rgbcurve_params_t *p, rgbcurve_channel_t channel)
Definition rgbcurve.c:350
static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition rgbcurve.c:1116
void init_presets(dt_iop_module_so_t *self)
Definition rgbcurve.c:171
static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
Definition rgbcurve.c:614
#define RGBCURVE_DEFAULT_STEP
Definition rgbcurve.c:612
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:1427
static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
Definition rgbcurve.c:1035
float(* _curve_table_ptr)[0x10000]
Definition rgbcurve.c:139
static float _curve_to_mouse(const float x, const float zoom_factor, const float offset)
Definition rgbcurve.c:292
static int _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)
Definition rgbcurve.c:487
static gboolean _area_resized_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition rgbcurve.c:447
#define DT_IOP_RGBCURVE_MAXNODES
Definition rgbcurve.c:65
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition rgbcurve.c:519
static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
Definition rgbcurve.c:588
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float r
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_develop_t * develop
Definition darktable.h:770
PangoFontDescription * pango_font_desc
Definition bauhaus.h:274
GdkRGBA graph_colors[3]
Definition bauhaus.h:283
lib_colorpicker_sample_statistics scope
Definition colorpicker.h:72
dt_dev_request_flags_t request_histogram
struct dt_iop_module_t *void * data
struct dt_develop_t::@19 color_picker
Authoritative darkroom color-picker state.
GList * iop
Definition develop.h:279
GSList * samples
Definition develop.h:392
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
gint scroll_mask
Definition gtk.h:224
int32_t reset
Definition gtk.h:172
GModule *dt_dev_operation_t op
Definition imageop.h:230
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:264
dt_iop_params_t * default_params
Definition imageop.h:307
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
uint32_t histogram_max[4]
Definition imageop.h:280
dt_aligned_pixel_t picked_output_color
Definition imageop.h:274
gboolean enabled
Definition imageop.h:298
dt_aligned_pixel_t picked_color_min
Definition imageop.h:272
dt_aligned_pixel_t picked_color_max
Definition imageop.h:272
int histogram_middle_grey
Definition imageop.h:287
uint32_t * histogram
Definition imageop.h:276
guint timeout_handle
Definition imageop.h:371
dt_aligned_pixel_t picked_color
Definition imageop.h:272
dt_iop_params_t * params
Definition imageop.h:307
dt_colorspaces_color_profile_type_t type
Definition iop_profile.h:53
char filename[DT_IOP_COLOR_ICC_LEN]
Definition iop_profile.h:54
int curve_changed[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:134
dt_iop_rgbcurve_params_t params
Definition rgbcurve.c:130
dt_draw_curve_t * curve[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:131
float unbounded_coeffs[DT_IOP_RGBCURVE_MAX_CHANNELS][3]
Definition rgbcurve.c:133
char filename_work[512]
Definition rgbcurve.c:136
dt_colorspaces_color_profile_type_t type_work
Definition rgbcurve.c:135
float table[DT_IOP_RGBCURVE_MAX_CHANNELS][0x10000]
Definition rgbcurve.c:132
GtkDrawingArea * area
Definition rgbcurve.c:110
GtkWidget * cmb_preserve_colors
Definition rgbcurve.c:123
GtkWidget * chk_compensate_middle_grey
Definition rgbcurve.c:122
GtkWidget * colorpicker_set_values
Definition rgbcurve.c:114
int minmax_curve_type[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:108
dt_draw_curve_t * minmax_curve[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:106
rgbcurve_channel_t channel
Definition rgbcurve.c:116
GtkNotebook * channel_tabs
Definition rgbcurve.c:112
int minmax_curve_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:107
int curve_num_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:97
dt_iop_rgb_norms_t preserve_colors
Definition rgbcurve.c:101
int curve_type[DT_IOP_RGBCURVE_MAX_CHANNELS]
Definition rgbcurve.c:98
gboolean compensate_middle_grey
Definition rgbcurve.c:100
dt_iop_rgbcurve_autoscale_t curve_autoscale
Definition rgbcurve.c:99
dt_iop_rgbcurve_node_t curve_nodes[DT_IOP_RGBCURVE_MAX_CHANNELS][20]
Definition rgbcurve.c:96
Region of interest passed through the pixelpipe.
Definition imageop.h:72
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_TOGGLEBUTTON(obj)