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 "widgets/gdkkeys.h"
69#include "develop/iop_profile.h"
72#include "config.h"
73#endif
74#include <assert.h>
75#include "system/macros.h"
76#include "system/openmp.h"
78#include "system/mem_alloc.h"
79#include "system/simd.h"
81#include <math.h>
82#include <stdint.h>
83#include <stdlib.h>
84#include <string.h>
85
86#include "widgets/bauhaus.h"
87#include "pixel/rgb_norms.h"
88#include "control/control.h"
89#include "develop/develop.h"
90#include "develop/imageop.h"
92#include "develop/imageop_gui.h"
93#include "widgets/draw.h"
94#include "gui/presets.h"
96
97#include "iop/iop_api.h"
98#include "libs/colorpicker.h"
99#include "widgets/notebook.h"
100#include "widgets/scroll_wrap.h"
101#include "gui/screen_metrics.h"
102
103#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(1)
104#define DT_GUI_CURVE_INFL .3f
105
106#define DT_IOP_TONECURVE_RES 256
107#define DT_IOP_TONECURVE_MAXNODES 20
108
110
111static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data);
112static gboolean dt_iop_tonecurve_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data);
113static gboolean dt_iop_tonecurve_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
114static gboolean dt_iop_tonecurve_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data);
115static gboolean dt_iop_tonecurve_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data);
116static gboolean dt_iop_tonecurve_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
117
118
126/*
127typedef enum dt_iop_tonecurve_preservecolors_t
128{
129 DT_TONECURVE_PRESERVE_NONE = 0,
130 DT_TONECURVE_PRESERVE_LUMINANCE = 1,
131 DT_TONECURVE_PRESERVE_LMAX = 2,
132 DT_TONECURVE_PRESERVE_LAVG = 3,
133 DT_TONECURVE_PRESERVE_LSUM = 4,
134 DT_TONECURVE_PRESERVE_LNORM = 5,
135 DT_TONECURVE_PRESERVE_LBP = 6,
136} dt_iop_tonecurve_preservecolors_t;
137*/
138typedef struct dt_iop_tonecurve_node_t
139{
140 float x;
141 float y;
143
145{
146 DT_S_SCALE_AUTOMATIC = 1, /* automatically adjust saturation based on L_out/L_in
147 $DESCRIPTION: "Lab, linked channels" */
148 DT_S_SCALE_MANUAL = 0, /* user specified curves
149 $DESCRIPTION: "Lab, independent channels" */
150 DT_S_SCALE_AUTOMATIC_XYZ = 2, /* automatically adjust saturation by
151 transforming the curve C to C' like:
152 L_out=C(L_in) -> Y_out=C'(Y_in) and applying C' to the X and Z
153 channels, too (and then transforming it back to Lab of course)
154 $DESCRIPTION: "XYZ, linked channels" */
155 DT_S_SCALE_AUTOMATIC_RGB = 3, /* similar to above but use an rgb working space
156 $DESCRIPTION: "RGB, linked channels" */
158
159// parameter structure of tonecurve 1st version, needed for use in legacy_params()
165
166// parameter structure of tonecurve 3rd version, needed for use in legacy_params()
176
187
188typedef struct dt_iop_tonecurve_params_t
189{
190 dt_iop_tonecurve_node_t tonecurve[3][DT_IOP_TONECURVE_MAXNODES]; // three curves (L, a, b) with max number
191 // of nodes
192 int tonecurve_nodes[3];
193 int tonecurve_type[3]; // $DEFAULT: MONOTONE_HERMITE
194 dt_iop_tonecurve_autoscale_t tonecurve_autoscale_ab; //$DEFAULT: DT_S_SCALE_AUTOMATIC_RGB $DESCRIPTION: "color space"
195 int tonecurve_preset; // $DEFAULT: 0
196 int tonecurve_unbound_ab; // $DEFAULT: 1
197 dt_iop_rgb_norms_t preserve_colors; // $DEFAULT: DT_RGB_NORM_AVERAGE $DESCRIPTION: "preserve colors"
199
223
225{
226 dt_draw_curve_t *curve[3]; // curves for pipe piece and pixel processing
227 int curve_nodes[3]; // number of nodes
228 int curve_type[3]; // curve style (e.g. CUBIC_SPLINE)
229 float table[3][0x10000]; // precomputed look-up tables for tone curve
230 float unbounded_coeffs_L[3]; // approximation for extrapolation of L
231 float unbounded_coeffs_ab[12]; // approximation for extrapolation of ab (left and right)
236
244
245
246const char *name()
247{
248 return _("tone curve");
249}
250
252{
253 return IOP_GROUP_TONES;
254}
255
260
262{
263 return IOP_CS_LAB;
264}
265
266const char **description(struct dt_iop_module_t *self)
267{
268 return dt_iop_set_description(self, _("alter an image's tones using curves"),
269 _("corrective and creative"),
270 _("linear or non-linear, Lab, display-referred"),
271 _("non-linear, Lab"),
272 _("non-linear, Lab, display-referred"));
273}
274
275int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
276 void *new_params, const int new_version)
277{
278 if(old_version == 1 && new_version == 5)
279 {
282
283 // start with a fresh copy of default parameters
284 // unfortunately default_params aren't inited at this stage.
285 *n = (dt_iop_tonecurve_params_t){ { { { 0.0, 0.0 }, { 1.0, 1.0 } },
286 { { 0.0, 0.0 }, { 0.5, 0.5 }, { 1.0, 1.0 } },
287 { { 0.0, 0.0 }, { 0.5, 0.5 }, { 1.0, 1.0 } } },
288 { 2, 3, 3 },
290 1,
291 0,
292 1 };
293 for(int k = 0; k < 6; k++) n->tonecurve[ch_L][k].x = o->tonecurve_x[k];
294 for(int k = 0; k < 6; k++) n->tonecurve[ch_L][k].y = o->tonecurve_y[k];
295 n->tonecurve_nodes[ch_L] = 6;
296 n->tonecurve_type[ch_L] = CUBIC_SPLINE;
297 n->tonecurve_autoscale_ab = 1;
298 n->tonecurve_preset = o->tonecurve_preset;
299 n->tonecurve_unbound_ab = 0;
300 n->preserve_colors = 0;
301 return 0;
302 }
303 else if(old_version == 2 && new_version == 5)
304 {
305 // version 2 never really materialized so there should be no legacy history stacks of that version around
306 return 1;
307 }
308 else if(old_version == 3 && new_version == 5)
309 {
312
313 memcpy(n->tonecurve, o->tonecurve, sizeof(n->tonecurve));
314 memcpy(n->tonecurve_nodes, o->tonecurve_nodes, sizeof(n->tonecurve_nodes));
315 memcpy(n->tonecurve_type, o->tonecurve_type, sizeof(n->tonecurve_type));
316 n->tonecurve_autoscale_ab = o->tonecurve_autoscale_ab;
317 n->tonecurve_preset = o->tonecurve_preset;
318 n->tonecurve_unbound_ab = 0;
319 n->preserve_colors = 0;
320 return 0;
321 }
322 else if(old_version == 4 && new_version == 5)
323 {
326
327 memcpy(n->tonecurve, o->tonecurve, sizeof(dt_iop_tonecurve_params4_t));
328 n->preserve_colors = 0;
329 return 0;
330 }
331 return 1;
332}
333
335int 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)
336{
337 (void)pipe;
338 const dt_iop_roi_t *const roi_out = &piece->roi_out;
339
340 const dt_iop_tonecurve_data_t *const restrict d = (dt_iop_tonecurve_data_t *)(piece->data);
341 const dt_iop_order_iccprofile_info_t *const work_profile
343 const float xm_L = 1.0f / d->unbounded_coeffs_L[0];
344 const float xm_ar = 1.0f / d->unbounded_coeffs_ab[0];
345 const float xm_al = 1.0f - 1.0f / d->unbounded_coeffs_ab[3];
346 const float xm_br = 1.0f / d->unbounded_coeffs_ab[6];
347 const float xm_bl = 1.0f - 1.0f / d->unbounded_coeffs_ab[9];
348 const float low_approximation = d->table[0][(int)(0.01f * 0x10000ul)];
349
350 const size_t npixels = (size_t)roi_out->width * roi_out->height;
351 const int autoscale_ab = d->autoscale_ab;
352 const int unbound_ab = d->unbound_ab;
353
354 const float *const restrict in = (float*)i;
355 float *const restrict out = (float*)o;
357 for(int k = 0; k < 4*npixels; k += 4)
358 {
359 const float L_in = in[k] / 100.0f;
360
361 out[k+0] = (L_in < xm_L) ? d->table[ch_L][CLAMP((int)(L_in * 0x10000ul), 0, 0xffff)]
362 : dt_iop_eval_exp(d->unbounded_coeffs_L, L_in);
363
364 if(autoscale_ab == DT_S_SCALE_MANUAL)
365 {
366 const float a_in = (in[k+1] + 128.0f) / 256.0f;
367 const float b_in = (in[k+2] + 128.0f) / 256.0f;
368
369 if(unbound_ab == 0)
370 {
371 // old style handling of a/b curves: only lut lookup with clamping
372 out[k+1] = d->table[ch_a][CLAMP((int)(a_in * 0x10000ul), 0, 0xffff)];
373 out[k+2] = d->table[ch_b][CLAMP((int)(b_in * 0x10000ul), 0, 0xffff)];
374 }
375 else
376 {
377 // new style handling of a/b curves: lut lookup with two-sided extrapolation;
378 // mind the x-axis reversal for the left-handed side
379 out[k+1] = (a_in > xm_ar)
380 ? dt_iop_eval_exp(d->unbounded_coeffs_ab, a_in)
381 : ((a_in < xm_al) ? dt_iop_eval_exp(d->unbounded_coeffs_ab + 3, 1.0f - a_in)
382 : d->table[ch_a][CLAMP((int)(a_in * 0x10000ul), 0, 0xffff)]);
383 out[k+2] = (b_in > xm_br)
384 ? dt_iop_eval_exp(d->unbounded_coeffs_ab + 6, b_in)
385 : ((b_in < xm_bl) ? dt_iop_eval_exp(d->unbounded_coeffs_ab + 9, 1.0f - b_in)
386 : d->table[ch_b][CLAMP((int)(b_in * 0x10000ul), 0, 0xffff)]);
387 }
388 }
389 else if(autoscale_ab == DT_S_SCALE_AUTOMATIC)
390 {
391 // in Lab: correct compressed Luminance for saturation:
392 if(L_in > 0.01f)
393 {
394 out[k+1] = in[k+1] * out[k] / in[k+0];
395 out[k+2] = in[k+2] * out[k] / in[k+0];
396 }
397 else
398 {
399 out[k+1] = in[k+1] * low_approximation;
400 out[k+2] = in[k+2] * low_approximation;
401 }
402 }
403 else if(autoscale_ab == DT_S_SCALE_AUTOMATIC_XYZ)
404 {
406 dt_Lab_to_XYZ(in + k, XYZ);
407 for(int c=0;c<3;c++)
408 XYZ[c] = (XYZ[c] < xm_L) ? d->table[ch_L][CLAMP((int)(XYZ[c] * 0x10000ul), 0, 0xffff)]
409 : dt_iop_eval_exp(d->unbounded_coeffs_L, XYZ[c]);
411 }
412 else if(autoscale_ab == DT_S_SCALE_AUTOMATIC_RGB)
413 {
414 dt_aligned_pixel_t rgb = {0, 0, 0};
415 dt_Lab_to_prophotorgb(in + k, rgb);
416 if(d->preserve_colors == DT_RGB_NORM_NONE)
417 {
418 for(int c = 0; c < 3; c++)
419 {
420 rgb[c] = (rgb[c] < xm_L) ? d->table[ch_L][CLAMP((int)(rgb[c] * 0x10000ul), 0, 0xffff)]
421 : dt_iop_eval_exp(d->unbounded_coeffs_L, rgb[c]);
422 }
423 }
424 else
425 {
426 float ratio = 1.f;
427 const float lum = dt_rgb_norm(rgb, d->preserve_colors, work_profile);
428 if(lum > 0.f)
429 {
430 const float curve_lum = (lum < xm_L)
431 ? d->table[ch_L][CLAMP((int)(lum * 0x10000ul), 0, 0xffff)]
432 : dt_iop_eval_exp(d->unbounded_coeffs_L, lum);
433 ratio = curve_lum / lum;
434 }
435 for(size_t c = 0; c < 3; c++)
436 {
437 rgb[c] = (ratio * rgb[c]);
438 }
439 }
440 dt_prophotorgb_to_Lab(rgb, out + k);
441 }
442
443 out[k+3] = in[k+3];
444 }
445
446 return 0;
447}
448
449static const struct
450{
451 const char *name;
452 const char *maker;
453 const char *model;
455 float iso_max;
458 // This is where you can paste the line provided by dt-curve-tool
459 // Here is a valid example for you to compare
460 // clang-format off
461 // nikon d750 by Edouard Gomez
462 {"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}},
463 // nikon d5100 contributed by Stefan Kauerauf
464 {"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}},
465 // nikon d7000 by Edouard Gomez
466 {"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}},
467 // nikon d7200 standard by Ralf Brown (firmware 1.00)
468 {"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}},
469 // nikon d7500 by Anders Bennehag (firmware C 1.00, LD 2.016)
470 {"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}},
471 // nikon d90 by Edouard Gomez
472 {"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}},
473 // Olympus OM-D E-M10 II by Lukas Schrangl
474 {"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}},
475 // clang-format on
477
479{
481 memset(&p, 0, sizeof(p));
482 p.tonecurve_nodes[ch_L] = 6;
483 p.tonecurve_nodes[ch_a] = 7;
484 p.tonecurve_nodes[ch_b] = 7;
485 p.tonecurve_type[ch_L] = CUBIC_SPLINE;
486 p.tonecurve_type[ch_a] = CUBIC_SPLINE;
487 p.tonecurve_type[ch_b] = CUBIC_SPLINE;
488 p.tonecurve_preset = 0;
489 p.tonecurve_autoscale_ab = DT_S_SCALE_AUTOMATIC_RGB;
490 p.tonecurve_unbound_ab = 1;
491
492 float linear_ab[7] = { 0.0, 0.08, 0.3, 0.5, 0.7, 0.92, 1.0 };
493
494 // linear a, b curves for presets
495 for(int k = 0; k < 7; k++) p.tonecurve[ch_a][k].x = linear_ab[k];
496 for(int k = 0; k < 7; k++) p.tonecurve[ch_a][k].y = linear_ab[k];
497 for(int k = 0; k < 7; k++) p.tonecurve[ch_b][k].x = linear_ab[k];
498 for(int k = 0; k < 7; k++) p.tonecurve[ch_b][k].y = linear_ab[k];
499
500 // More useful low contrast curve (based on Samsung NX -2 Contrast)
501 p.tonecurve[ch_L][0].x = 0.000000;
502 p.tonecurve[ch_L][1].x = 0.003862;
503 p.tonecurve[ch_L][2].x = 0.076613;
504 p.tonecurve[ch_L][3].x = 0.169355;
505 p.tonecurve[ch_L][4].x = 0.774194;
506 p.tonecurve[ch_L][5].x = 1.000000;
507 p.tonecurve[ch_L][0].y = 0.000000;
508 p.tonecurve[ch_L][1].y = 0.007782;
509 p.tonecurve[ch_L][2].y = 0.156182;
510 p.tonecurve[ch_L][3].y = 0.290352;
511 p.tonecurve[ch_L][4].y = 0.773852;
512 p.tonecurve[ch_L][5].y = 1.000000;
513 dt_gui_presets_add_generic(_("contrast compression"), self->op,
514 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
515
516 p.tonecurve_nodes[ch_L] = 7;
517 float linear_L[7] = { 0.0, 0.08, 0.17, 0.50, 0.83, 0.92, 1.0 };
518
519 // Linear - no 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 dt_gui_presets_add_generic(_("gamma 1.0 (linear)"), self->op,
523 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
524
525 // Linear contrast
526 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
527 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
528 p.tonecurve[ch_L][1].y -= 0.020;
529 p.tonecurve[ch_L][2].y -= 0.030;
530 p.tonecurve[ch_L][4].y += 0.030;
531 p.tonecurve[ch_L][5].y += 0.020;
532 dt_gui_presets_add_generic(_("contrast - med (linear)"), self->op,
533 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
534
535 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
536 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
537 p.tonecurve[ch_L][1].y -= 0.040;
538 p.tonecurve[ch_L][2].y -= 0.060;
539 p.tonecurve[ch_L][4].y += 0.060;
540 p.tonecurve[ch_L][5].y += 0.040;
541 dt_gui_presets_add_generic(_("contrast - high (linear)"), self->op,
542 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
543
544 // Gamma contrast
545 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
546 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
547 p.tonecurve[ch_L][1].y -= 0.020;
548 p.tonecurve[ch_L][2].y -= 0.030;
549 p.tonecurve[ch_L][4].y += 0.030;
550 p.tonecurve[ch_L][5].y += 0.020;
551 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].x = powf(p.tonecurve[ch_L][k].x, 2.2f);
552 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(p.tonecurve[ch_L][k].y, 2.2f);
553 dt_gui_presets_add_generic(_("contrast - med (gamma 2.2)"), self->op,
554 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
555
556 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
557 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
558 p.tonecurve[ch_L][1].y -= 0.040;
559 p.tonecurve[ch_L][2].y -= 0.060;
560 p.tonecurve[ch_L][4].y += 0.060;
561 p.tonecurve[ch_L][5].y += 0.040;
562 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].x = powf(p.tonecurve[ch_L][k].x, 2.2f);
563 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(p.tonecurve[ch_L][k].y, 2.2f);
564 dt_gui_presets_add_generic(_("contrast - high (gamma 2.2)"), self->op,
565 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
566
569 p.tonecurve_type[ch_L] = MONOTONE_HERMITE;
570
571 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].x = linear_L[k];
572 for(int k = 0; k < 7; k++) p.tonecurve[ch_L][k].y = linear_L[k];
573
574 // Gamma 2.0 - no contrast
575 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(linear_L[k], 2.0f);
576 dt_gui_presets_add_generic(_("gamma 2.0"), self->op,
577 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
578
579 // Gamma 0.5 - no contrast
580 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(linear_L[k], 0.5f);
581 dt_gui_presets_add_generic(_("gamma 0.5"), self->op,
582 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
583
584 // Log2 - no contrast
585 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = logf(linear_L[k] + 1.0f) / logf(2.0f);
586 dt_gui_presets_add_generic(_("logarithm (base 2)"), self->op,
587 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
588
589 // Exp2 - no contrast
590 for(int k = 1; k < 6; k++) p.tonecurve[ch_L][k].y = powf(2.0f, linear_L[k]) - 1.0f;
591 dt_gui_presets_add_generic(_("exponential (base 2)"), self->op,
592 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
593
594 for (int k=0; k<sizeof(preset_camera_curves)/sizeof(preset_camera_curves[0]); k++)
595 {
596 // insert the preset
599
600 // restrict it to model, maker
601 dt_gui_presets_update_mml(preset_camera_curves[k].name, self->op, self->version(),
602 preset_camera_curves[k].maker, preset_camera_curves[k].model, "");
603
604 // restrict it to iso
605 dt_gui_presets_update_iso(preset_camera_curves[k].name, self->op, self->version(),
606 preset_camera_curves[k].iso_min, preset_camera_curves[k].iso_max);
607
608 // restrict it to raw images
610
611 // hide all non-matching presets in case the model string is set.
612 dt_gui_presets_update_filter(preset_camera_curves[k].name, self->op, self->version(), 1);
613 }
614}
615
618{
621
622 if(pipe->type == DT_DEV_PIXELPIPE_PREVIEW)
624 else
625 piece->request_histogram &= ~(DT_REQUEST_ON);
626
627 for(int ch = 0; ch < ch_max; ch++)
628 {
629 // take care of possible change of curve type or number of nodes (not yet implemented in UI)
630 if(d->curve_type[ch] != p->tonecurve_type[ch] || d->curve_nodes[ch] != p->tonecurve_nodes[ch])
631 {
632 dt_draw_curve_destroy(d->curve[ch]);
633 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, p->tonecurve_type[ch]);
634 d->curve_nodes[ch] = p->tonecurve_nodes[ch];
635 d->curve_type[ch] = p->tonecurve_type[ch];
636 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
637 (void)dt_draw_curve_add_point(d->curve[ch], p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
638 }
639 else
640 {
641 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
642 dt_draw_curve_set_point(d->curve[ch], k, p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
643 }
644 dt_draw_curve_calc_values(d->curve[ch], 0.0f, 1.0f, 0x10000, NULL, d->table[ch]);
645 }
646 for(int k = 0; k < 0x10000; k++) d->table[ch_L][k] *= 100.0f;
647 for(int k = 0; k < 0x10000; k++) d->table[ch_a][k] = d->table[ch_a][k] * 256.0f - 128.0f;
648 for(int k = 0; k < 0x10000; k++) d->table[ch_b][k] = d->table[ch_b][k] * 256.0f - 128.0f;
649
650 piece->process_cl_ready = 1;
651 if(p->tonecurve_autoscale_ab == DT_S_SCALE_AUTOMATIC_XYZ)
652 {
653 // derive curve for XYZ:
654 for(int k=0;k<0x10000;k++)
655 {
656 dt_aligned_pixel_t XYZ = {k/(float)0x10000, k/(float)0x10000, k/(float)0x10000};
657 dt_aligned_pixel_t Lab = {0.0};
659 Lab[0] = d->table[ch_L][CLAMP((int)(Lab[0]/100.0f * 0x10000), 0, 0xffff)];
661 d->table[ch_L][k] = XYZ[1]; // now mapping Y_in to Y_out
662 }
663 }
664 else if(p->tonecurve_autoscale_ab == DT_S_SCALE_AUTOMATIC_RGB)
665 {
666 // derive curve for rgb:
667 for(int k=0;k<0x10000;k++)
668 {
669 dt_aligned_pixel_t rgb = {k/(float)0x10000, k/(float)0x10000, k/(float)0x10000};
670 dt_aligned_pixel_t Lab = {0.0};
671 dt_prophotorgb_to_Lab(rgb, Lab);
672 Lab[0] = d->table[ch_L][CLAMP((int)(Lab[0]/100.0f * 0x10000), 0, 0xffff)];
673 dt_Lab_to_prophotorgb(Lab, rgb);
674 d->table[ch_L][k] = rgb[1]; // now mapping G_in to G_out
675 }
676 }
677
678 d->autoscale_ab = p->tonecurve_autoscale_ab;
679 d->unbound_ab = p->tonecurve_unbound_ab;
680 d->preserve_colors = p->preserve_colors;
681
682 // extrapolation for L-curve (right hand side only):
683 const float xm_L = p->tonecurve[ch_L][p->tonecurve_nodes[ch_L] - 1].x;
684 const float x_L[4] = { 0.7f * xm_L, 0.8f * xm_L, 0.9f * xm_L, 1.0f * xm_L };
685 const float y_L[4] = { d->table[ch_L][CLAMP((int)(x_L[0] * 0x10000ul), 0, 0xffff)],
686 d->table[ch_L][CLAMP((int)(x_L[1] * 0x10000ul), 0, 0xffff)],
687 d->table[ch_L][CLAMP((int)(x_L[2] * 0x10000ul), 0, 0xffff)],
688 d->table[ch_L][CLAMP((int)(x_L[3] * 0x10000ul), 0, 0xffff)] };
689 dt_iop_estimate_exp(x_L, y_L, 4, d->unbounded_coeffs_L);
690
691 // extrapolation for a-curve right side:
692 const float xm_ar = p->tonecurve[ch_a][p->tonecurve_nodes[ch_a] - 1].x;
693 const float x_ar[4] = { 0.7f * xm_ar, 0.8f * xm_ar, 0.9f * xm_ar, 1.0f * xm_ar };
694 const float y_ar[4] = { d->table[ch_a][CLAMP((int)(x_ar[0] * 0x10000ul), 0, 0xffff)],
695 d->table[ch_a][CLAMP((int)(x_ar[1] * 0x10000ul), 0, 0xffff)],
696 d->table[ch_a][CLAMP((int)(x_ar[2] * 0x10000ul), 0, 0xffff)],
697 d->table[ch_a][CLAMP((int)(x_ar[3] * 0x10000ul), 0, 0xffff)] };
698 dt_iop_estimate_exp(x_ar, y_ar, 4, d->unbounded_coeffs_ab);
699
700 // extrapolation for a-curve left side (we need to mirror the x-axis):
701 const float xm_al = 1.0f - p->tonecurve[ch_a][0].x;
702 const float x_al[4] = { 0.7f * xm_al, 0.8f * xm_al, 0.9f * xm_al, 1.0f * xm_al };
703 const float y_al[4] = { d->table[ch_a][CLAMP((int)((1.0f - x_al[0]) * 0x10000ul), 0, 0xffff)],
704 d->table[ch_a][CLAMP((int)((1.0f - x_al[1]) * 0x10000ul), 0, 0xffff)],
705 d->table[ch_a][CLAMP((int)((1.0f - x_al[2]) * 0x10000ul), 0, 0xffff)],
706 d->table[ch_a][CLAMP((int)((1.0f - x_al[3]) * 0x10000ul), 0, 0xffff)] };
707 dt_iop_estimate_exp(x_al, y_al, 4, d->unbounded_coeffs_ab + 3);
708
709 // extrapolation for b-curve right side:
710 const float xm_br = p->tonecurve[ch_b][p->tonecurve_nodes[ch_b] - 1].x;
711 const float x_br[4] = { 0.7f * xm_br, 0.8f * xm_br, 0.9f * xm_br, 1.0f * xm_br };
712 const float y_br[4] = { d->table[ch_b][CLAMP((int)(x_br[0] * 0x10000ul), 0, 0xffff)],
713 d->table[ch_b][CLAMP((int)(x_br[1] * 0x10000ul), 0, 0xffff)],
714 d->table[ch_b][CLAMP((int)(x_br[2] * 0x10000ul), 0, 0xffff)],
715 d->table[ch_b][CLAMP((int)(x_br[3] * 0x10000ul), 0, 0xffff)] };
716 dt_iop_estimate_exp(x_br, y_br, 4, d->unbounded_coeffs_ab + 6);
717
718 // extrapolation for b-curve left side (we need to mirror the x-axis):
719 const float xm_bl = 1.0f - p->tonecurve[ch_b][0].x;
720 const float x_bl[4] = { 0.7f * xm_bl, 0.8f * xm_bl, 0.9f * xm_bl, 1.0f * xm_bl };
721 const float y_bl[4] = { d->table[ch_b][CLAMP((int)((1.0f - x_bl[0]) * 0x10000ul), 0, 0xffff)],
722 d->table[ch_b][CLAMP((int)((1.0f - x_bl[1]) * 0x10000ul), 0, 0xffff)],
723 d->table[ch_b][CLAMP((int)((1.0f - x_bl[2]) * 0x10000ul), 0, 0xffff)],
724 d->table[ch_b][CLAMP((int)((1.0f - x_bl[3]) * 0x10000ul), 0, 0xffff)] };
725 dt_iop_estimate_exp(x_bl, y_bl, 4, d->unbounded_coeffs_ab + 9);
726}
727
728static float eval_grey(float x)
729{
730 // "log base" is a combined scaling and offset change so that x->[0,1] with
731 // the left side of the histogram expanded (slider->right) or not (slider left, linear)
732 return x;
733}
734
736{
737 // create part of the pixelpipe
740 piece->data = (void *)d;
741 piece->data_size = sizeof(dt_iop_tonecurve_data_t);
742 d->autoscale_ab = DT_S_SCALE_AUTOMATIC;
743 d->unbound_ab = 1;
744 for(int ch = 0; ch < ch_max; ch++)
745 {
746 d->curve[ch] = dt_draw_curve_new(0.0, 1.0, default_params->tonecurve_type[ch]);
747 d->curve_nodes[ch] = default_params->tonecurve_nodes[ch];
748 d->curve_type[ch] = default_params->tonecurve_type[ch];
749 for(int k = 0; k < default_params->tonecurve_nodes[ch]; k++)
750 (void)dt_draw_curve_add_point(d->curve[ch], default_params->tonecurve[ch][k].x,
751 default_params->tonecurve[ch][k].y);
752 }
753 for(int k = 0; k < 0x10000; k++) d->table[ch_L][k] = 100.0f * k / 0x10000; // identity for L
754 for(int k = 0; k < 0x10000; k++) d->table[ch_a][k] = 256.0f * k / 0x10000 - 128.0f; // identity for a
755 for(int k = 0; k < 0x10000; k++) d->table[ch_b][k] = 256.0f * k / 0x10000 - 128.0f; // identity for b
756}
757
759{
760 // clean up everything again.
762 for(int ch = 0; ch < ch_max; ch++) dt_draw_curve_destroy(d->curve[ch]);
763 dt_free_align(piece->data);
764 piece->data = NULL;
765}
766
767void gui_reset(struct dt_iop_module_t *self)
768{
771 dt_bauhaus_combobox_set(g->interpolator, p->tonecurve_type[ch_L]);
772 dt_bauhaus_combobox_set(g->preserve_colors, p->preserve_colors);
773 dt_bauhaus_slider_set(g->logbase, 0);
774 g->loglogscale = 0;
775 g->semilog = 0;
776
777 g->channel = (tonecurve_channel_t)ch_L;
778 gtk_widget_queue_draw(self->widget);
779}
780
781void gui_update(struct dt_iop_module_t *self)
782{
785
786 gui_changed(self, g->autoscale_ab, 0);
787
788 dt_bauhaus_combobox_set(g->interpolator, p->tonecurve_type[ch_L]);
789 g->loglogscale = eval_grey(dt_bauhaus_slider_get(g->logbase));
790 // that's all, gui curve is read directly from params during expose event.
791 gtk_widget_queue_draw(self->widget);
792}
793
795{
796 dt_iop_default_init(module);
797
798 module->request_histogram |= (DT_REQUEST_ON);
799
800 dt_iop_tonecurve_params_t *d = module->default_params;
801
802 d->tonecurve_nodes[0] = 2;
803 d->tonecurve_nodes[1] =
804 d->tonecurve_nodes[2] = 3;
805 d->tonecurve[0][1].x = d->tonecurve[0][1].y =
806 d->tonecurve[1][2].x = d->tonecurve[1][2].y =
807 d->tonecurve[2][2].x = d->tonecurve[2][2].y = 1.0f;
808 d->tonecurve[1][1].x = d->tonecurve[1][1].y =
809 d->tonecurve[2][1].x = d->tonecurve[2][1].y = 0.5f;
810}
811
813{
816 module->data = gd;
817 for(int k=0; k<3; k++)
818 {
819 gd->picked_color[k] = .0f;
820 gd->picked_color_min[k] = .0f;
821 gd->picked_color_max[k] = .0f;
822 gd->picked_output_color[k] = .0f;
823 }
824}
825
827{
828 dt_free(module->data);
829}
830
831static void logbase_callback(GtkWidget *slider, dt_iop_module_t *self)
832{
833 if(dt_gui_widgets_suppressed()) return;
835 g->loglogscale = eval_grey(dt_bauhaus_slider_get(g->logbase));
836 gtk_widget_queue_draw(GTK_WIDGET(g->area));
837}
838
839void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
840{
843
844 if(w == g->autoscale_ab)
845 {
846 g->channel = (tonecurve_channel_t)ch_L;
847 gtk_notebook_set_current_page(GTK_NOTEBOOK(g->channel_tabs), ch_L);
848
849 gtk_notebook_set_show_tabs(g->channel_tabs, p->tonecurve_autoscale_ab == DT_S_SCALE_MANUAL);
850 gtk_widget_set_visible(g->preserve_colors, p->tonecurve_autoscale_ab == DT_S_SCALE_AUTOMATIC_RGB);
851
852 gtk_widget_queue_draw(self->widget);
853 }
854}
855
857{
858 if(dt_gui_widgets_suppressed()) return;
861 const int combo = dt_bauhaus_combobox_get(widget);
862 if(combo == 0) p->tonecurve_type[ch_L] = p->tonecurve_type[ch_a] = p->tonecurve_type[ch_b] = CUBIC_SPLINE;
863 if(combo == 1) p->tonecurve_type[ch_L] = p->tonecurve_type[ch_a] = p->tonecurve_type[ch_b] = CATMULL_ROM;
864 if(combo == 2) p->tonecurve_type[ch_L] = p->tonecurve_type[ch_a] = p->tonecurve_type[ch_b] = MONOTONE_HERMITE;
865 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
866 gtk_widget_queue_draw(GTK_WIDGET(g->area));
867}
868
869static void tab_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data)
870{
871 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
873 if(dt_gui_widgets_suppressed()) return;
874 c->channel = (tonecurve_channel_t)page_num;
875 gtk_widget_queue_draw(self->widget);
876}
877
878static gboolean area_resized(GtkWidget *widget, GdkEvent *event, gpointer user_data)
879{
880 GtkRequisition r;
881 GtkAllocation allocation;
882 gtk_widget_get_allocation(widget, &allocation);
883 r.width = allocation.width;
884 r.height = allocation.width;
885 gtk_widget_get_preferred_size(widget, &r, NULL);
886 return TRUE;
887}
888
889static float to_log(const float x, const float base, const int ch, const int semilog, const int is_ordinate)
890{
891 // don't log-encode the a and b channels
892 if(base > 0.0f && ch == ch_L)
893 {
894 if (semilog == 1 && is_ordinate == 1)
895 {
896 // we don't want log on ordinate axis in semilog x
897 return x;
898 }
899 else if (semilog == -1 && is_ordinate == 0)
900 {
901 // we don't want log on abcissa axis in semilog y
902 return x;
903 }
904 else
905 {
906 return logf(x * base + 1.0f) / logf(base + 1.0f);
907 }
908 }
909 else
910 {
911 return x;
912 }
913}
914
915static float to_lin(const float x, const float base, const int ch, const int semilog, const int is_ordinate)
916{
917 // don't log-encode the a and b channels
918 if(base > 0.0f && ch == ch_L)
919 {
920 if (semilog == 1 && is_ordinate == 1)
921 {
922 // we don't want log on ordinate axis in semilog x
923 return x;
924 }
925 else if (semilog == -1 && is_ordinate == 0)
926 {
927 // we don't want log on abcissa axis in semilog y
928 return x;
929 }
930 else
931 {
932 return (powf(base + 1.0f, x) - 1.0f) / base;
933 }
934 }
935 else
936 {
937 return x;
938 }
939}
940
942{
943 (void)pipe;
945
946 for(int k=0; k<3; k++)
947 {
948 gd->picked_color[k] = self->picked_color[k];
949 gd->picked_color_min[k] = self->picked_color_min[k];
950 gd->picked_color_max[k] = self->picked_color_max[k];
952 }
954}
955
957{
960
961 int ch = c->channel;
962 int nodes = p->tonecurve_nodes[ch];
963 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
964 int autoscale_ab = p->tonecurve_autoscale_ab;
965
966 // if autoscale_ab is on: do not modify a and b curves
967 if((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L) return;
968
969 if(nodes <= 2) return;
970
971 const float mx = tonecurve[c->selected].x;
972
973 // delete vertex if order has changed
974 // for all points, x coordinate of point must be strictly larger than
975 // the x coordinate of the previous point
976 if((c->selected > 0 && (tonecurve[c->selected - 1].x >= mx))
977 || (c->selected < nodes - 1 && (tonecurve[c->selected + 1].x <= mx)))
978 {
979 for(int k = c->selected; k < nodes - 1; k++)
980 {
981 tonecurve[k].x = tonecurve[k + 1].x;
982 tonecurve[k].y = tonecurve[k + 1].y;
983 }
984 c->selected = -2; // avoid re-insertion of that point immediately after this
985 p->tonecurve_nodes[ch]--;
986 }
987}
988
989static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
990{
993
994 int ch = c->channel;
995 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
996
997 tonecurve[c->selected].x = CLAMP(tonecurve[c->selected].x + dx, 0.0f, 1.0f);
998 tonecurve[c->selected].y = CLAMP(tonecurve[c->selected].y + dy, 0.0f, 1.0f);
999
1000 dt_iop_tonecurve_sanity_check(self, widget);
1001
1002 gtk_widget_queue_draw(widget);
1003
1004 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1005 return TRUE;
1006}
1007
1008#define TONECURVE_DEFAULT_STEP (0.001f)
1009
1010static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
1011{
1012 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1015
1016 int ch = c->channel;
1017 int autoscale_ab = p->tonecurve_autoscale_ab;
1018
1019 // if autoscale_ab is on: do not modify a and b curves
1020 if((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L) return TRUE;
1021
1022 if(c->selected < 0) return TRUE;
1023
1024 gdouble delta_y;
1025 if(dt_gui_get_scroll_delta(event, &delta_y))
1026 {
1027 delta_y *= -TONECURVE_DEFAULT_STEP;
1028 return _move_point_internal(self, widget, 0.0, delta_y, event->state);
1029 }
1030
1031 return TRUE;
1032}
1033
1034static gboolean dt_iop_tonecurve_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
1035{
1036 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1039
1040 int ch = c->channel;
1041 int autoscale_ab = p->tonecurve_autoscale_ab;
1042
1043 // if autoscale_ab is on: do not modify a and b curves
1044 if((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L) return FALSE;
1045
1046 if(c->selected < 0) return FALSE;
1047
1048 guint key = dt_keys_mainpad_alternatives(event->keyval);
1049 int handled = 0;
1050 float dx = 0.0f, dy = 0.0f;
1051 if(key == GDK_KEY_Up)
1052 {
1053 handled = 1;
1055 }
1056 else if(key == GDK_KEY_Down)
1057 {
1058 handled = 1;
1060 }
1061 else if(key == GDK_KEY_Right)
1062 {
1063 handled = 1;
1065 }
1066 else if(key == GDK_KEY_Left)
1067 {
1068 handled = 1;
1070 }
1071
1072 if(!handled) return FALSE;
1073
1074 return _move_point_internal(self, widget, dx, dy, event->state);
1075}
1076
1077#undef TONECURVE_DEFAULT_STEP
1078
1079void gui_init(struct dt_iop_module_t *self)
1080{
1083
1084 for(int ch = 0; ch < ch_max; ch++)
1085 {
1086 c->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->tonecurve_type[ch]);
1087 c->minmax_curve_nodes[ch] = p->tonecurve_nodes[ch];
1088 c->minmax_curve_type[ch] = p->tonecurve_type[ch];
1089 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
1090 (void)dt_draw_curve_add_point(c->minmax_curve[ch], p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
1091 }
1092
1093 c->channel = ch_L;
1094 c->mouse_x = c->mouse_y = -1.0;
1095 c->selected = -1;
1096 c->loglogscale = 0;
1097 c->semilog = 0;
1098 self->timeout_handle = 0;
1099
1100 c->autoscale_ab = dt_bauhaus_combobox_from_params(self, "tonecurve_autoscale_ab");
1101 gtk_widget_set_tooltip_text(c->autoscale_ab, _("if set to auto, a and b curves have no effect and are "
1102 "not displayed. chroma values (a and b) of each pixel are "
1103 "then adjusted based on L curve data. auto XYZ is similar "
1104 "but applies the saturation changes in XYZ space."));
1105 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1106
1107 c->channel_tabs = dt_ui_notebook_new();
1108 dt_ui_notebook_page(c->channel_tabs, N_("L"), _("tonecurve for L channel"));
1109 dt_ui_notebook_page(c->channel_tabs, N_("a"), _("tonecurve for a channel"));
1110 dt_ui_notebook_page(c->channel_tabs, N_("b"), _("tonecurve for b channel"));
1111 g_signal_connect(G_OBJECT(c->channel_tabs), "switch_page", G_CALLBACK(tab_switch), self);
1112 dt_ui_notebook_set_picker_owner(c->channel_tabs, self);
1113 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(c->channel_tabs), TRUE, TRUE, 0);
1114 gtk_box_pack_start(GTK_BOX(hbox), gtk_grid_new(), TRUE, TRUE, 0);
1115
1116 c->colorpicker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT_AREA, hbox);
1117 gtk_widget_set_tooltip_text(c->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
1118
1119 gtk_box_pack_start(GTK_BOX(self->widget), hbox, FALSE, FALSE, 0);
1120
1121 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
1122 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
1123 g_object_set_data(G_OBJECT(c->area), "iop-instance", self);
1124 gtk_box_pack_start(GTK_BOX(self->widget),
1125 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
1126 "plugins/darkroom/tonecurve/graphheight", 280, 100),
1127 FALSE, FALSE, 0);
1128
1129 // FIXME: that tooltip goes in the way of the numbers when you hover a node to get a reading
1130 //gtk_widget_set_tooltip_text(GTK_WIDGET(c->area), _("double click to reset curve"));
1131
1132 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK | dt_widget_scroll_mask()
1133 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1134 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1135 gtk_widget_set_can_focus(GTK_WIDGET(c->area), TRUE);
1136 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(dt_iop_tonecurve_draw), self);
1137 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(dt_iop_tonecurve_button_press), self);
1138 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(dt_iop_tonecurve_motion_notify), self);
1139 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(dt_iop_tonecurve_leave_notify), self);
1140 g_signal_connect(G_OBJECT(c->area), "enter-notify-event", G_CALLBACK(dt_iop_tonecurve_enter_notify), self);
1141 g_signal_connect(G_OBJECT(c->area), "configure-event", G_CALLBACK(area_resized), self);
1142 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(_scrolled), self);
1143 g_signal_connect(G_OBJECT(c->area), "key-press-event", G_CALLBACK(dt_iop_tonecurve_key_press), self);
1144
1145 /* From src/common/curve_tools.h :
1146 #define CUBIC_SPLINE 0
1147 #define CATMULL_ROM 1
1148 #define MONOTONE_HERMITE 2
1149 */
1151 dt_bauhaus_widget_set_label(c->interpolator, N_("interpolation method"));
1152 dt_bauhaus_combobox_add(c->interpolator, _("cubic spline"));
1153 dt_bauhaus_combobox_add(c->interpolator, _("centripetal spline"));
1154 dt_bauhaus_combobox_add(c->interpolator, _("monotonic spline"));
1155 gtk_box_pack_start(GTK_BOX(self->widget), c->interpolator , TRUE, TRUE, 0);
1156 gtk_widget_set_tooltip_text(c->interpolator, _("change this method if you see oscillations or cusps in the curve\n"
1157 "- cubic spline is better to produce smooth curves but oscillates when nodes are too close\n"
1158 "- centripetal is better to avoids cusps and oscillations with close nodes but is less smooth\n"
1159 "- monotonic is better for accuracy of pure analytical functions (log, gamma, exp)\n"));
1160 g_signal_connect(G_OBJECT(c->interpolator), "value-changed", G_CALLBACK(interpolator_callback), self);
1161
1162 c->preserve_colors = dt_bauhaus_combobox_from_params(self, "preserve_colors");
1163 gtk_widget_set_tooltip_text(c->preserve_colors, _("method to preserve colors when applying contrast"));
1164
1165 c->logbase = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 40.0f, 0, 0.0f, 2);
1166 dt_bauhaus_widget_set_label(c->logbase, N_("scale for graph"));
1167 gtk_box_pack_start(GTK_BOX(self->widget), c->logbase , TRUE, TRUE, 0);
1168 g_signal_connect(G_OBJECT(c->logbase), "value-changed", G_CALLBACK(logbase_callback), self);
1169
1170 c->sizegroup = GTK_SIZE_GROUP(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL));
1171 gtk_size_group_add_widget(c->sizegroup, GTK_WIDGET(c->area));
1172 gtk_size_group_add_widget(c->sizegroup, GTK_WIDGET(c->channel_tabs));
1173}
1174
1176{
1178 // this one we need to unref manually. not so the initially unowned widgets.
1179 g_object_unref(c->sizegroup);
1180 dt_draw_curve_destroy(c->minmax_curve[ch_L]);
1181 dt_draw_curve_destroy(c->minmax_curve[ch_a]);
1182 dt_draw_curve_destroy(c->minmax_curve[ch_b]);
1183
1185}
1186
1187static gboolean dt_iop_tonecurve_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1188{
1189 gtk_widget_queue_draw(widget);
1190 return TRUE;
1191}
1192
1193static gboolean dt_iop_tonecurve_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1194{
1195 gtk_widget_queue_draw(widget);
1196 return TRUE;
1197}
1198
1199static void picker_scale(const float *in, float *out)
1200{
1201 out[0] = CLAMP(in[0] / 100.0f, 0.0f, 1.0f);
1202 out[1] = CLAMP((in[1] + 128.0f) / 256.0f, 0.0f, 1.0f);
1203 out[2] = CLAMP((in[2] + 128.0f) / 256.0f, 0.0f, 1.0f);
1204}
1205
1206static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1207{
1208 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1212
1213 int ch = c->channel;
1214 int nodes = p->tonecurve_nodes[ch];
1215 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
1216
1217 if(c->minmax_curve_type[ch] != p->tonecurve_type[ch] || c->minmax_curve_nodes[ch] != p->tonecurve_nodes[ch])
1218 {
1219 dt_draw_curve_destroy(c->minmax_curve[ch]);
1220 c->minmax_curve[ch] = dt_draw_curve_new(0.0, 1.0, p->tonecurve_type[ch]);
1221 c->minmax_curve_nodes[ch] = p->tonecurve_nodes[ch];
1222 c->minmax_curve_type[ch] = p->tonecurve_type[ch];
1223 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
1224 (void)dt_draw_curve_add_point(c->minmax_curve[ch], p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
1225 }
1226 else
1227 {
1228 for(int k = 0; k < p->tonecurve_nodes[ch]; k++)
1229 dt_draw_curve_set_point(c->minmax_curve[ch], k, p->tonecurve[ch][k].x, p->tonecurve[ch][k].y);
1230 }
1231 dt_draw_curve_t *minmax_curve = c->minmax_curve[ch];
1232 dt_draw_curve_calc_values(minmax_curve, 0.0, 1.0, DT_IOP_TONECURVE_RES, c->draw_xs, c->draw_ys);
1233
1234 float unbounded_coeffs[3];
1235 const float xm = tonecurve[nodes - 1].x;
1236 {
1237 const float x[4] = { 0.7f * xm, 0.8f * xm, 0.9f * xm, 1.0f * xm };
1238 const float y[4] = { c->draw_ys[CLAMP((int)(x[0] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1239 c->draw_ys[CLAMP((int)(x[1] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1240 c->draw_ys[CLAMP((int)(x[2] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1241 c->draw_ys[CLAMP((int)(x[3] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)] };
1243 }
1244
1245 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1246 GtkAllocation allocation;
1247 gtk_widget_get_allocation(widget, &allocation);
1248 int width = allocation.width, height = allocation.height;
1249 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1250 cairo_t *cr = cairo_create(cst);
1251
1252 cairo_translate(cr, inset, inset);
1253 width -= 2 * inset;
1254 height -= 2 * inset;
1255 char text[256];
1256
1257 // Draw frame borders
1258 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(0.5));
1259 set_color(cr, dt_bauhaus_get_global()->graph_border);
1260 cairo_rectangle(cr, 0, 0, width, height);
1261 cairo_stroke_preserve(cr);
1262
1263 if (ch==ch_L)
1264 { // remove below black to white transition to improve readability of the graph
1265 cairo_set_source_rgb(cr, .3, .3, .3);
1266 cairo_rectangle(cr, 0, 0, width, height);
1267 cairo_fill(cr);
1268 }
1269 else
1270 {
1271 // Draw the background gradient along the diagonal
1272 // ch == 0 : black to white
1273 // ch == 1 : green to magenta
1274 // ch == 2 : blue to yellow
1275 const float origin[3][3] = { { 0.0f, 0.0f, 0.0f }, // L = 0, @ (a, b) = 0
1276 { 0.0f, 231.0f/255.0f, 181.0f/255.0f },// a = -128 @ L = 75, b = 0
1277 { 0.0f, 30.0f/255.0f, 195.0f/255.0f}}; // b = -128 @ L = 75, a = 0
1278
1279 const float destin[3][3] = { { 1.0f, 1.0f, 1.0f }, // L = 100 @ (a, b) = 0
1280 { 1.0f, 0.0f, 192.0f/255.0f } , // a = 128 @ L = 75, b = 0
1281 { 215.0f/255.0f, 182.0f/255.0f, 0.0f}};// b = 128 @ L = 75, a = 0
1282
1283 // since Cairo paints with sRGB at gamma 2.4, linear gradients are not linear and, at 50%,
1284 // we don't see the neutral grey we would expect in the middle of a linear gradient between
1285 // 2 complimentary colors. So we add it artificially, but that will break the smoothness
1286 // of the transition.
1287
1288 // middle step for gradients (50 %)
1289 const float midgrey = to_log(0.45f, c->loglogscale, ch, c->semilog, 0);
1290
1291 const float middle[3][3] = { { midgrey, midgrey, midgrey }, // L = 50 @ (a, b) = 0
1292 { 0.67f, 0.67f, 0.67f}, // L = 75 @ (a, b) = 0
1293 { 0.67f, 0.67f, 0.67f}}; // L = 75 @ (a, b) = 0
1294
1295 const float opacities[3] = { 0.5f, 0.5f, 0.5f};
1296
1297 cairo_pattern_t *pat;
1298 pat = cairo_pattern_create_linear (height, 0.0, 0.0, width);
1299 cairo_pattern_add_color_stop_rgba (pat, 1, origin[ch][0], origin[ch][1], origin[ch][2], opacities[ch]);
1300 cairo_pattern_add_color_stop_rgba (pat, 0.5, middle[ch][0], middle[ch][1], middle[ch][2], opacities[ch]);
1301 cairo_pattern_add_color_stop_rgba (pat, 0, destin[ch][0], destin[ch][1], destin[ch][2], opacities[ch]);
1302 cairo_set_source (cr, pat);
1303 cairo_fill (cr);
1304 cairo_pattern_destroy (pat);
1305 }
1306
1307 // draw grid
1308 set_color(cr, dt_bauhaus_get_global()->graph_border);
1309
1310 if (c->loglogscale > 0.0f && ch == ch_L )
1311 {
1312 if (c->semilog == 0)
1313 {
1314 dt_draw_loglog_grid(cr, 4, 0, height, width, 0, c->loglogscale + 1.0f);
1315 }
1316 else if (c->semilog == 1)
1317 {
1318 dt_draw_semilog_x_grid(cr, 4, 0, height, width, 0, c->loglogscale + 1.0f);
1319 }
1320 else if (c->semilog == -1)
1321 {
1322 dt_draw_semilog_y_grid(cr, 4, 0, height, width, 0, c->loglogscale + 1.0f);
1323 }
1324 }
1325 else
1326 {
1327 dt_draw_grid(cr, 4, 0, 0, width, height);
1328 }
1329
1330 // draw identity line
1331 cairo_move_to(cr, 0, height);
1332 cairo_line_to(cr, width, 0);
1333 cairo_stroke(cr);
1334 cairo_translate(cr, 0, height);
1335
1336 // draw histogram in background
1337 // only if module is enabled
1338 if(self->enabled)
1339 {
1340 float *raw_mean, *raw_min, *raw_max;
1341 float *raw_mean_output;
1342 dt_aligned_pixel_t picker_mean, picker_min, picker_max;
1343 const gboolean is_linear = FALSE;
1344
1345 raw_mean = gd->picked_color;
1346 raw_min = gd->picked_color_min;
1347 raw_max = gd->picked_color_max;
1348 raw_mean_output = gd->picked_output_color;
1349
1350 const uint32_t *hist = self->histogram;
1351 const float hist_max = is_linear ? self->histogram_max[ch] : logf(1.0 + self->histogram_max[ch]);
1352 if(!IS_NULL_PTR(hist) && hist_max > 0.0f)
1353 {
1354 cairo_save(cr);
1355 cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
1356 cairo_move_to(cr, 0, height);
1357 set_color(cr, dt_bauhaus_get_global()->inset_histogram);
1358
1359 if (ch == ch_L && c->loglogscale > 0.0f)
1360 {
1361 dt_draw_histogram_8_log_base(cr, hist, 4, ch, is_linear, c->loglogscale + 1.0f);
1362 }
1363 else
1364 {
1365 dt_draw_histogram_8(cr, hist, 4, ch, is_linear);
1366 }
1367 cairo_restore(cr);
1368 }
1369
1370 cairo_move_to(cr, 0, height);
1372 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker)))
1373 {
1374 // the global live samples ...
1375 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
1376
1377 for(GSList *samples = self->dev->color_picker.samples; samples; samples = g_slist_next(samples))
1378 {
1379 dt_colorpicker_sample_t *sample = samples->data;
1380
1381 picker_scale(sample->lab[DT_LIB_COLORPICKER_STATISTIC_MEAN], picker_mean);
1384
1385 // Convert abcissa to log coordinates if needed
1386 picker_min[ch] = to_log(picker_min[ch], c->loglogscale, ch, c->semilog, 0);
1387 picker_max[ch] = to_log(picker_max[ch], c->loglogscale, ch, c->semilog, 0);
1388 picker_mean[ch] = to_log(picker_mean[ch], c->loglogscale, ch, c->semilog, 0);
1389
1390 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.35);
1391 cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
1392 -height);
1393 cairo_fill(cr);
1394 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.5);
1395 cairo_move_to(cr, width * picker_mean[ch], 0);
1396 cairo_line_to(cr, width * picker_mean[ch], -height);
1397 cairo_stroke(cr);
1398 }
1399
1400 // ... and the local sample
1401 if(raw_max[0] >= 0.0f)
1402 {
1403 cairo_save(cr);
1404 PangoLayout *layout;
1405 PangoRectangle ink;
1406 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
1407 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
1408 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
1409 layout = pango_cairo_create_layout(cr);
1410 pango_layout_set_font_description(layout, desc);
1411
1412 picker_scale(raw_mean, picker_mean);
1413 picker_scale(raw_min, picker_min);
1414 picker_scale(raw_max, picker_max);
1415
1416 // scale conservatively to 100% of width:
1417 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
1418 pango_layout_set_text(layout, text, -1);
1419 pango_layout_get_pixel_extents(layout, &ink, NULL);
1420 pango_font_description_set_absolute_size(desc, width*1.0/ink.width * PANGO_SCALE);
1421 pango_layout_set_font_description(layout, desc);
1422
1423 picker_min[ch] = to_log(picker_min[ch], c->loglogscale, ch, c->semilog, 0);
1424 picker_max[ch] = to_log(picker_max[ch], c->loglogscale, ch, c->semilog, 0);
1425 picker_mean[ch] = to_log(picker_mean[ch], c->loglogscale, ch, c->semilog, 0);
1426
1427 cairo_set_source_rgba(cr, 0.7, 0.5, 0.5, 0.35);
1428 cairo_rectangle(cr, width * picker_min[ch], 0, width * fmax(picker_max[ch] - picker_min[ch], 0.0f),
1429 -height);
1430 cairo_fill(cr);
1431 cairo_set_source_rgba(cr, 0.9, 0.7, 0.7, 0.5);
1432 cairo_move_to(cr, width * picker_mean[ch], 0);
1433 cairo_line_to(cr, width * picker_mean[ch], -height);
1434 cairo_stroke(cr);
1435
1436 snprintf(text, sizeof(text), "%.1f \342\206\222 %.1f", raw_mean[ch], raw_mean_output[ch]);
1437
1438 set_color(cr, dt_bauhaus_get_global()->graph_fg);
1439 cairo_set_font_size(cr, DT_PIXEL_APPLY_DPI(0.04) * height);
1440 pango_layout_set_text(layout, text, -1);
1441 pango_layout_get_pixel_extents(layout, &ink, NULL);
1442 cairo_move_to(cr, 0.02f * width, -0.94 * height - ink.height - ink.y);
1443 pango_cairo_show_layout(cr, layout);
1444 cairo_stroke(cr);
1445 pango_font_description_free(desc);
1446 g_object_unref(layout);
1447 cairo_restore(cr);
1448 }
1449 }
1450 }
1451
1452 // draw curve
1453 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
1454 set_color(cr, dt_bauhaus_get_global()->graph_fg);
1455
1456 for(int k = 0; k < DT_IOP_TONECURVE_RES; k++)
1457 {
1458 const float xx = k / (DT_IOP_TONECURVE_RES - 1.0f);
1459 float yy;
1460
1461 if(xx > xm)
1462 {
1464 }
1465 else
1466 {
1467 yy = c->draw_ys[k];
1468 }
1469
1470 const float x = to_log(xx, c->loglogscale, ch, c->semilog, 0),
1471 y = to_log(yy, c->loglogscale, ch, c->semilog, 1);
1472
1473 cairo_line_to(cr, x * width, -height * y);
1474 }
1475 cairo_stroke(cr);
1476
1477 // draw nodes positions
1478 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
1479 for(int k = 0; k < nodes; k++)
1480 {
1481 const float x = to_log(tonecurve[k].x, c->loglogscale, ch, c->semilog, 0),
1482 y = to_log(tonecurve[k].y, c->loglogscale, ch, c->semilog, 1);
1483
1484 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
1485 set_color(cr, dt_bauhaus_get_global()->graph_fg);
1486 cairo_stroke_preserve(cr);
1487 set_color(cr, dt_bauhaus_get_global()->graph_bg);
1488 cairo_fill(cr);
1489 }
1490
1491 // draw selected cursor
1492 if(c->selected >= 0)
1493 {
1494 // draw information about current selected node
1495 PangoLayout *layout;
1496 PangoRectangle ink;
1497 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
1498 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
1499 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
1500 layout = pango_cairo_create_layout(cr);
1501 pango_layout_set_font_description(layout, desc);
1502
1503 // scale conservatively to 100% of width:
1504 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
1505 pango_layout_set_text(layout, text, -1);
1506 pango_layout_get_pixel_extents(layout, &ink, NULL);
1507 pango_font_description_set_absolute_size(desc, width*1.0/ink.width * PANGO_SCALE);
1508 pango_layout_set_font_description(layout, desc);
1509
1510 const float min_scale_value = ch == ch_L ? 0.0f : -128.0f;
1511 const float max_scale_value = ch == ch_L ? 100.0f : 128.0f;
1512
1513 const float x_node_value = tonecurve[c->selected].x * (max_scale_value - min_scale_value) + min_scale_value;
1514 const float y_node_value = tonecurve[c->selected].y * (max_scale_value - min_scale_value) + min_scale_value;
1515 const float d_node_value = y_node_value - x_node_value;
1516 snprintf(text, sizeof(text), "%.1f / %.1f ( %+.1f)", x_node_value, y_node_value, d_node_value);
1517
1518 set_color(cr, dt_bauhaus_get_global()->graph_fg);
1519 pango_layout_set_text(layout, text, -1);
1520 pango_layout_get_pixel_extents(layout, &ink, NULL);
1521 cairo_move_to(cr, 0.98f * width - ink.width - ink.x, -0.02 * height - ink.height - ink.y);
1522 pango_cairo_show_layout(cr, layout);
1523 cairo_stroke(cr);
1524 pango_font_description_free(desc);
1525 g_object_unref(layout);
1526
1527 // enlarge selected node
1528 set_color(cr, dt_bauhaus_get_global()->graph_fg_active);
1529 const float x = to_log(tonecurve[c->selected].x, c->loglogscale, ch, c->semilog, 0),
1530 y = to_log(tonecurve[c->selected].y, c->loglogscale, ch, c->semilog, 1);
1531
1532 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(6), 0, 2. * M_PI);
1533 cairo_fill(cr);
1534 }
1535
1536 cairo_destroy(cr);
1537 cairo_set_source_surface(crf, cst, 0, 0);
1538 cairo_paint(crf);
1539 cairo_surface_destroy(cst);
1540 return TRUE;
1541}
1542
1543static inline int _add_node(dt_iop_tonecurve_node_t *tonecurve, int *nodes, float x, float y)
1544{
1545 int selected = -1;
1546 if(tonecurve[0].x > x)
1547 selected = 0;
1548 else
1549 {
1550 for(int k = 1; k < *nodes; k++)
1551 {
1552 if(tonecurve[k].x > x)
1553 {
1554 selected = k;
1555 break;
1556 }
1557 }
1558 }
1559 if(selected == -1) selected = *nodes;
1560 for(int i = *nodes; i > selected; i--)
1561 {
1562 tonecurve[i].x = tonecurve[i - 1].x;
1563 tonecurve[i].y = tonecurve[i - 1].y;
1564 }
1565 // found a new point
1566 tonecurve[selected].x = x;
1567 tonecurve[selected].y = y;
1568 (*nodes)++;
1569 return selected;
1570}
1571
1572static gboolean dt_iop_tonecurve_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1573{
1574 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1577
1578 int ch = c->channel;
1579 int nodes = p->tonecurve_nodes[ch];
1580 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
1581 int autoscale_ab = p->tonecurve_autoscale_ab;
1582
1583 // if autoscale_ab is on: do not modify a and b curves
1584 if((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L) goto finally;
1585
1586 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1587 GtkAllocation allocation;
1588 gtk_widget_get_allocation(widget, &allocation);
1589 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
1590 double old_m_x = c->mouse_x;
1591 double old_m_y = c->mouse_y;
1592 c->mouse_x = event->x - inset;
1593 c->mouse_y = event->y - inset;
1594
1595 const float mx = CLAMP(c->mouse_x, 0, width) / width;
1596 const float my = 1.0f - CLAMP(c->mouse_y, 0, height) / height;
1597 const float linx = to_lin(mx, c->loglogscale, ch, c->semilog, 0),
1598 liny = to_lin(my, c->loglogscale, ch, c->semilog, 1);
1599
1600 if(event->state & GDK_BUTTON1_MASK)
1601 {
1602 // got a vertex selected:
1603 if(c->selected >= 0)
1604 {
1605 // this is used to translate mause position in loglogscale to make this behavior unified with linear scale.
1606 const float translate_mouse_x = old_m_x / width - to_log(tonecurve[c->selected].x, c->loglogscale, ch, c->semilog, 0);
1607 const float translate_mouse_y = 1 - old_m_y / height - to_log(tonecurve[c->selected].y, c->loglogscale, ch, c->semilog, 1);
1608 // dx & dy are in linear coordinates
1609 const float dx = to_lin(c->mouse_x / width - translate_mouse_x, c->loglogscale, ch, c->semilog, 0)
1610 - to_lin(old_m_x / width - translate_mouse_x, c->loglogscale, ch, c->semilog, 0);
1611 const float dy = to_lin(1 - c->mouse_y / height - translate_mouse_y, c->loglogscale, ch, c->semilog, 1)
1612 - to_lin(1 - old_m_y / height - translate_mouse_y, c->loglogscale, ch, c->semilog, 1);
1613 return _move_point_internal(self, widget, dx, dy, event->state);
1614 }
1615 else if(nodes < DT_IOP_TONECURVE_MAXNODES && c->selected >= -1)
1616 {
1617 // no vertex was close, create a new one!
1618 c->selected = _add_node(tonecurve, &p->tonecurve_nodes[ch], linx, liny);
1619 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1620 }
1621 }
1622 else
1623 {
1624 // minimum area around the node to select it:
1625 float min = .04f;
1626 min *= min; // comparing against square
1627 int nearest = -1;
1628 for(int k = 0; k < nodes; k++)
1629 {
1630 float dist
1631 = (my - to_log(tonecurve[k].y, c->loglogscale, ch, c->semilog, 1)) * (my - to_log(tonecurve[k].y, c->loglogscale, ch, c->semilog, 1))
1632 + (mx - to_log(tonecurve[k].x, c->loglogscale, ch, c->semilog, 0)) * (mx - to_log(tonecurve[k].x, c->loglogscale, ch, c->semilog, 0));
1633 if(dist < min)
1634 {
1635 min = dist;
1636 nearest = k;
1637 }
1638 }
1639 c->selected = nearest;
1640 }
1641finally:
1642 if(c->selected >= 0) gtk_widget_grab_focus(widget);
1643 gtk_widget_queue_draw(widget);
1644 return TRUE;
1645}
1646
1647static gboolean dt_iop_tonecurve_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1648{
1649 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1653
1654 int ch = c->channel;
1655 int autoscale_ab = p->tonecurve_autoscale_ab;
1656 int nodes = p->tonecurve_nodes[ch];
1657 dt_iop_tonecurve_node_t *tonecurve = p->tonecurve[ch];
1658
1659 if(event->button == 1)
1660 {
1661 if(event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, GDK_CONTROL_MASK)
1662 && nodes < DT_IOP_TONECURVE_MAXNODES && c->selected == -1)
1663 {
1664 // 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
1665 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1666 GtkAllocation allocation;
1667 gtk_widget_get_allocation(widget, &allocation);
1668 int width = allocation.width - 2 * inset;
1669 c->mouse_x = event->x - inset;
1670 c->mouse_y = event->y - inset;
1671
1672 const float mx = CLAMP(c->mouse_x, 0, width) / (float)width;
1673 const float linx = to_lin(mx, c->loglogscale, ch, c->semilog, 0);
1674
1675 // don't add a node too close to others in x direction, it can crash dt
1676 int selected = -1;
1677 if(tonecurve[0].x > mx)
1678 selected = 0;
1679 else
1680 {
1681 for(int k = 1; k < nodes; k++)
1682 {
1683 if(tonecurve[k].x > mx)
1684 {
1685 selected = k;
1686 break;
1687 }
1688 }
1689 }
1690 if(selected == -1) selected = nodes;
1691 // > 0 -> check distance to left neighbour
1692 // < nodes -> check distance to right neighbour
1693 if(!((selected > 0 && linx - tonecurve[selected - 1].x <= 0.025) ||
1694 (selected < nodes && tonecurve[selected].x - linx <= 0.025)))
1695 {
1696 // evaluate the curve at the current x position
1697 const float y = dt_draw_curve_calc_value(c->minmax_curve[ch], linx);
1698
1699 if(y >= 0.0 && y <= 1.0) // never add something outside the viewport, you couldn't change it afterwards
1700 {
1701 // create a new node
1702 selected = _add_node(tonecurve, &p->tonecurve_nodes[ch], linx, y);
1703
1704 // maybe set the new one as being selected
1705 float min = .04f;
1706 min *= min; // comparing against square
1707 for(int k = 0; k < nodes; k++)
1708 {
1709 float other_y = to_log(tonecurve[k].y, c->loglogscale, ch, c->semilog, 1);
1710 float dist = (y - other_y) * (y - other_y);
1711 if(dist < min) c->selected = selected;
1712 }
1713
1714 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1715 gtk_widget_queue_draw(self->widget);
1716 }
1717 }
1718 return TRUE;
1719 }
1720 else if(event->type == GDK_2BUTTON_PRESS)
1721 {
1722 // reset current curve
1723 // if autoscale_ab is on: allow only reset of L curve
1724 if(!((autoscale_ab != DT_S_SCALE_MANUAL) && ch != ch_L))
1725 {
1726 p->tonecurve_nodes[ch] = d->tonecurve_nodes[ch];
1727 p->tonecurve_type[ch] = d->tonecurve_type[ch];
1728 for(int k = 0; k < d->tonecurve_nodes[ch]; k++)
1729 {
1730 p->tonecurve[ch][k].x = d->tonecurve[ch][k].x;
1731 p->tonecurve[ch][k].y = d->tonecurve[ch][k].y;
1732 }
1733 c->selected = -2; // avoid motion notify re-inserting immediately.
1734 dt_bauhaus_combobox_set(c->interpolator, p->tonecurve_type[ch_L]);
1735 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1736 gtk_widget_queue_draw(self->widget);
1737 }
1738 else
1739 {
1740 if(ch != ch_L)
1741 {
1742 p->tonecurve_autoscale_ab = DT_S_SCALE_MANUAL;
1743 c->selected = -2; // avoid motion notify re-inserting immediately.
1744 dt_bauhaus_combobox_set(c->autoscale_ab, 1);
1745 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1746 gtk_widget_queue_draw(self->widget);
1747 }
1748 }
1749 return TRUE;
1750 }
1751 }
1752 else if(event->button == 3 && c->selected >= 0)
1753 {
1754 if(c->selected == 0 || c->selected == nodes - 1)
1755 {
1756 float reset_value = c->selected == 0 ? 0 : 1;
1757 tonecurve[c->selected].y = tonecurve[c->selected].x = reset_value;
1758 gtk_widget_queue_draw(self->widget);
1759 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1760 return TRUE;
1761 }
1762
1763 for(int k = c->selected; k < nodes - 1; k++)
1764 {
1765 tonecurve[k].x = tonecurve[k + 1].x;
1766 tonecurve[k].y = tonecurve[k + 1].y;
1767 }
1768 tonecurve[nodes - 1].x = tonecurve[nodes - 1].y = 0;
1769 c->selected = -2; // avoid re-insertion of that point immediately after this
1770 p->tonecurve_nodes[ch]--;
1771 gtk_widget_queue_draw(self->widget);
1772 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1773 return TRUE;
1774 }
1775 return FALSE;
1776}
1777
1778// clang-format off
1779// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1780// vim: shiftwidth=2 expandtab tabstop=2 cindent
1781// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1782// 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:3275
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2131
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3326
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2085
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
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:1631
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1693
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1819
@ DEVELOP_BLEND_CS_RGB_DISPLAY
Definition blend.h:59
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ 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:46
@ DT_LIB_COLORPICKER_STATISTIC_MIN
Definition colorpicker.h:45
@ DT_LIB_COLORPICKER_STATISTIC_MEAN
Definition colorpicker.h:44
dt_iop_order_iccprofile_info_t * dt_colorspaces_add_profile(const dt_colorspaces_color_profile_type_t profile_type, const char *profile_filename, const int intent)
Find-or-build the derived matrix/LUT data for a profile identity, memoised.
static const float x
static dt_aligned_pixel_t float *const const float unbounded_coeffs[3][3]
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
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)
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:913
#define CATMULL_ROM
Definition curve_tools.h:35
#define CUBIC_SPLINE
Definition curve_tools.h:34
#define MONOTONE_HERMITE
Definition curve_tools.h:36
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:668
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:43
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:323
static void set_color(cairo_t *cr, GdkRGBA color)
Definition draw.h:124
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:157
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:237
static float dt_draw_curve_calc_value(dt_draw_curve_t *c, const float x)
Definition draw.h:359
static void dt_draw_curve_destroy(dt_draw_curve_t *c)
Definition draw.h:296
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:457
static void dt_draw_curve_set_point(dt_draw_curve_t *c, const int num, const float x, const float y)
Definition draw.h:302
static int dt_draw_curve_add_point(dt_draw_curve_t *c, const float x, const float y)
Definition draw.h:378
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:221
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:468
static dt_draw_curve_t * dt_draw_curve_new(const float min, const float max, unsigned int type)
Definition draw.h:280
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:205
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
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:42
#define DT_GUI_MODULE(x)
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:327
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:3243
@ DT_REQUEST_COLORPICK_MODULE
Definition imageop.h:211
#define IOP_GUI_FREE
Definition imageop.h:608
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:182
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:181
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:183
@ IOP_GROUP_TONES
Definition imageop.h:151
#define IOP_GUI_ALLOC(module)
Definition imageop.h:605
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)
void init_global(dt_iop_module_so_t *module)
static const struct @48 preset_camera_curves[]
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)
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 IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:65
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Definition mem_alloc.h:122
static void * dt_calloc_align(size_t size)
Definition mem_alloc.h:129
#define dt_free(ptr)
Definition mem_alloc.h:97
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition notebook.c:88
GtkNotebook * dt_ui_notebook_new()
Definition notebook.c:83
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and report every "switch_page" to the hos...
Definition notebook.c:118
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:60
@ DT_REQUEST_ON
Definition pixelpipe.h:52
@ DT_DEV_PIXELPIPE_PREVIEW
Definition pixelpipe.h:44
@ DT_COLORSPACE_PROPHOTO_RGB
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
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.
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float uint32_t state[4]
const float r
lib_colorpicker_sample_statistics lab
Definition colorpicker.h:75
dt_dev_request_flags_t request_histogram
struct dt_iop_module_t *void * data
dt_dev_pixelpipe_type_t type
GSList * samples
Definition develop.h:398
struct dt_develop_t::@15 color_picker
Authoritative darkroom color-picker state.
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:314
GtkWidget * widget
Definition imageop.h:344
struct dt_develop_t * dev
Definition imageop.h:303
dt_iop_gui_data_t * gui_data
Definition imageop.h:318
uint32_t histogram_max[4]
Definition imageop.h:287
dt_iop_global_data_t * global_data
Definition imageop.h:321
dt_aligned_pixel_t picked_output_color
Definition imageop.h:281
gboolean enabled
Definition imageop.h:305
dt_aligned_pixel_t picked_color_min
Definition imageop.h:279
dt_aligned_pixel_t picked_color_max
Definition imageop.h:279
uint32_t * histogram
Definition imageop.h:283
guint timeout_handle
Definition imageop.h:378
dt_aligned_pixel_t picked_color
Definition imageop.h:279
dt_iop_params_t * params
Definition imageop.h:314
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
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
GtkWidget * notebook
#define __DT_CLONE_TARGETS__
gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
GdkEventMask dt_widget_scroll_mask(void)
gboolean dt_gui_widgets_suppressed(void)
#define DT_GUI_BOX_SPACING
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_PIXEL_APPLY_DPI(value)