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