Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
iop/tonecurve.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2013, 2016-2017 johannes hanika.
4 Copyright (C) 2010 Alexandre Prokoudine.
5 Copyright (C) 2010-2011 Bruce Guenter.
6 Copyright (C) 2010-2012 Henrik Andersson.
7 Copyright (C) 2010, 2012-2019 Tobias Ellinghaus.
8 Copyright (C) 2011 Brian Teague.
9 Copyright (C) 2011 Jochen Schroeder.
10 Copyright (C) 2011 Olivier Tribout.
11 Copyright (C) 2011-2012, 2015 Pascal de Bruijn.
12 Copyright (C) 2011 Robert Bieber.
13 Copyright (C) 2011 Rostyslav Pidgornyi.
14 Copyright (C) 2011-2017, 2019 Ulrich Pegelow.
15 Copyright (C) 2012 José Carlos García Sogo.
16 Copyright (C) 2012 Richard Wonka.
17 Copyright (C) 2013 Dennis Gnad.
18 Copyright (C) 2013-2015 Edouard Gomez.
19 Copyright (C) 2013 hal.
20 Copyright (C) 2013-2014, 2018-2022 Pascal Obry.
21 Copyright (C) 2013-2017 Roman Lebedev.
22 Copyright (C) 2013 Thomas Pryds.
23 Copyright (C) 2014, 2019 parafin.
24 Copyright (C) 2015 Pedro Côrte-Real.
25 Copyright (C) 2015, 2020-2021 Ralf Brown.
26 Copyright (C) 2015 Stefan Kauerauf.
27 Copyright (C) 2016 Asma.
28 Copyright (C) 2017-2018, 2020-2021 Dan Torop.
29 Copyright (C) 2017 Dominik Markiewicz.
30 Copyright (C) 2017, 2020 Heiko Bauke.
31 Copyright (C) 2018 Anders Bennehag.
32 Copyright (C) 2018-2019, 2022-2026 Aurélien PIERRE.
33 Copyright (C) 2018-2019 Edgardo Hoszowski.
34 Copyright (C) 2018 Lukas Schrangl.
35 Copyright (C) 2018 Maurizio Paglia.
36 Copyright (C) 2018 rawfiner.
37 Copyright (C) 2019 Andreas Schneider.
38 Copyright (C) 2019-2022 Diederik Ter Rahe.
39 Copyright (C) 2019 Diederik ter Rahe.
40 Copyright (C) 2019 emeikei.
41 Copyright (C) 2019 freetuz.
42 Copyright (C) 2019 luzpaz.
43 Copyright (C) 2019 Philippe Weyland.
44 Copyright (C) 2020 Aldric Renaudin.
45 Copyright (C) 2020 Chris Elston.
46 Copyright (C) 2020 GrahamByrnes.
47 Copyright (C) 2021 lhietal.
48 Copyright (C) 2021 Marco Carrarini.
49 Copyright (C) 2022 Hanno Schwalm.
50 Copyright (C) 2022 Martin Bařinka.
51 Copyright (C) 2022 Philipp Lutz.
52 Copyright (C) 2023 Luca Zulberti.
53
54 darktable is free software: you can redistribute it and/or modify
55 it under the terms of the GNU General Public License as published by
56 the Free Software Foundation, either version 3 of the License, or
57 (at your option) any later version.
58
59 darktable is distributed in the hope that it will be useful,
60 but WITHOUT ANY WARRANTY; without even the implied warranty of
61 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
62 GNU General Public License for more details.
63
64 You should have received a copy of the GNU General Public License
65 along with darktable. If not, see <http://www.gnu.org/licenses/>.
66*/
67#ifdef HAVE_CONFIG_H
68#include "common/darktable.h"
69#include "gui/gdkkeys.h"
70#include "config.h"
71#endif
72#include <assert.h>
73#include <math.h>
74#include <stdint.h>
75#include <stdlib.h>
76#include <string.h>
77
78#include "bauhaus/bauhaus.h"
79#include "common/opencl.h"
81#include "common/rgb_norms.h"
82#include "control/control.h"
83#include "develop/develop.h"
84#include "develop/imageop.h"
86#include "develop/imageop_gui.h"
87#include "dtgtk/drawingarea.h"
88#include "dtgtk/paint.h"
89#include "gui/draw.h"
90#include "gui/gtk.h"
91#include "gui/presets.h"
93
94#include "iop/iop_api.h"
95#include "libs/colorpicker.h"
96
97#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(1)
98#define DT_GUI_CURVE_INFL .3f
99
100#define DT_IOP_TONECURVE_RES 256
101#define DT_IOP_TONECURVE_MAXNODES 20
102
104
105static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data);
106static gboolean dt_iop_tonecurve_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data);
107static gboolean dt_iop_tonecurve_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
108static gboolean dt_iop_tonecurve_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data);
109static gboolean dt_iop_tonecurve_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data);
110static gboolean dt_iop_tonecurve_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
111
112
120/*
121typedef enum dt_iop_tonecurve_preservecolors_t
122{
123 DT_TONECURVE_PRESERVE_NONE = 0,
124 DT_TONECURVE_PRESERVE_LUMINANCE = 1,
125 DT_TONECURVE_PRESERVE_LMAX = 2,
126 DT_TONECURVE_PRESERVE_LAVG = 3,
127 DT_TONECURVE_PRESERVE_LSUM = 4,
128 DT_TONECURVE_PRESERVE_LNORM = 5,
129 DT_TONECURVE_PRESERVE_LBP = 6,
130} dt_iop_tonecurve_preservecolors_t;
131*/
132typedef struct dt_iop_tonecurve_node_t
133{
134 float x;
135 float y;
137
139{
140 DT_S_SCALE_AUTOMATIC = 1, /* automatically adjust saturation based on L_out/L_in
141 $DESCRIPTION: "Lab, linked channels" */
142 DT_S_SCALE_MANUAL = 0, /* user specified curves
143 $DESCRIPTION: "Lab, independent channels" */
144 DT_S_SCALE_AUTOMATIC_XYZ = 2, /* automatically adjust saturation by
145 transforming the curve C to C' like:
146 L_out=C(L_in) -> Y_out=C'(Y_in) and applying C' to the X and Z
147 channels, too (and then transforming it back to Lab of course)
148 $DESCRIPTION: "XYZ, linked channels" */
149 DT_S_SCALE_AUTOMATIC_RGB = 3, /* similar to above but use an rgb working space
150 $DESCRIPTION: "RGB, linked channels" */
152
153// parameter structure of tonecurve 1st version, needed for use in legacy_params()
159
160// parameter structure of tonecurve 3rd version, needed for use in legacy_params()
170
181
182typedef struct dt_iop_tonecurve_params_t
183{
184 dt_iop_tonecurve_node_t tonecurve[3][DT_IOP_TONECURVE_MAXNODES]; // three curves (L, a, b) with max number
185 // of nodes
186 int tonecurve_nodes[3];
187 int tonecurve_type[3]; // $DEFAULT: MONOTONE_HERMITE
188 dt_iop_tonecurve_autoscale_t tonecurve_autoscale_ab; //$DEFAULT: DT_S_SCALE_AUTOMATIC_RGB $DESCRIPTION: "color space"
189 int tonecurve_preset; // $DEFAULT: 0
190 int tonecurve_unbound_ab; // $DEFAULT: 1
191 dt_iop_rgb_norms_t preserve_colors; // $DEFAULT: DT_RGB_NORM_AVERAGE $DESCRIPTION: "preserve colors"
193
217
219{
220 dt_draw_curve_t *curve[3]; // curves for pipe piece and pixel processing
221 int curve_nodes[3]; // number of nodes
222 int curve_type[3]; // curve style (e.g. CUBIC_SPLINE)
223 float table[3][0x10000]; // precomputed look-up tables for tone curve
224 float unbounded_coeffs_L[3]; // approximation for extrapolation of L
225 float unbounded_coeffs_ab[12]; // approximation for extrapolation of ab (left and right)
230
238
239
240const char *name()
241{
242 return _("tone curve");
243}
244
246{
247 return IOP_GROUP_TONES;
248}
249
254
256{
257 return IOP_CS_LAB;
258}
259
260const char **description(struct dt_iop_module_t *self)
261{
262 return dt_iop_set_description(self, _("alter an image's tones using curves"),
263 _("corrective and creative"),
264 _("linear or non-linear, Lab, display-referred"),
265 _("non-linear, Lab"),
266 _("non-linear, Lab, display-referred"));
267}
268
269int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
270 void *new_params, const int new_version)
271{
272 if(old_version == 1 && new_version == 5)
273 {
276
277 // start with a fresh copy of default parameters
278 // unfortunately default_params aren't inited at this stage.
279 *n = (dt_iop_tonecurve_params_t){ { { { 0.0, 0.0 }, { 1.0, 1.0 } },
280 { { 0.0, 0.0 }, { 0.5, 0.5 }, { 1.0, 1.0 } },
281 { { 0.0, 0.0 }, { 0.5, 0.5 }, { 1.0, 1.0 } } },
282 { 2, 3, 3 },
284 1,
285 0,
286 1 };
287 for(int k = 0; k < 6; k++) n->tonecurve[ch_L][k].x = o->tonecurve_x[k];
288 for(int k = 0; k < 6; k++) n->tonecurve[ch_L][k].y = o->tonecurve_y[k];
289 n->tonecurve_nodes[ch_L] = 6;
290 n->tonecurve_type[ch_L] = CUBIC_SPLINE;
291 n->tonecurve_autoscale_ab = 1;
292 n->tonecurve_preset = o->tonecurve_preset;
293 n->tonecurve_unbound_ab = 0;
294 n->preserve_colors = 0;
295 return 0;
296 }
297 else if(old_version == 2 && new_version == 5)
298 {
299 // version 2 never really materialized so there should be no legacy history stacks of that version around
300 return 1;
301 }
302 else if(old_version == 3 && new_version == 5)
303 {
306
307 memcpy(n->tonecurve, o->tonecurve, sizeof(n->tonecurve));
308 memcpy(n->tonecurve_nodes, o->tonecurve_nodes, sizeof(n->tonecurve_nodes));
309 memcpy(n->tonecurve_type, o->tonecurve_type, sizeof(n->tonecurve_type));
310 n->tonecurve_autoscale_ab = o->tonecurve_autoscale_ab;
311 n->tonecurve_preset = o->tonecurve_preset;
312 n->tonecurve_unbound_ab = 0;
313 n->preserve_colors = 0;
314 return 0;
315 }
316 else if(old_version == 4 && new_version == 5)
317 {
320
321 memcpy(n->tonecurve, o->tonecurve, sizeof(dt_iop_tonecurve_params4_t));
322 n->preserve_colors = 0;
323 return 0;
324 }
325 return 1;
326}
327
329int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const i, void *const o)
330{
331 (void)pipe;
332 const dt_iop_roi_t *const roi_out = &piece->roi_out;
333
334 const dt_iop_tonecurve_data_t *const restrict d = (dt_iop_tonecurve_data_t *)(piece->data);
335 const dt_iop_order_iccprofile_info_t *const work_profile
337 const float xm_L = 1.0f / d->unbounded_coeffs_L[0];
338 const float xm_ar = 1.0f / d->unbounded_coeffs_ab[0];
339 const float xm_al = 1.0f - 1.0f / d->unbounded_coeffs_ab[3];
340 const float xm_br = 1.0f / d->unbounded_coeffs_ab[6];
341 const float xm_bl = 1.0f - 1.0f / d->unbounded_coeffs_ab[9];
342 const float low_approximation = d->table[0][(int)(0.01f * 0x10000ul)];
343
344 const size_t npixels = (size_t)roi_out->width * roi_out->height;
345 const int autoscale_ab = d->autoscale_ab;
346 const int unbound_ab = d->unbound_ab;
347
348 const float *const restrict in = (float*)i;
349 float *const restrict out = (float*)o;
351 for(int k = 0; k < 4*npixels; k += 4)
352 {
353 const float L_in = in[k] / 100.0f;
354
355 out[k+0] = (L_in < xm_L) ? d->table[ch_L][CLAMP((int)(L_in * 0x10000ul), 0, 0xffff)]
356 : dt_iop_eval_exp(d->unbounded_coeffs_L, L_in);
357
358 if(autoscale_ab == DT_S_SCALE_MANUAL)
359 {
360 const float a_in = (in[k+1] + 128.0f) / 256.0f;
361 const float b_in = (in[k+2] + 128.0f) / 256.0f;
362
363 if(unbound_ab == 0)
364 {
365 // old style handling of a/b curves: only lut lookup with clamping
366 out[k+1] = d->table[ch_a][CLAMP((int)(a_in * 0x10000ul), 0, 0xffff)];
367 out[k+2] = d->table[ch_b][CLAMP((int)(b_in * 0x10000ul), 0, 0xffff)];
368 }
369 else
370 {
371 // new style handling of a/b curves: lut lookup with two-sided extrapolation;
372 // mind the x-axis reversal for the left-handed side
373 out[k+1] = (a_in > xm_ar)
374 ? dt_iop_eval_exp(d->unbounded_coeffs_ab, a_in)
375 : ((a_in < xm_al) ? dt_iop_eval_exp(d->unbounded_coeffs_ab + 3, 1.0f - a_in)
376 : d->table[ch_a][CLAMP((int)(a_in * 0x10000ul), 0, 0xffff)]);
377 out[k+2] = (b_in > xm_br)
378 ? dt_iop_eval_exp(d->unbounded_coeffs_ab + 6, b_in)
379 : ((b_in < xm_bl) ? dt_iop_eval_exp(d->unbounded_coeffs_ab + 9, 1.0f - b_in)
380 : d->table[ch_b][CLAMP((int)(b_in * 0x10000ul), 0, 0xffff)]);
381 }
382 }
383 else if(autoscale_ab == DT_S_SCALE_AUTOMATIC)
384 {
385 // in Lab: correct compressed Luminance for saturation:
386 if(L_in > 0.01f)
387 {
388 out[k+1] = in[k+1] * out[k] / in[k+0];
389 out[k+2] = in[k+2] * out[k] / in[k+0];
390 }
391 else
392 {
393 out[k+1] = in[k+1] * low_approximation;
394 out[k+2] = in[k+2] * low_approximation;
395 }
396 }
397 else if(autoscale_ab == DT_S_SCALE_AUTOMATIC_XYZ)
398 {
400 dt_Lab_to_XYZ(in + k, XYZ);
401 for(int c=0;c<3;c++)
402 XYZ[c] = (XYZ[c] < xm_L) ? d->table[ch_L][CLAMP((int)(XYZ[c] * 0x10000ul), 0, 0xffff)]
403 : dt_iop_eval_exp(d->unbounded_coeffs_L, XYZ[c]);
405 }
406 else if(autoscale_ab == DT_S_SCALE_AUTOMATIC_RGB)
407 {
408 dt_aligned_pixel_t rgb = {0, 0, 0};
409 dt_Lab_to_prophotorgb(in + k, rgb);
410 if(d->preserve_colors == DT_RGB_NORM_NONE)
411 {
412 for(int c = 0; c < 3; c++)
413 {
414 rgb[c] = (rgb[c] < xm_L) ? d->table[ch_L][CLAMP((int)(rgb[c] * 0x10000ul), 0, 0xffff)]
415 : dt_iop_eval_exp(d->unbounded_coeffs_L, rgb[c]);
416 }
417 }
418 else
419 {
420 float ratio = 1.f;
421 const float lum = dt_rgb_norm(rgb, d->preserve_colors, work_profile);
422 if(lum > 0.f)
423 {
424 const float curve_lum = (lum < xm_L)
425 ? d->table[ch_L][CLAMP((int)(lum * 0x10000ul), 0, 0xffff)]
426 : dt_iop_eval_exp(d->unbounded_coeffs_L, lum);
427 ratio = curve_lum / lum;
428 }
429 for(size_t c = 0; c < 3; c++)
430 {
431 rgb[c] = (ratio * rgb[c]);
432 }
433 }
434 dt_prophotorgb_to_Lab(rgb, out + k);
435 }
436
437 out[k+3] = in[k+3];
438 }
439
440 return 0;
441}
442
443static const struct
444{
445 const char *name;
446 const char *maker;
447 const char *model;
449 float iso_max;
452 // This is where you can paste the line provided by dt-curve-tool
453 // Here is a valid example for you to compare
454 // clang-format off
455 // nikon d750 by Edouard Gomez
456 {"Nikon D750", "NIKON CORPORATION", "NIKON D750", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.083508, 0.073677}, {0.212191, 0.274799}, {0.397095, 0.594035}, {0.495025, 0.714660}, {0.683565, 0.878550}, {0.854059, 0.950927}, {1.000000, 1.000000}}, {{0.000000, 0.000000}, {0.125000, 0.125000}, {0.250000, 0.250000}, {0.375000, 0.375000}, {0.500000, 0.500000}, {0.625000, 0.625000}, {0.750000, 0.750000}, {0.875000, 0.875000}}, {{0.000000, 0.000000}, {0.125000, 0.125000}, {0.250000, 0.250000}, {0.375000, 0.375000}, {0.500000, 0.500000}, {0.625000, 0.625000}, {0.750000, 0.750000}, {0.875000, 0.875000} }}, {8, 8, 8}, {2, 2, 2}, 1, 0, 0}},
457 // nikon d5100 contributed by Stefan Kauerauf
458 {"NIKON D5100", "NIKON CORPORATION", "NIKON D5100", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.000957, 0.000176}, {0.002423, 0.000798}, {0.005893, 0.003685}, {0.013219, 0.006619}, {0.023372, 0.011954}, {0.037580, 0.017817}, {0.069695, 0.035353}, {0.077276, 0.040315}, {0.123707, 0.082707}, {0.145249, 0.112105}, {0.189168, 0.186135}, {0.219576, 0.243677}, {0.290201, 0.385251}, {0.428150, 0.613355}, {0.506199, 0.700256}, {0.622833, 0.805488}, {0.702763, 0.870959}, {0.935053, 0.990139}, {1.000000, 1.000000}}, {{0.000000, 0.000000}, {0.050000, 0.050000}, {0.100000, 0.100000}, {0.150000, 0.150000}, {0.200000, 0.200000}, {0.250000, 0.250000}, {0.300000, 0.300000}, {0.350000, 0.350000}, {0.400000, 0.400000}, {0.450000, 0.450000}, {0.500000, 0.500000}, {0.550000, 0.550000}, {0.600000, 0.600000}, {0.650000, 0.650000}, {0.700000, 0.700000}, {0.750000, 0.750000}, {0.800000, 0.800000}, {0.850000, 0.850000}, {0.900000, 0.900000}, {0.950000, 0.950000}}, {{0.000000, 0.000000}, {0.050000, 0.050000}, {0.100000, 0.100000}, {0.150000, 0.150000}, {0.200000, 0.200000}, {0.250000, 0.250000}, {0.300000, 0.300000}, {0.350000, 0.350000}, {0.400000, 0.400000}, {0.450000, 0.450000}, {0.500000, 0.500000}, {0.550000, 0.550000}, {0.600000, 0.600000}, {0.650000, 0.650000}, {0.700000, 0.700000}, {0.750000, 0.750000}, {0.800000, 0.800000}, {0.850000, 0.850000}, {0.900000, 0.900000}, {0.950000, 0.950000}}}, {20, 20, 20}, {2, 2, 2}, 1, 0, 0}},
459 // nikon d7000 by Edouard Gomez
460 {"Nikon D7000", "NIKON CORPORATION", "NIKON D7000", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.110633, 0.111192}, {0.209771, 0.286963}, {0.355888, 0.561236}, {0.454987, 0.673098}, {0.769212, 0.920485}, {0.800468, 0.933428}, {1.000000, 1.000000}}, {{0.000000, 0.000000}, {0.125000, 0.125000}, {0.250000, 0.250000}, {0.375000, 0.375000}, {0.500000, 0.500000}, {0.625000, 0.625000}, {0.750000, 0.750000}, {0.875000, 0.875000}}, {{0.000000, 0.000000}, {0.125000, 0.125000}, {0.250000, 0.250000}, {0.375000, 0.375000}, {0.500000, 0.500000}, {0.625000, 0.625000}, {0.750000, 0.750000}, {0.875000, 0.875000}}}, {8, 8, 8}, {2, 2, 2}, 1, 0, 0}},
461 // nikon d7200 standard by Ralf Brown (firmware 1.00)
462 {"Nikon D7200", "NIKON CORPORATION", "NIKON D7200", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.000618, 0.003286}, {0.001639, 0.003705}, {0.005227, 0.005101}, {0.013299, 0.011192}, {0.016048, 0.013130}, {0.037941, 0.027014}, {0.058195, 0.041339}, {0.086531, 0.069088}, {0.116679, 0.107283}, {0.155629, 0.159422}, {0.205477, 0.246265}, {0.225923, 0.287343}, {0.348056, 0.509104}, {0.360629, 0.534732}, {0.507562, 0.762089}, {0.606899, 0.865692}, {0.734828, 0.947468}, {0.895488, 0.992021}, {1.000000, 1.000000}}, {{0.000000, 0.000000}, {0.050000, 0.050000}, {0.100000, 0.100000}, {0.150000, 0.150000}, {0.200000, 0.200000}, {0.250000, 0.250000}, {0.300000, 0.300000}, {0.350000, 0.350000}, {0.400000, 0.400000}, {0.450000, 0.450000}, {0.500000, 0.500000}, {0.550000, 0.550000}, {0.600000, 0.600000}, {0.650000, 0.650000}, {0.700000, 0.700000}, {0.750000, 0.750000}, {0.800000, 0.800000}, {0.850000, 0.850000}, {0.900000, 0.900000}, {0.950000, 0.950000}}, {{0.000000, 0.000000}, {0.050000, 0.050000}, {0.100000, 0.100000}, {0.150000, 0.150000}, {0.200000, 0.200000}, {0.250000, 0.250000}, {0.300000, 0.300000}, {0.350000, 0.350000}, {0.400000, 0.400000}, {0.450000, 0.450000}, {0.500000, 0.500000}, {0.550000, 0.550000}, {0.600000, 0.600000}, {0.650000, 0.650000}, {0.700000, 0.700000}, {0.750000, 0.750000}, {0.800000, 0.800000}, {0.850000, 0.850000}, {0.900000, 0.900000}, {0.950000, 0.950000}}}, {20, 20, 20}, {2, 2, 2}, 1, 0, 0}},
463 // nikon d7500 by Anders Bennehag (firmware C 1.00, LD 2.016)
464 {"NIKON D7500", "NIKON CORPORATION", "NIKON D7500", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.000421, 0.003412}, {0.003775, 0.004001}, {0.013762, 0.008704}, {0.016698, 0.010230}, {0.034965, 0.018732}, {0.087311, 0.049808}, {0.101389, 0.060789}, {0.166845, 0.145269}, {0.230944, 0.271288}, {0.333399, 0.502609}, {0.353207, 0.542549}, {0.550014, 0.819535}, {0.731749, 0.944033}, {0.783283, 0.960546}, {1.000000, 1.000000}}, {{0.000000, 0.000000}, {0.062500, 0.062500}, {0.125000, 0.125000}, {0.187500, 0.187500}, {0.250000, 0.250000}, {0.312500, 0.312500}, {0.375000, 0.375000}, {0.437500, 0.437500}, {0.500000, 0.500000}, {0.562500, 0.562500}, {0.625000, 0.625000}, {0.687500, 0.687500}, {0.750000, 0.750000}, {0.812500, 0.812500}, {0.875000, 0.875000}, {0.937500, 0.937500}}, {{0.000000, 0.000000}, {0.062500, 0.062500}, {0.125000, 0.125000}, {0.187500, 0.187500}, {0.250000, 0.250000}, {0.312500, 0.312500}, {0.375000, 0.375000}, {0.437500, 0.437500}, {0.500000, 0.500000}, {0.562500, 0.562500}, {0.625000, 0.625000}, {0.687500, 0.687500}, {0.750000, 0.750000}, {0.812500, 0.812500}, {0.875000, 0.875000}, {0.937500, 0.937500}}}, {16, 16, 16}, {2, 2, 2}, 1, 0, 0}},
465 // nikon d90 by Edouard Gomez
466 {"Nikon D90", "NIKON CORPORATION", "NIKON D90", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.002915, 0.006453}, {0.023324, 0.021601}, {0.078717, 0.074963}, {0.186589, 0.242230}, {0.364432, 0.544956}, {0.629738, 0.814127}, {1.000000, 1.000000}}, {{0.000000, 0.000000}, {0.125000, 0.125000}, {0.250000, 0.250000}, {0.375000, 0.375000}, {0.500000, 0.500000}, {0.625000, 0.625000}, {0.750000, 0.750000}, {0.875000, 0.875000}}, {{0.000000, 0.000000}, {0.125000, 0.125000}, {0.250000, 0.250000}, {0.375000, 0.375000}, {0.500000, 0.500000}, {0.625000, 0.625000}, {0.750000, 0.750000}, {0.875000, 0.875000}}}, {8, 8, 8}, {2, 2, 2}, 1, 0, 0}},
467 // Olympus OM-D E-M10 II by Lukas Schrangl
468 {"Olympus OM-D E-M10 II", "OLYMPUS CORPORATION ", "E-M10MarkII ", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.004036, 0.000809}, {0.015047, 0.009425}, {0.051948, 0.042053}, {0.071777, 0.066635}, {0.090018, 0.086722}, {0.110197, 0.118773}, {0.145817, 0.171861}, {0.207476, 0.278652}, {0.266832, 0.402823}, {0.428061, 0.696319}, {0.559728, 0.847113}, {0.943576, 0.993482}, {1.000000, 1.000000}}, {{0.000000, 0.000000}, {0.071429, 0.071429}, {0.142857, 0.142857}, {0.214286, 0.214286}, {0.285714, 0.285714}, {0.357143, 0.357143}, {0.428571, 0.428571}, {0.500000, 0.500000}, {0.571429, 0.571429}, {0.642857, 0.642857}, {0.714286, 0.714286}, {0.785714, 0.785714}, {0.857143, 0.857143}, {0.928571, 0.928571}}, {{0.000000, 0.000000}, {0.071429, 0.071429}, {0.142857, 0.142857}, {0.214286, 0.214286}, {0.285714, 0.285714}, {0.357143, 0.357143}, {0.428571, 0.428571}, {0.500000, 0.500000}, {0.571429, 0.571429}, {0.642857, 0.642857}, {0.714286, 0.714286}, {0.785714, 0.785714}, {0.857143, 0.857143}, {0.928571, 0.928571}}}, {14, 14, 14}, {2, 2, 2}, 1, 0, 0}},
469 // clang-format on
471
473{
475 memset(&p, 0, sizeof(p));
476 p.tonecurve_nodes[ch_L] = 6;
477 p.tonecurve_nodes[ch_a] = 7;
478 p.tonecurve_nodes[ch_b] = 7;
479 p.tonecurve_type[ch_L] = CUBIC_SPLINE;
480 p.tonecurve_type[ch_a] = CUBIC_SPLINE;
481 p.tonecurve_type[ch_b] = CUBIC_SPLINE;
482 p.tonecurve_preset = 0;
483 p.tonecurve_autoscale_ab = DT_S_SCALE_AUTOMATIC_RGB;
484 p.tonecurve_unbound_ab = 1;
485
486 float linear_ab[7] = { 0.0, 0.08, 0.3, 0.5, 0.7, 0.92, 1.0 };
487
488 // linear a, b curves for presets
489 for(int k = 0; k < 7; k++) p.tonecurve[ch_a][k].x = linear_ab[k];
490 for(int k = 0; k < 7; k++) p.tonecurve[ch_a][k].y = linear_ab[k];
491 for(int k = 0; k < 7; k++) p.tonecurve[ch_b][k].x = linear_ab[k];
492 for(int k = 0; k < 7; k++) p.tonecurve[ch_b][k].y = linear_ab[k];
493
494 // More useful low contrast curve (based on Samsung NX -2 Contrast)
495 p.tonecurve[ch_L][0].x = 0.000000;
496 p.tonecurve[ch_L][1].x = 0.003862;
497 p.tonecurve[ch_L][2].x = 0.076613;
498 p.tonecurve[ch_L][3].x = 0.169355;
499 p.tonecurve[ch_L][4].x = 0.774194;
500 p.tonecurve[ch_L][5].x = 1.000000;
501 p.tonecurve[ch_L][0].y = 0.000000;
502 p.tonecurve[ch_L][1].y = 0.007782;
503 p.tonecurve[ch_L][2].y = 0.156182;
504 p.tonecurve[ch_L][3].y = 0.290352;
505 p.tonecurve[ch_L][4].y = 0.773852;
506 p.tonecurve[ch_L][5].y = 1.000000;
507 dt_gui_presets_add_generic(_("contrast compression"), self->op,
508 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
509
510 p.tonecurve_nodes[ch_L] = 7;
511 float linear_L[7] = { 0.0, 0.08, 0.17, 0.50, 0.83, 0.92, 1.0 };
512
513 // Linear - no contrast
514 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
515 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
516 dt_gui_presets_add_generic(_("gamma 1.0 (linear)"), self->op,
517 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
518
519 // Linear contrast
520 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
521 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
522 p.tonecurve[ch_L][1].y -= 0.020;
523 p.tonecurve[ch_L][2].y -= 0.030;
524 p.tonecurve[ch_L][4].y += 0.030;
525 p.tonecurve[ch_L][5].y += 0.020;
526 dt_gui_presets_add_generic(_("contrast - med (linear)"), self->op,
527 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
528
529 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
530 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
531 p.tonecurve[ch_L][1].y -= 0.040;
532 p.tonecurve[ch_L][2].y -= 0.060;
533 p.tonecurve[ch_L][4].y += 0.060;
534 p.tonecurve[ch_L][5].y += 0.040;
535 dt_gui_presets_add_generic(_("contrast - high (linear)"), self->op,
536 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
537
538 // Gamma contrast
539 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
540 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
541 p.tonecurve[ch_L][1].y -= 0.020;
542 p.tonecurve[ch_L][2].y -= 0.030;
543 p.tonecurve[ch_L][4].y += 0.030;
544 p.tonecurve[ch_L][5].y += 0.020;
545 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].x = powf(p.tonecurve[ch_L][k].x, 2.2f);
546 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(p.tonecurve[ch_L][k].y, 2.2f);
547 dt_gui_presets_add_generic(_("contrast - med (gamma 2.2)"), self->op,
548 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
549
550 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
551 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
552 p.tonecurve[ch_L][1].y -= 0.040;
553 p.tonecurve[ch_L][2].y -= 0.060;
554 p.tonecurve[ch_L][4].y += 0.060;
555 p.tonecurve[ch_L][5].y += 0.040;
556 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].x = powf(p.tonecurve[ch_L][k].x, 2.2f);
557 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(p.tonecurve[ch_L][k].y, 2.2f);
558 dt_gui_presets_add_generic(_("contrast - high (gamma 2.2)"), self->op,
559 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
560
563 p.tonecurve_type[ch_L] = MONOTONE_HERMITE;
564
565 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
566 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
567
568 // Gamma 2.0 - no contrast
569 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(linear_L[k], 2.0f);
570 dt_gui_presets_add_generic(_("gamma 2.0"), self->op,
571 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
572
573 // Gamma 0.5 - no contrast
574 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(linear_L[k], 0.5f);
575 dt_gui_presets_add_generic(_("gamma 0.5"), self->op,
576 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
577
578 // Log2 - no contrast
579 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = logf(linear_L[k] + 1.0f) / logf(2.0f);
580 dt_gui_presets_add_generic(_("logarithm (base 2)"), self->op,
581 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
582
583 // Exp2 - no contrast
584 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(2.0f, linear_L[k]) - 1.0f;
585 dt_gui_presets_add_generic(_("exponential (base 2)"), self->op,
586 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
587
588 for (int k=0; k<sizeof(preset_camera_curves)/sizeof(preset_camera_curves[0]); k++)
589 {
590 // insert the preset
593
594 // restrict it to model, maker
595 dt_gui_presets_update_mml(preset_camera_curves[k].name, self->op, self->version(),
596 preset_camera_curves[k].maker, preset_camera_curves[k].model, "");
597
598 // restrict it to iso
599 dt_gui_presets_update_iso(preset_camera_curves[k].name, self->op, self->version(),
600 preset_camera_curves[k].iso_min, preset_camera_curves[k].iso_max);
601
602 // restrict it to raw images
604
605 // hide all non-matching presets in case the model string is set.
606 dt_gui_presets_update_filter(preset_camera_curves[k].name, self->op, self->version(), 1);
607 }
608}
609
612{
615
616 if(pipe->type == DT_DEV_PIXELPIPE_PREVIEW)
618 else
619 piece->request_histogram &= ~(DT_REQUEST_ON);
620
621 for(int ch = 0; ch < ch_max; ch++)
622 {
623 // take care of possible change of curve type or number of nodes (not yet implemented in UI)
624 if(d->curve_type[ch] != p->tonecurve_type[ch] || d->curve_nodes[ch] != p->tonecurve_nodes[ch])
625 {
626 dt_draw_curve_destroy(d->curve[ch]);
627 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, p->tonecurve_type[ch]);
628 d->curve_nodes[ch] = p->tonecurve_nodes[ch];
629 d->curve_type[ch] = p->tonecurve_type[ch];
630 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
631 (void)dt_draw_curve_add_point(d->curve[ch], p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
632 }
633 else
634 {
635 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
636 dt_draw_curve_set_point(d->curve[ch], k, p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
637 }
638 dt_draw_curve_calc_values(d->curve[ch], 0.0f, 1.0f, 0x10000, NULL, d->table[ch]);
639 }
640 for(int k = 0; k < 0x10000; k++) d->table[ch_L][k] *= 100.0f;
641 for(int k = 0; k < 0x10000; k++) d->table[ch_a][k] = d->table[ch_a][k] * 256.0f - 128.0f;
642 for(int k = 0; k < 0x10000; k++) d->table[ch_b][k] = d->table[ch_b][k] * 256.0f - 128.0f;
643
644 piece->process_cl_ready = 1;
645 if(p->tonecurve_autoscale_ab == DT_S_SCALE_AUTOMATIC_XYZ)
646 {
647 // derive curve for XYZ:
648 for(int k=0;k<0x10000;k++)
649 {
650 dt_aligned_pixel_t XYZ = {k/(float)0x10000, k/(float)0x10000, k/(float)0x10000};
651 dt_aligned_pixel_t Lab = {0.0};
653 Lab[0] = d->table[ch_L][CLAMP((int)(Lab[0]/100.0f * 0x10000), 0, 0xffff)];
655 d->table[ch_L][k] = XYZ[1]; // now mapping Y_in to Y_out
656 }
657 }
658 else if(p->tonecurve_autoscale_ab == DT_S_SCALE_AUTOMATIC_RGB)
659 {
660 // derive curve for rgb:
661 for(int k=0;k<0x10000;k++)
662 {
663 dt_aligned_pixel_t rgb = {k/(float)0x10000, k/(float)0x10000, k/(float)0x10000};
664 dt_aligned_pixel_t Lab = {0.0};
665 dt_prophotorgb_to_Lab(rgb, Lab);
666 Lab[0] = d->table[ch_L][CLAMP((int)(Lab[0]/100.0f * 0x10000), 0, 0xffff)];
667 dt_Lab_to_prophotorgb(Lab, rgb);
668 d->table[ch_L][k] = rgb[1]; // now mapping G_in to G_out
669 }
670 }
671
672 d->autoscale_ab = p->tonecurve_autoscale_ab;
673 d->unbound_ab = p->tonecurve_unbound_ab;
674 d->preserve_colors = p->preserve_colors;
675
676 // extrapolation for L-curve (right hand side only):
677 const float xm_L = p->tonecurve[ch_L][p->tonecurve_nodes[ch_L] - 1].x;
678 const float x_L[4] = { 0.7f * xm_L, 0.8f * xm_L, 0.9f * xm_L, 1.0f * xm_L };
679 const float y_L[4] = { d->table[ch_L][CLAMP((int)(x_L[0] * 0x10000ul), 0, 0xffff)],
680 d->table[ch_L][CLAMP((int)(x_L[1] * 0x10000ul), 0, 0xffff)],
681 d->table[ch_L][CLAMP((int)(x_L[2] * 0x10000ul), 0, 0xffff)],
682 d->table[ch_L][CLAMP((int)(x_L[3] * 0x10000ul), 0, 0xffff)] };
683 dt_iop_estimate_exp(x_L, y_L, 4, d->unbounded_coeffs_L);
684
685 // extrapolation for a-curve right side:
686 const float xm_ar = p->tonecurve[ch_a][p->tonecurve_nodes[ch_a] - 1].x;
687 const float x_ar[4] = { 0.7f * xm_ar, 0.8f * xm_ar, 0.9f * xm_ar, 1.0f * xm_ar };
688 const float y_ar[4] = { d->table[ch_a][CLAMP((int)(x_ar[0] * 0x10000ul), 0, 0xffff)],
689 d->table[ch_a][CLAMP((int)(x_ar[1] * 0x10000ul), 0, 0xffff)],
690 d->table[ch_a][CLAMP((int)(x_ar[2] * 0x10000ul), 0, 0xffff)],
691 d->table[ch_a][CLAMP((int)(x_ar[3] * 0x10000ul), 0, 0xffff)] };
692 dt_iop_estimate_exp(x_ar, y_ar, 4, d->unbounded_coeffs_ab);
693
694 // extrapolation for a-curve left side (we need to mirror the x-axis):
695 const float xm_al = 1.0f - p->tonecurve[ch_a][0].x;
696 const float x_al[4] = { 0.7f * xm_al, 0.8f * xm_al, 0.9f * xm_al, 1.0f * xm_al };
697 const float y_al[4] = { d->table[ch_a][CLAMP((int)((1.0f - x_al[0]) * 0x10000ul), 0, 0xffff)],
698 d->table[ch_a][CLAMP((int)((1.0f - x_al[1]) * 0x10000ul), 0, 0xffff)],
699 d->table[ch_a][CLAMP((int)((1.0f - x_al[2]) * 0x10000ul), 0, 0xffff)],
700 d->table[ch_a][CLAMP((int)((1.0f - x_al[3]) * 0x10000ul), 0, 0xffff)] };
701 dt_iop_estimate_exp(x_al, y_al, 4, d->unbounded_coeffs_ab + 3);
702
703 // extrapolation for b-curve right side:
704 const float xm_br = p->tonecurve[ch_b][p->tonecurve_nodes[ch_b] - 1].x;
705 const float x_br[4] = { 0.7f * xm_br, 0.8f * xm_br, 0.9f * xm_br, 1.0f * xm_br };
706 const float y_br[4] = { d->table[ch_b][CLAMP((int)(x_br[0] * 0x10000ul), 0, 0xffff)],
707 d->table[ch_b][CLAMP((int)(x_br[1] * 0x10000ul), 0, 0xffff)],
708 d->table[ch_b][CLAMP((int)(x_br[2] * 0x10000ul), 0, 0xffff)],
709 d->table[ch_b][CLAMP((int)(x_br[3] * 0x10000ul), 0, 0xffff)] };
710 dt_iop_estimate_exp(x_br, y_br, 4, d->unbounded_coeffs_ab + 6);
711
712 // extrapolation for b-curve left side (we need to mirror the x-axis):
713 const float xm_bl = 1.0f - p->tonecurve[ch_b][0].x;
714 const float x_bl[4] = { 0.7f * xm_bl, 0.8f * xm_bl, 0.9f * xm_bl, 1.0f * xm_bl };
715 const float y_bl[4] = { d->table[ch_b][CLAMP((int)((1.0f - x_bl[0]) * 0x10000ul), 0, 0xffff)],
716 d->table[ch_b][CLAMP((int)((1.0f - x_bl[1]) * 0x10000ul), 0, 0xffff)],
717 d->table[ch_b][CLAMP((int)((1.0f - x_bl[2]) * 0x10000ul), 0, 0xffff)],
718 d->table[ch_b][CLAMP((int)((1.0f - x_bl[3]) * 0x10000ul), 0, 0xffff)] };
719 dt_iop_estimate_exp(x_bl, y_bl, 4, d->unbounded_coeffs_ab + 9);
720}
721
722static float eval_grey(float x)
723{
724 // "log base" is a combined scaling and offset change so that x->[0,1] with
725 // the left side of the histogram expanded (slider->right) or not (slider left, linear)
726 return x;
727}
728
730{
731 // create part of the pixelpipe
734 piece->data = (void *)d;
735 piece->data_size = sizeof(dt_iop_tonecurve_data_t);
736 d->autoscale_ab = DT_S_SCALE_AUTOMATIC;
737 d->unbound_ab = 1;
738 for(int ch = 0; ch < ch_max; ch++)
739 {
740 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, default_params->tonecurve_type[ch]);
741 d->curve_nodes[ch] = default_params->tonecurve_nodes[ch];
742 d->curve_type[ch] = default_params->tonecurve_type[ch];
743 for(int k = 0; k < default_params->tonecurve_nodes[ch]; k++)
744 (void)dt_draw_curve_add_point(d->curve[ch], default_params->tonecurve[ch][k].x,
745 default_params->tonecurve[ch][k].y);
746 }
747 for(int k = 0; k < 0x10000; k++) d->table[ch_L][k] = 100.0f * k / 0x10000; // identity for L
748 for(int k = 0; k < 0x10000; k++) d->table[ch_a][k] = 256.0f * k / 0x10000 - 128.0f; // identity for a
749 for(int k = 0; k < 0x10000; k++) d->table[ch_b][k] = 256.0f * k / 0x10000 - 128.0f; // identity for b
750}
751
753{
754 // clean up everything again.
756 for(int ch = 0; ch < ch_max; ch++) dt_draw_curve_destroy(d->curve[ch]);
757 dt_free_align(piece->data);
758 piece->data = NULL;
759}
760
761void gui_reset(struct dt_iop_module_t *self)
762{
765 dt_bauhaus_combobox_set(g->interpolator, p->tonecurve_type[ch_L]);
766 dt_bauhaus_combobox_set(g->preserve_colors, p->preserve_colors);
767 dt_bauhaus_slider_set(g->logbase, 0);
768 g->loglogscale = 0;
769 g->semilog = 0;
770
771 g->channel = (tonecurve_channel_t)ch_L;
772 gtk_widget_queue_draw(self->widget);
773}
774
775void gui_update(struct dt_iop_module_t *self)
776{
779
780 gui_changed(self, g->autoscale_ab, 0);
781
782 dt_bauhaus_combobox_set(g->interpolator, p->tonecurve_type[ch_L]);
783 g->loglogscale = eval_grey(dt_bauhaus_slider_get(g->logbase));
784 // that's all, gui curve is read directly from params during expose event.
785 gtk_widget_queue_draw(self->widget);
786}
787
789{
790 dt_iop_default_init(module);
791
792 module->request_histogram |= (DT_REQUEST_ON);
793
794 dt_iop_tonecurve_params_t *d = module->default_params;
795
796 d->tonecurve_nodes[0] = 2;
797 d->tonecurve_nodes[1] =
798 d->tonecurve_nodes[2] = 3;
799 d->tonecurve[0][1].x = d->tonecurve[0][1].y =
800 d->tonecurve[1][2].x = d->tonecurve[1][2].y =
801 d->tonecurve[2][2].x = d->tonecurve[2][2].y = 1.0f;
802 d->tonecurve[1][1].x = d->tonecurve[1][1].y =
803 d->tonecurve[2][1].x = d->tonecurve[2][1].y = 0.5f;
804}
805
807{
810 module->data = gd;
811 for(int k=0; k<3; k++)
812 {
813 gd->picked_color[k] = .0f;
814 gd->picked_color_min[k] = .0f;
815 gd->picked_color_max[k] = .0f;
816 gd->picked_output_color[k] = .0f;
817 }
818}
819
821{
822 dt_free(module->data);
823}
824
825static void logbase_callback(GtkWidget *slider, dt_iop_module_t *self)
826{
827 if(darktable.gui->reset) return;
829 g->loglogscale = eval_grey(dt_bauhaus_slider_get(g->logbase));
830 gtk_widget_queue_draw(GTK_WIDGET(g->area));
831}
832
833void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
834{
837
838 if(w == g->autoscale_ab)
839 {
840 g->channel = (tonecurve_channel_t)ch_L;
841 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->channel_tabs), ch_L);
842
843 gtk_notebook_set_show_tabs(g->channel_tabs, p->tonecurve_autoscale_ab == DT_S_SCALE_MANUAL);
844 gtk_widget_set_visible(g->preserve_colors, p->tonecurve_autoscale_ab == DT_S_SCALE_AUTOMATIC_RGB);
845
846 gtk_widget_queue_draw(self->widget);
847 }
848}
849
851{
852 if(darktable.gui->reset) return;
855 const int combo = dt_bauhaus_combobox_get(widget);
856 if(combo == 0) p->tonecurve_type[ch_L] = p->tonecurve_type[ch_a] = p->tonecurve_type[ch_b] = CUBIC_SPLINE;
857 if(combo == 1) p->tonecurve_type[ch_L] = p->tonecurve_type[ch_a] = p->tonecurve_type[ch_b] = CATMULL_ROM;
858 if(combo == 2) p->tonecurve_type[ch_L] = p->tonecurve_type[ch_a] = p->tonecurve_type[ch_b] = MONOTONE_HERMITE;
860 gtk_widget_queue_draw(GTK_WIDGET(g->area));
861}
862
863static void tab_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
864{
865 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
867 if(darktable.gui->reset) return;
868 c->channel = (tonecurve_channel_t)page_num;
869 gtk_widget_queue_draw(self->widget);
870}
871
872static gboolean area_resized(GtkWidget *widget, GdkEvent *event, gpointer user_data)
873{
874 GtkRequisition r;
875 GtkAllocation allocation;
876 gtk_widget_get_allocation(widget, &allocation);
877 r.width = allocation.width;
878 r.height = allocation.width;
879 gtk_widget_get_preferred_size(widget, &r, NULL);
880 return TRUE;
881}
882
883static float to_log(const float x, const float base, const int ch, const int semilog, const int is_ordinate)
884{
885 // don't log-encode the a and b channels
886 if(base > 0.0f && ch == ch_L)
887 {
888 if (semilog == 1 && is_ordinate == 1)
889 {
890 // we don't want log on ordinate axis in semilog x
891 return x;
892 }
893 else if (semilog == -1 && is_ordinate == 0)
894 {
895 // we don't want log on abcissa axis in semilog y
896 return x;
897 }
898 else
899 {
900 return logf(x * base + 1.0f) / logf(base + 1.0f);
901 }
902 }
903 else
904 {
905 return x;
906 }
907}
908
909static float to_lin(const float x, const float base, const int ch, const int semilog, const int is_ordinate)
910{
911 // don't log-encode the a and b channels
912 if(base > 0.0f && ch == ch_L)
913 {
914 if (semilog == 1 && is_ordinate == 1)
915 {
916 // we don't want log on ordinate axis in semilog x
917 return x;
918 }
919 else if (semilog == -1 && is_ordinate == 0)
920 {
921 // we don't want log on abcissa axis in semilog y
922 return x;
923 }
924 else
925 {
926 return (powf(base + 1.0f, x) - 1.0f) / base;
927 }
928 }
929 else
930 {
931 return x;
932 }
933}
934
936{
937 (void)pipe;
939
940 for(int k=0; k<3; k++)
941 {
942 gd->picked_color[k] = self->picked_color[k];
943 gd->picked_color_min[k] = self->picked_color_min[k];
944 gd->picked_color_max[k] = self->picked_color_max[k];
946 }
948}
949
951{
954
955 int ch = c->channel;
956 int nodes = p->tonecurve_nodes[ch];
957 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
958 int autoscale_ab = p->tonecurve_autoscale_ab;
959
960 // if autoscale_ab is on: do not modify a and b curves
961 if((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L) return;
962
963 if(nodes <= 2) return;
964
965 const float mx = tonecurve[c->selected].x;
966
967 // delete vertex if order has changed
968 // for all points, x coordinate of point must be strictly larger than
969 // the x coordinate of the previous point
970 if((c->selected > 0 && (tonecurve[c->selected - 1].x >= mx))
971 || (c->selected < nodes - 1 && (tonecurve[c->selected + 1].x <= mx)))
972 {
973 for(int k = c->selected; k < nodes - 1; k++)
974 {
975 tonecurve[k].x = tonecurve[k + 1].x;
976 tonecurve[k].y = tonecurve[k + 1].y;
977 }
978 c->selected = -2; // avoid re-insertion of that point immediately after this
979 p->tonecurve_nodes[ch]--;
980 }
981}
982
983static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
984{
987
988 int ch = c->channel;
989 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
990
991 tonecurve[c->selected].x = CLAMP(tonecurve[c->selected].x + dx, 0.0f, 1.0f);
992 tonecurve[c->selected].y = CLAMP(tonecurve[c->selected].y + dy, 0.0f, 1.0f);
993
994 dt_iop_tonecurve_sanity_check(self, widget);
995
996 gtk_widget_queue_draw(widget);
997
999 return TRUE;
1000}
1001
1002#define TONECURVE_DEFAULT_STEP (0.001f)
1003
1004static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
1005{
1006 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1009
1010 int ch = c->channel;
1011 int autoscale_ab = p->tonecurve_autoscale_ab;
1012
1013 // if autoscale_ab is on: do not modify a and b curves
1014 if((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L) return TRUE;
1015
1016 if(c->selected < 0) return TRUE;
1017
1018 gdouble delta_y;
1019 if(dt_gui_get_scroll_delta(event, &delta_y))
1020 {
1021 delta_y *= -TONECURVE_DEFAULT_STEP;
1022 return _move_point_internal(self, widget, 0.0, delta_y, event->state);
1023 }
1024
1025 return TRUE;
1026}
1027
1028static gboolean dt_iop_tonecurve_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
1029{
1030 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1033
1034 int ch = c->channel;
1035 int autoscale_ab = p->tonecurve_autoscale_ab;
1036
1037 // if autoscale_ab is on: do not modify a and b curves
1038 if((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L) return FALSE;
1039
1040 if(c->selected < 0) return FALSE;
1041
1042 guint key = dt_keys_mainpad_alternatives(event->keyval);
1043 int handled = 0;
1044 float dx = 0.0f, dy = 0.0f;
1045 if(key == GDK_KEY_Up)
1046 {
1047 handled = 1;
1049 }
1050 else if(key == GDK_KEY_Down)
1051 {
1052 handled = 1;
1054 }
1055 else if(key == GDK_KEY_Right)
1056 {
1057 handled = 1;
1059 }
1060 else if(key == GDK_KEY_Left)
1061 {
1062 handled = 1;
1064 }
1065
1066 if(!handled) return FALSE;
1067
1068 return _move_point_internal(self, widget, dx, dy, event->state);
1069}
1070
1071#undef TONECURVE_DEFAULT_STEP
1072
1073void gui_init(struct dt_iop_module_t *self)
1074{
1077
1078 for(int ch = 0; ch < ch_max; ch++)
1079 {
1080 c->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->tonecurve_type[ch]);
1081 c->minmax_curve_nodes[ch] = p->tonecurve_nodes[ch];
1082 c->minmax_curve_type[ch] = p->tonecurve_type[ch];
1083 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
1084 (void)dt_draw_curve_add_point(c->minmax_curve[ch], p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
1085 }
1086
1087 c->channel = ch_L;
1088 c->mouse_x = c->mouse_y = -1.0;
1089 c->selected = -1;
1090 c->loglogscale = 0;
1091 c->semilog = 0;
1092 self->timeout_handle = 0;
1093
1094 c->autoscale_ab = dt_bauhaus_combobox_from_params(self, "tonecurve_autoscale_ab");
1095 gtk_widget_set_tooltip_text(c->autoscale_ab, _("if set to auto, a and b curves have no effect and are "
1096 "not displayed. chroma values (a and b) of each pixel are "
1097 "then adjusted based on L curve data. auto XYZ is similar "
1098 "but applies the saturation changes in XYZ space."));
1099 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1100
1101 c->channel_tabs = dt_ui_notebook_new();
1102 dt_ui_notebook_page(c->channel_tabs, N_("L"), _("tonecurve for L channel"));
1103 dt_ui_notebook_page(c->channel_tabs, N_("a"), _("tonecurve for a channel"));
1104 dt_ui_notebook_page(c->channel_tabs, N_("b"), _("tonecurve for b channel"));
1105 g_signal_connect(G_OBJECT(c->channel_tabs), "switch_page", G_CALLBACK(tab_switch), self);
1106 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(c->channel_tabs), TRUE, TRUE, 0);
1107 gtk_box_pack_start(GTK_BOX(hbox), gtk_grid_new(), TRUE, TRUE, 0);
1108
1109 c->colorpicker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT_AREA, hbox);
1110 gtk_widget_set_tooltip_text(c->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
1111
1112 gtk_box_pack_start(GTK_BOX(self->widget), hbox, FALSE, FALSE, 0);
1113
1114 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
1115 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
1116 g_object_set_data(G_OBJECT(c->area), "iop-instance", self);
1117 gtk_box_pack_start(GTK_BOX(self->widget),
1118 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
1119 "plugins/darkroom/tonecurve/graphheight", 280, 100),
1120 FALSE, FALSE, 0);
1121
1122 // FIXME: that tooltip goes in the way of the numbers when you hover a node to get a reading
1123 //gtk_widget_set_tooltip_text(GTK_WIDGET(c->area), _("double click to reset curve"));
1124
1125 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask
1126 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1127 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1128 gtk_widget_set_can_focus(GTK_WIDGET(c->area), TRUE);
1129 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(dt_iop_tonecurve_draw), self);
1130 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(dt_iop_tonecurve_button_press), self);
1131 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(dt_iop_tonecurve_motion_notify), self);
1132 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(dt_iop_tonecurve_leave_notify), self);
1133 g_signal_connect(G_OBJECT(c->area), "enter-notify-event", G_CALLBACK(dt_iop_tonecurve_enter_notify), self);
1134 g_signal_connect(G_OBJECT(c->area), "configure-event", G_CALLBACK(area_resized), self);
1135 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(_scrolled), self);
1136 g_signal_connect(G_OBJECT(c->area), "key-press-event", G_CALLBACK(dt_iop_tonecurve_key_press), self);
1137
1138 /* From src/common/curve_tools.h :
1139 #define CUBIC_SPLINE 0
1140 #define CATMULL_ROM 1
1141 #define MONOTONE_HERMITE 2
1142 */
1143 c->interpolator = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(self));
1144 dt_bauhaus_widget_set_label(c->interpolator, N_("interpolation method"));
1145 dt_bauhaus_combobox_add(c->interpolator, _("cubic spline"));
1146 dt_bauhaus_combobox_add(c->interpolator, _("centripetal spline"));
1147 dt_bauhaus_combobox_add(c->interpolator, _("monotonic spline"));
1148 gtk_box_pack_start(GTK_BOX(self->widget), c->interpolator , TRUE, TRUE, 0);
1149 gtk_widget_set_tooltip_text(c->interpolator, _("change this method if you see oscillations or cusps in the curve\n"
1150 "- cubic spline is better to produce smooth curves but oscillates when nodes are too close\n"
1151 "- centripetal is better to avoids cusps and oscillations with close nodes but is less smooth\n"
1152 "- monotonic is better for accuracy of pure analytical functions (log, gamma, exp)\n"));
1153 g_signal_connect(G_OBJECT(c->interpolator), "value-changed", G_CALLBACK(interpolator_callback), self);
1154
1155 c->preserve_colors = dt_bauhaus_combobox_from_params(self, "preserve_colors");
1156 gtk_widget_set_tooltip_text(c->preserve_colors, _("method to preserve colors when applying contrast"));
1157
1158 c->logbase = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.0f, 40.0f, 0, 0.0f, 2);
1159 dt_bauhaus_widget_set_label(c->logbase, N_("scale for graph"));
1160 gtk_box_pack_start(GTK_BOX(self->widget), c->logbase , TRUE, TRUE, 0);
1161 g_signal_connect(G_OBJECT(c->logbase), "value-changed", G_CALLBACK(logbase_callback), self);
1162
1163 c->sizegroup = GTK_SIZE_GROUP(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL));
1164 gtk_size_group_add_widget(c->sizegroup, GTK_WIDGET(c->area));
1165 gtk_size_group_add_widget(c->sizegroup, GTK_WIDGET(c->channel_tabs));
1166}
1167
1169{
1171 // this one we need to unref manually. not so the initially unowned widgets.
1172 g_object_unref(c->sizegroup);
1173 dt_draw_curve_destroy(c->minmax_curve[ch_L]);
1174 dt_draw_curve_destroy(c->minmax_curve[ch_a]);
1175 dt_draw_curve_destroy(c->minmax_curve[ch_b]);
1177
1179}
1180
1181static gboolean dt_iop_tonecurve_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1182{
1183 gtk_widget_queue_draw(widget);
1184 return TRUE;
1185}
1186
1187static gboolean dt_iop_tonecurve_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1188{
1189 gtk_widget_queue_draw(widget);
1190 return TRUE;
1191}
1192
1193static void picker_scale(const float *in, float *out)
1194{
1195 out[0] = CLAMP(in[0] / 100.0f, 0.0f, 1.0f);
1196 out[1] = CLAMP((in[1] + 128.0f) / 256.0f, 0.0f, 1.0f);
1197 out[2] = CLAMP((in[2] + 128.0f) / 256.0f, 0.0f, 1.0f);
1198}
1199
1200static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1201{
1202 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1206
1207 int ch = c->channel;
1208 int nodes = p->tonecurve_nodes[ch];
1209 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
1210
1211 if(c->minmax_curve_type[ch] != p->tonecurve_type[ch] || c->minmax_curve_nodes[ch] != p->tonecurve_nodes[ch])
1212 {
1213 dt_draw_curve_destroy(c->minmax_curve[ch]);
1214 c->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->tonecurve_type[ch]);
1215 c->minmax_curve_nodes[ch] = p->tonecurve_nodes[ch];
1216 c->minmax_curve_type[ch] = p->tonecurve_type[ch];
1217 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
1218 (void)dt_draw_curve_add_point(c->minmax_curve[ch], p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
1219 }
1220 else
1221 {
1222 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
1223 dt_draw_curve_set_point(c->minmax_curve[ch], k, p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
1224 }
1225 dt_draw_curve_t *minmax_curve = c->minmax_curve[ch];
1226 dt_draw_curve_calc_values(minmax_curve, 0.0, 1.0, DT_IOP_TONECURVE_RES, c->draw_xs, c->draw_ys);
1227
1228 float unbounded_coeffs[3];
1229 const float xm = tonecurve[nodes - 1].x;
1230 {
1231 const float x[4] = { 0.7f * xm, 0.8f * xm, 0.9f * xm, 1.0f * xm };
1232 const float y[4] = { c->draw_ys[CLAMP((int)(x[0] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1233 c->draw_ys[CLAMP((int)(x[1] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1234 c->draw_ys[CLAMP((int)(x[2] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1235 c->draw_ys[CLAMP((int)(x[3] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)] };
1237 }
1238
1239 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1240 GtkAllocation allocation;
1241 gtk_widget_get_allocation(widget, &allocation);
1242 int width = allocation.width, height = allocation.height;
1243 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1244 cairo_t *cr = cairo_create(cst);
1245
1246 cairo_translate(cr, inset, inset);
1247 width -= 2 * inset;
1248 height -= 2 * inset;
1249 char text[256];
1250
1251 // Draw frame borders
1252 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(0.5));
1254 cairo_rectangle(cr, 0, 0, width, height);
1255 cairo_stroke_preserve(cr);
1256
1257 if (ch==ch_L)
1258 { // remove below black to white transition to improve readability of the graph
1259 cairo_set_source_rgb(cr, .3, .3, .3);
1260 cairo_rectangle(cr, 0, 0, width, height);
1261 cairo_fill(cr);
1262 }
1263 else
1264 {
1265 // Draw the background gradient along the diagonal
1266 // ch == 0 : black to white
1267 // ch == 1 : green to magenta
1268 // ch == 2 : blue to yellow
1269 const float origin[3][3] = { { 0.0f, 0.0f, 0.0f }, // L = 0, @ (a, b) = 0
1270 { 0.0f, 231.0f/255.0f, 181.0f/255.0f },// a = -128 @ L = 75, b = 0
1271 { 0.0f, 30.0f/255.0f, 195.0f/255.0f}}; // b = -128 @ L = 75, a = 0
1272
1273 const float destin[3][3] = { { 1.0f, 1.0f, 1.0f }, // L = 100 @ (a, b) = 0
1274 { 1.0f, 0.0f, 192.0f/255.0f } , // a = 128 @ L = 75, b = 0
1275 { 215.0f/255.0f, 182.0f/255.0f, 0.0f}};// b = 128 @ L = 75, a = 0
1276
1277 // since Cairo paints with sRGB at gamma 2.4, linear gradients are not linear and, at 50%,
1278 // we don't see the neutral grey we would expect in the middle of a linear gradient between
1279 // 2 complimentary colors. So we add it artificially, but that will break the smoothness
1280 // of the transition.
1281
1282 // middle step for gradients (50 %)
1283 const float midgrey = to_log(0.45f, c->loglogscale, ch, c->semilog, 0);
1284
1285 const float middle[3][3] = { { midgrey, midgrey, midgrey }, // L = 50 @ (a, b) = 0
1286 { 0.67f, 0.67f, 0.67f}, // L = 75 @ (a, b) = 0
1287 { 0.67f, 0.67f, 0.67f}}; // L = 75 @ (a, b) = 0
1288
1289 const float opacities[3] = { 0.5f, 0.5f, 0.5f};
1290
1291 cairo_pattern_t *pat;
1292 pat = cairo_pattern_create_linear (height, 0.0, 0.0, width);
1293 cairo_pattern_add_color_stop_rgba (pat, 1, origin[ch][0], origin[ch][1], origin[ch][2], opacities[ch]);
1294 cairo_pattern_add_color_stop_rgba (pat, 0.5, middle[ch][0], middle[ch][1], middle[ch][2], opacities[ch]);
1295 cairo_pattern_add_color_stop_rgba (pat, 0, destin[ch][0], destin[ch][1], destin[ch][2], opacities[ch]);
1296 cairo_set_source (cr, pat);
1297 cairo_fill (cr);
1298 cairo_pattern_destroy (pat);
1299 }
1300
1301 // draw grid
1303
1304 if (c->loglogscale > 0.0f && ch == ch_L )
1305 {
1306 if (c->semilog == 0)
1307 {
1308 dt_draw_loglog_grid(cr, 4, 0, height, width, 0, c->loglogscale + 1.0f);
1309 }
1310 else if (c->semilog == 1)
1311 {
1312 dt_draw_semilog_x_grid(cr, 4, 0, height, width, 0, c->loglogscale + 1.0f);
1313 }
1314 else if (c->semilog == -1)
1315 {
1316 dt_draw_semilog_y_grid(cr, 4, 0, height, width, 0, c->loglogscale + 1.0f);
1317 }
1318 }
1319 else
1320 {
1321 dt_draw_grid(cr, 4, 0, 0, width, height);
1322 }
1323
1324 // draw identity line
1325 cairo_move_to(cr, 0, height);
1326 cairo_line_to(cr, width, 0);
1327 cairo_stroke(cr);
1328 cairo_translate(cr, 0, height);
1329
1330 // draw histogram in background
1331 // only if module is enabled
1332 if(self->enabled)
1333 {
1334 float *raw_mean, *raw_min, *raw_max;
1335 float *raw_mean_output;
1336 dt_aligned_pixel_t picker_mean, picker_min, picker_max;
1337 const gboolean is_linear = FALSE;
1338
1339 raw_mean = gd->picked_color;
1340 raw_min = gd->picked_color_min;
1341 raw_max = gd->picked_color_max;
1342 raw_mean_output = gd->picked_output_color;
1343
1344 const uint32_t *hist = self->histogram;
1345 const float hist_max = is_linear ? self->histogram_max[ch] : logf(1.0 + self->histogram_max[ch]);
1346 if(!IS_NULL_PTR(hist) && hist_max > 0.0f)
1347 {
1348 cairo_save(cr);
1349 cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
1350 cairo_move_to(cr, 0, height);
1352
1353 if (ch == ch_L && c->loglogscale > 0.0f)
1354 {
1355 dt_draw_histogram_8_log_base(cr, hist, 4, ch, is_linear, c->loglogscale + 1.0f);
1356 }
1357 else
1358 {
1359 dt_draw_histogram_8(cr, hist, 4, ch, is_linear);
1360 }
1361 cairo_restore(cr);
1362 }
1363
1364 cairo_move_to(cr, 0, height);
1366 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker)))
1367 {
1368 // the global live samples ...
1369 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
1370
1371 for(GSList *samples = darktable.develop->color_picker.samples; samples; samples = g_slist_next(samples))
1372 {
1373 dt_colorpicker_sample_t *sample = samples->data;
1374
1375 picker_scale(sample->lab[DT_LIB_COLORPICKER_STATISTIC_MEAN], picker_mean);
1378
1379 // Convert abcissa to log coordinates if needed
1380 picker_min[ch] = to_log(picker_min[ch], c->loglogscale, ch, c->semilog, 0);
1381 picker_max[ch] = to_log(picker_max[ch], c->loglogscale, ch, c->semilog, 0);
1382 picker_mean[ch] = to_log(picker_mean[ch], c->loglogscale, ch, c->semilog, 0);
1383
1384 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.35);
1385 cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
1386 -height);
1387 cairo_fill(cr);
1388 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.5);
1389 cairo_move_to(cr, width * picker_mean[ch], 0);
1390 cairo_line_to(cr, width * picker_mean[ch], -height);
1391 cairo_stroke(cr);
1392 }
1393
1394 // ... and the local sample
1395 if(raw_max[0] >= 0.0f)
1396 {
1397 cairo_save(cr);
1398 PangoLayout *layout;
1399 PangoRectangle ink;
1400 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
1401 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
1402 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
1403 layout = pango_cairo_create_layout(cr);
1404 pango_layout_set_font_description(layout, desc);
1405
1406 picker_scale(raw_mean, picker_mean);
1407 picker_scale(raw_min, picker_min);
1408 picker_scale(raw_max, picker_max);
1409
1410 // scale conservatively to 100% of width:
1411 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
1412 pango_layout_set_text(layout, text, -1);
1413 pango_layout_get_pixel_extents(layout, &ink, NULL);
1414 pango_font_description_set_absolute_size(desc, width*1.0/ink.width * PANGO_SCALE);
1415 pango_layout_set_font_description(layout, desc);
1416
1417 picker_min[ch] = to_log(picker_min[ch], c->loglogscale, ch, c->semilog, 0);
1418 picker_max[ch] = to_log(picker_max[ch], c->loglogscale, ch, c->semilog, 0);
1419 picker_mean[ch] = to_log(picker_mean[ch], c->loglogscale, ch, c->semilog, 0);
1420
1421 cairo_set_source_rgba(cr, 0.7, 0.5, 0.5, 0.35);
1422 cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
1423 -height);
1424 cairo_fill(cr);
1425 cairo_set_source_rgba(cr, 0.9, 0.7, 0.7, 0.5);
1426 cairo_move_to(cr, width * picker_mean[ch], 0);
1427 cairo_line_to(cr, width * picker_mean[ch], -height);
1428 cairo_stroke(cr);
1429
1430 snprintf(text, sizeof(text), "%.1f \342\206\222 %.1f", raw_mean[ch], raw_mean_output[ch]);
1431
1433 cairo_set_font_size(cr, DT_PIXEL_APPLY_DPI(0.04) * height);
1434 pango_layout_set_text(layout, text, -1);
1435 pango_layout_get_pixel_extents(layout, &ink, NULL);
1436 cairo_move_to(cr, 0.02f * width, -0.94 * height - ink.height - ink.y);
1437 pango_cairo_show_layout(cr, layout);
1438 cairo_stroke(cr);
1439 pango_font_description_free(desc);
1440 g_object_unref(layout);
1441 cairo_restore(cr);
1442 }
1443 }
1444 }
1445
1446 // draw curve
1447 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
1449
1450 for(int k = 0; k < DT_IOP_TONECURVE_RES; k++)
1451 {
1452 const float xx = k / (DT_IOP_TONECURVE_RES - 1.0f);
1453 float yy;
1454
1455 if(xx > xm)
1456 {
1458 }
1459 else
1460 {
1461 yy = c->draw_ys[k];
1462 }
1463
1464 const float x = to_log(xx, c->loglogscale, ch, c->semilog, 0),
1465 y = to_log(yy, c->loglogscale, ch, c->semilog, 1);
1466
1467 cairo_line_to(cr, x * width, -height * y);
1468 }
1469 cairo_stroke(cr);
1470
1471 // draw nodes positions
1472 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
1473 for(int k = 0; k < nodes; k++)
1474 {
1475 const float x = to_log(tonecurve[k].x, c->loglogscale, ch, c->semilog, 0),
1476 y = to_log(tonecurve[k].y, c->loglogscale, ch, c->semilog, 1);
1477
1478 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
1480 cairo_stroke_preserve(cr);
1482 cairo_fill(cr);
1483 }
1484
1485 // draw selected cursor
1486 if(c->selected >= 0)
1487 {
1488 // draw information about current selected node
1489 PangoLayout *layout;
1490 PangoRectangle ink;
1491 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
1492 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
1493 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
1494 layout = pango_cairo_create_layout(cr);
1495 pango_layout_set_font_description(layout, desc);
1496
1497 // scale conservatively to 100% of width:
1498 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
1499 pango_layout_set_text(layout, text, -1);
1500 pango_layout_get_pixel_extents(layout, &ink, NULL);
1501 pango_font_description_set_absolute_size(desc, width*1.0/ink.width * PANGO_SCALE);
1502 pango_layout_set_font_description(layout, desc);
1503
1504 const float min_scale_value = ch == ch_L ? 0.0f : -128.0f;
1505 const float max_scale_value = ch == ch_L ? 100.0f : 128.0f;
1506
1507 const float x_node_value = tonecurve[c->selected].x * (max_scale_value - min_scale_value) + min_scale_value;
1508 const float y_node_value = tonecurve[c->selected].y * (max_scale_value - min_scale_value) + min_scale_value;
1509 const float d_node_value = y_node_value - x_node_value;
1510 snprintf(text, sizeof(text), "%.1f / %.1f ( %+.1f)", x_node_value, y_node_value, d_node_value);
1511
1513 pango_layout_set_text(layout, text, -1);
1514 pango_layout_get_pixel_extents(layout, &ink, NULL);
1515 cairo_move_to(cr, 0.98f * width - ink.width - ink.x, -0.02 * height - ink.height - ink.y);
1516 pango_cairo_show_layout(cr, layout);
1517 cairo_stroke(cr);
1518 pango_font_description_free(desc);
1519 g_object_unref(layout);
1520
1521 // enlarge selected node
1523 const float x = to_log(tonecurve[c->selected].x, c->loglogscale, ch, c->semilog, 0),
1524 y = to_log(tonecurve[c->selected].y, c->loglogscale, ch, c->semilog, 1);
1525
1526 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(6), 0, 2. * M_PI);
1527 cairo_fill(cr);
1528 }
1529
1530 cairo_destroy(cr);
1531 cairo_set_source_surface(crf, cst, 0, 0);
1532 cairo_paint(crf);
1533 cairo_surface_destroy(cst);
1534 return TRUE;
1535}
1536
1537static inline int _add_node(dt_iop_tonecurve_node_t *tonecurve, int *nodes, float x, float y)
1538{
1539 int selected = -1;
1540 if(tonecurve[0].x > x)
1541 selected = 0;
1542 else
1543 {
1544 for(int k = 1; k < *nodes; k++)
1545 {
1546 if(tonecurve[k].x > x)
1547 {
1548 selected = k;
1549 break;
1550 }
1551 }
1552 }
1553 if(selected == -1) selected = *nodes;
1554 for(int i = *nodes; i > selected; i--)
1555 {
1556 tonecurve[i].x = tonecurve[i - 1].x;
1557 tonecurve[i].y = tonecurve[i - 1].y;
1558 }
1559 // found a new point
1560 tonecurve[selected].x = x;
1561 tonecurve[selected].y = y;
1562 (*nodes)++;
1563 return selected;
1564}
1565
1566static gboolean dt_iop_tonecurve_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1567{
1568 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1571
1572 int ch = c->channel;
1573 int nodes = p->tonecurve_nodes[ch];
1574 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
1575 int autoscale_ab = p->tonecurve_autoscale_ab;
1576
1577 // if autoscale_ab is on: do not modify a and b curves
1578 if((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L) goto finally;
1579
1580 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1581 GtkAllocation allocation;
1582 gtk_widget_get_allocation(widget, &allocation);
1583 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
1584 double old_m_x = c->mouse_x;
1585 double old_m_y = c->mouse_y;
1586 c->mouse_x = event->x - inset;
1587 c->mouse_y = event->y - inset;
1588
1589 const float mx = CLAMP(c->mouse_x, 0, width) / width;
1590 const float my = 1.0f - CLAMP(c->mouse_y, 0, height) / height;
1591 const float linx = to_lin(mx, c->loglogscale, ch, c->semilog, 0),
1592 liny = to_lin(my, c->loglogscale, ch, c->semilog, 1);
1593
1594 if(event->state & GDK_BUTTON1_MASK)
1595 {
1596 // got a vertex selected:
1597 if(c->selected >= 0)
1598 {
1599 // this is used to translate mause position in loglogscale to make this behavior unified with linear scale.
1600 const float translate_mouse_x = old_m_x / width - to_log(tonecurve[c->selected].x, c->loglogscale, ch, c->semilog, 0);
1601 const float translate_mouse_y = 1 - old_m_y / height - to_log(tonecurve[c->selected].y, c->loglogscale, ch, c->semilog, 1);
1602 // dx & dy are in linear coordinates
1603 const float dx = to_lin(c->mouse_x / width - translate_mouse_x, c->loglogscale, ch, c->semilog, 0)
1604 - to_lin(old_m_x / width - translate_mouse_x, c->loglogscale, ch, c->semilog, 0);
1605 const float dy = to_lin(1 - c->mouse_y / height - translate_mouse_y, c->loglogscale, ch, c->semilog, 1)
1606 - to_lin(1 - old_m_y / height - translate_mouse_y, c->loglogscale, ch, c->semilog, 1);
1607 return _move_point_internal(self, widget, dx, dy, event->state);
1608 }
1609 else if(nodes < DT_IOP_TONECURVE_MAXNODES && c->selected >= -1)
1610 {
1611 // no vertex was close, create a new one!
1612 c->selected = _add_node(tonecurve, &p->tonecurve_nodes[ch], linx, liny);
1614 }
1615 }
1616 else
1617 {
1618 // minimum area around the node to select it:
1619 float min = .04f;
1620 min *= min; // comparing against square
1621 int nearest = -1;
1622 for(int k = 0; k < nodes; k++)
1623 {
1624 float dist
1625 = (my - to_log(tonecurve[k].y, c->loglogscale, ch, c->semilog, 1)) * (my - to_log(tonecurve[k].y, c->loglogscale, ch, c->semilog, 1))
1626 + (mx - to_log(tonecurve[k].x, c->loglogscale, ch, c->semilog, 0)) * (mx - to_log(tonecurve[k].x, c->loglogscale, ch, c->semilog, 0));
1627 if(dist < min)
1628 {
1629 min = dist;
1630 nearest = k;
1631 }
1632 }
1633 c->selected = nearest;
1634 }
1635finally:
1636 if(c->selected >= 0) gtk_widget_grab_focus(widget);
1637 gtk_widget_queue_draw(widget);
1638 return TRUE;
1639}
1640
1641static gboolean dt_iop_tonecurve_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1642{
1643 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1647
1648 int ch = c->channel;
1649 int autoscale_ab = p->tonecurve_autoscale_ab;
1650 int nodes = p->tonecurve_nodes[ch];
1651 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
1652
1653 if(event->button == 1)
1654 {
1655 if(event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, GDK_CONTROL_MASK)
1656 && nodes < DT_IOP_TONECURVE_MAXNODES && c->selected == -1)
1657 {
1658 // 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
1659 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1660 GtkAllocation allocation;
1661 gtk_widget_get_allocation(widget, &allocation);
1662 int width = allocation.width - 2 * inset;
1663 c->mouse_x = event->x - inset;
1664 c->mouse_y = event->y - inset;
1665
1666 const float mx = CLAMP(c->mouse_x, 0, width) / (float)width;
1667 const float linx = to_lin(mx, c->loglogscale, ch, c->semilog, 0);
1668
1669 // don't add a node too close to others in x direction, it can crash dt
1670 int selected = -1;
1671 if(tonecurve[0].x > mx)
1672 selected = 0;
1673 else
1674 {
1675 for(int k = 1; k < nodes; k++)
1676 {
1677 if(tonecurve[k].x > mx)
1678 {
1679 selected = k;
1680 break;
1681 }
1682 }
1683 }
1684 if(selected == -1) selected = nodes;
1685 // > 0 -> check distance to left neighbour
1686 // < nodes -> check distance to right neighbour
1687 if(!((selected > 0 && linx - tonecurve[selected - 1].x <= 0.025) ||
1688 (selected < nodes && tonecurve[selected].x - linx <= 0.025)))
1689 {
1690 // evaluate the curve at the current x position
1691 const float y = dt_draw_curve_calc_value(c->minmax_curve[ch], linx);
1692
1693 if(y >= 0.0 && y <= 1.0) // never add something outside the viewport, you couldn't change it afterwards
1694 {
1695 // create a new node
1696 selected = _add_node(tonecurve, &p->tonecurve_nodes[ch], linx, y);
1697
1698 // maybe set the new one as being selected
1699 float min = .04f;
1700 min *= min; // comparing against square
1701 for(int k = 0; k < nodes; k++)
1702 {
1703 float other_y = to_log(tonecurve[k].y, c->loglogscale, ch, c->semilog, 1);
1704 float dist = (y - other_y) * (y - other_y);
1705 if(dist < min) c->selected = selected;
1706 }
1707
1709 gtk_widget_queue_draw(self->widget);
1710 }
1711 }
1712 return TRUE;
1713 }
1714 else if(event->type == GDK_2BUTTON_PRESS)
1715 {
1716 // reset current curve
1717 // if autoscale_ab is on: allow only reset of L curve
1718 if(!((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L))
1719 {
1720 p->tonecurve_nodes[ch] = d->tonecurve_nodes[ch];
1721 p->tonecurve_type[ch] = d->tonecurve_type[ch];
1722 for(int k = 0; k < d->tonecurve_nodes[ch]; k++)
1723 {
1724 p->tonecurve[ch][k].x = d->tonecurve[ch][k].x;
1725 p->tonecurve[ch][k].y = d->tonecurve[ch][k].y;
1726 }
1727 c->selected = -2; // avoid motion notify re-inserting immediately.
1728 dt_bauhaus_combobox_set(c->interpolator, p->tonecurve_type[ch_L]);
1730 gtk_widget_queue_draw(self->widget);
1731 }
1732 else
1733 {
1734 if(ch != ch_L)
1735 {
1736 p->tonecurve_autoscale_ab = DT_S_SCALE_MANUAL;
1737 c->selected = -2; // avoid motion notify re-inserting immediately.
1738 dt_bauhaus_combobox_set(c->autoscale_ab, 1);
1740 gtk_widget_queue_draw(self->widget);
1741 }
1742 }
1743 return TRUE;
1744 }
1745 }
1746 else if(event->button == 3 && c->selected >= 0)
1747 {
1748 if(c->selected == 0 || c->selected == nodes - 1)
1749 {
1750 float reset_value = c->selected == 0 ? 0 : 1;
1751 tonecurve[c->selected].y = tonecurve[c->selected].x = reset_value;
1752 gtk_widget_queue_draw(self->widget);
1754 return TRUE;
1755 }
1756
1757 for(int k = c->selected; k < nodes - 1; k++)
1758 {
1759 tonecurve[k].x = tonecurve[k + 1].x;
1760 tonecurve[k].y = tonecurve[k + 1].y;
1761 }
1762 tonecurve[nodes - 1].x = tonecurve[nodes - 1].y = 0;
1763 c->selected = -2; // avoid re-insertion of that point immediately after this
1764 p->tonecurve_nodes[ch]--;
1765 gtk_widget_queue_draw(self->widget);
1767 return TRUE;
1768 }
1769 return FALSE;
1770}
1771
1772// clang-format off
1773// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1774// vim: shiftwidth=2 expandtab tabstop=2 cindent
1775// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1776// clang-format on
#define DT_IOP_TONECURVE_MAXNODES
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
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3483
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3506
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_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1780
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
@ IOP_CS_LAB
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ 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_COLORSPACE_PROPHOTO_RGB
static dt_aligned_pixel_t rgb
dt_Lab_to_XYZ(Lab, XYZ)
static const float const float const float min
static dt_aligned_pixel_t XYZ
static dt_aligned_pixel_t Lab
const dt_colormatrix_t dt_aligned_pixel_t out
dt_XYZ_to_Lab(XYZ, Lab)
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_free(ptr)
Definition darktable.h:456
#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
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_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:143
static void dt_draw_semilog_y_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom, const float base)
Definition draw.h:223
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_histogram_8_log_base(cairo_t *cr, const uint32_t *hist, int32_t channels, int32_t channel, const gboolean linear, float base_log)
Definition draw.h:443
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 void dt_draw_semilog_x_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom, const float base)
Definition draw.h:207
static void dt_draw_histogram_8(cairo_t *cr, const uint32_t *hist, int32_t channels, int32_t channel, const gboolean linear)
Definition draw.h:454
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_loglog_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom, const float base)
Definition draw.h:191
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
GtkNotebook * dt_ui_notebook_new()
Definition gtk.c:2254
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_update_filter(const char *name, dt_dev_operation_t op, const int32_t version, const int filter)
void dt_gui_presets_update_ldr(const char *name, dt_dev_operation_t op, const int32_t version, const int ldrflag)
void dt_gui_presets_update_iso(const char *name, dt_dev_operation_t op, const int32_t version, const float min, const float max)
void dt_gui_presets_update_mml(const char *name, dt_dev_operation_t op, const int32_t version, const char *maker, const char *model, const char *lens)
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)
@ FOR_RAW
Definition gui/presets.h:41
#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_MODULE
Definition imageop.h:197
#define IOP_GUI_FREE
Definition imageop.h:602
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:168
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:169
@ IOP_GROUP_TONES
Definition imageop.h:137
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
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)
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)
void init(dt_iop_module_t *module)
static float to_lin(const float x, const float base, const int ch, const int semilog, const int is_ordinate)
static gboolean area_resized(GtkWidget *widget, GdkEvent *event, gpointer user_data)
const char ** description(struct dt_iop_module_t *self)
struct dt_iop_tonecurve_params_t dt_iop_tonecurve_params_t
int default_group()
static void interpolator_callback(GtkWidget *widget, dt_iop_module_t *self)
static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
#define DT_IOP_TONECURVE_MAXNODES
static gboolean dt_iop_tonecurve_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
static gboolean dt_iop_tonecurve_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
static gboolean dt_iop_tonecurve_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
dt_iop_tonecurve_autoscale_t
@ DT_S_SCALE_AUTOMATIC_RGB
@ DT_S_SCALE_AUTOMATIC_XYZ
@ DT_S_SCALE_MANUAL
@ DT_S_SCALE_AUTOMATIC
#define DT_GUI_CURVE_EDITOR_INSET
static float eval_grey(float x)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
struct dt_iop_tonecurve_params_t preset
void gui_reset(struct dt_iop_module_t *self)
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
static int _add_node(dt_iop_tonecurve_node_t *tonecurve, int *nodes, float x, float y)
static gboolean dt_iop_tonecurve_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
void gui_init(struct dt_iop_module_t *self)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
static void dt_iop_tonecurve_sanity_check(dt_iop_module_t *self, GtkWidget *widget)
const char * name
float iso_max
static void logbase_callback(GtkWidget *slider, dt_iop_module_t *self)
void cleanup_global(dt_iop_module_so_t *module)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
int flags()
#define TONECURVE_DEFAULT_STEP
void gui_cleanup(struct dt_iop_module_t *self)
void init_presets(dt_iop_module_so_t *self)
static gboolean dt_iop_tonecurve_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
#define DT_IOP_TONECURVE_RES
const char * maker
__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 i, void *const o)
struct dt_iop_tonecurve_node_t dt_iop_tonecurve_node_t
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
int iso_min
static void picker_scale(const float *in, float *out)
static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
static const struct @60 preset_camera_curves[]
void init_global(dt_iop_module_so_t *module)
const char * model
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
tonecurve_channel_t
@ ch_L
@ ch_b
@ ch_a
@ ch_max
static void tab_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
static float to_log(const float x, const float base, const int ch, const int semilog, const int is_ordinate)
dt_iop_order_iccprofile_info_t * dt_ioppr_add_profile_info_to_list(struct dt_develop_t *dev, const dt_colorspaces_color_profile_type_t profile_type, const char *profile_filename, const int intent)
static const float x
static dt_aligned_pixel_t float *const const float unbounded_coeffs[3][3]
tonecurve_channel_t
Definition lightroom.c:156
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
@ DT_DEV_PIXELPIPE_PREVIEW
Definition pixelpipe.h:40
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
GdkRGBA graph_bg
Definition bauhaus.h:281
GdkRGBA graph_border
Definition bauhaus.h:281
GdkRGBA graph_fg_active
Definition bauhaus.h:281
PangoFontDescription * pango_font_desc
Definition bauhaus.h:274
GdkRGBA inset_histogram
Definition bauhaus.h:282
GdkRGBA graph_fg
Definition bauhaus.h:281
lib_colorpicker_sample_statistics lab
Definition colorpicker.h:74
dt_dev_request_flags_t request_histogram
struct dt_iop_module_t *void * data
dt_dev_pixelpipe_type_t type
struct dt_develop_t::@19 color_picker
Authoritative darkroom color-picker state.
GSList * samples
Definition develop.h:392
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_iop_global_data_t * data
Definition imageop.h:233
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_iop_global_data_t * global_data
Definition imageop.h:314
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
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
Region of interest passed through the pixelpipe.
Definition imageop.h:72
dt_draw_curve_t * curve[3]
float table[3][0x10000]
dt_draw_curve_t * minmax_curve[3]
tonecurve_channel_t channel
dt_iop_tonecurve_node_t tonecurve[3][20]
dt_iop_tonecurve_node_t tonecurve[3][20]
dt_iop_tonecurve_node_t tonecurve[3][20]
Definition lightroom.c:171
dt_iop_tonecurve_autoscale_t tonecurve_autoscale_ab
dt_iop_rgb_norms_t preserve_colors