Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
basecurve.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-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 Milan Knížek.
8 Copyright (C) 2010-2015 Pascal de Bruijn.
9 Copyright (C) 2010-2016, 2019 Tobias Ellinghaus.
10 Copyright (C) 2011 Brian Teague.
11 Copyright (C) 2011 Jochen Schroeder.
12 Copyright (C) 2011 Olivier Tribout.
13 Copyright (C) 2011 Robert Bieber.
14 Copyright (C) 2011-2012, 2014, 2016, 2019 Ulrich Pegelow.
15 Copyright (C) 2012 Richard Wonka.
16 Copyright (C) 2013, 2020 Aldric Renaudin.
17 Copyright (C) 2013-2014, 2018-2022 Pascal Obry.
18 Copyright (C) 2013-2017 Roman Lebedev.
19 Copyright (C) 2013 Thomas Pryds.
20 Copyright (C) 2014, 2017-2018, 2021 Dan Torop.
21 Copyright (C) 2014, 2019 parafin.
22 Copyright (C) 2015 Edouard Gomez.
23 Copyright (C) 2015 Pedro Côrte-Real.
24 Copyright (C) 2015, 2020-2021 Ralf Brown.
25 Copyright (C) 2015 Stefan Kauerauf.
26 Copyright (C) 2017 Dominik Markiewicz.
27 Copyright (C) 2017-2018 Heiko Bauke.
28 Copyright (C) 2017 Matthieu Moy.
29 Copyright (C) 2017 Peter Budai.
30 Copyright (C) 2018 Anders Bennehag.
31 Copyright (C) 2018, 2020-2023, 2025-2026 Aurélien PIERRE.
32 Copyright (C) 2018-2019 Edgardo Hoszowski.
33 Copyright (C) 2018 Lukas Schrangl.
34 Copyright (C) 2018 Maurizio Paglia.
35 Copyright (C) 2018 rawfiner.
36 Copyright (C) 2019 Andreas Schneider.
37 Copyright (C) 2019 Andrew Dodd.
38 Copyright (C) 2019 Andy Dodd.
39 Copyright (C) 2019-2022 Diederik Ter Rahe.
40 Copyright (C) 2019 emeikei.
41 Copyright (C) 2020 Chris Elston.
42 Copyright (C) 2020 GrahamByrnes.
43 Copyright (C) 2020 Hubert Kowalski.
44 Copyright (C) 2020 Tomasz Golinski.
45 Copyright (C) 2021 lhietal.
46 Copyright (C) 2022 Hanno Schwalm.
47 Copyright (C) 2022 Martin Bařinka.
48 Copyright (C) 2022 Philipp Lutz.
49
50 darktable is free software: you can redistribute it and/or modify
51 it under the terms of the GNU General Public License as published by
52 the Free Software Foundation, either version 3 of the License, or
53 (at your option) any later version.
54
55 darktable is distributed in the hope that it will be useful,
56 but WITHOUT ANY WARRANTY; without even the implied warranty of
57 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
58 GNU General Public License for more details.
59
60 You should have received a copy of the GNU General Public License
61 along with darktable. If not, see <http://www.gnu.org/licenses/>.
62*/
63#ifdef HAVE_CONFIG_H
65#include "develop/iop_profile.h"
68#include "widgets/gdkkeys.h"
69#include "config.h"
70#endif
71#include "widgets/bauhaus.h"
72#include "system/macros.h"
73#include "system/openmp.h"
75#include "system/mem_alloc.h"
77#include "database/database.h"
78#include "math/math.h"
79#include "pixel/rgb_norms.h"
80#include "develop/develop.h"
81#include "develop/imageop.h"
83#include "develop/imageop_gui.h"
84#include "develop/tiling.h"
85#include "widgets/draw.h"
86#include "gui/presets.h"
87
88#include "iop/iop_api.h"
89
90#include <assert.h>
91#include <gtk/gtk.h>
92#include <inttypes.h>
93#include <stdlib.h>
94#include <string.h>
95#include "widgets/scroll_wrap.h"
96#include "gui/screen_metrics.h"
97
98#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(5)
99#define DT_GUI_CURVE_INFL .3f
100#define DT_IOP_TONECURVE_RES 256
101#define MAXNODES 20
102
103
105
107{
108 float x; // $MIN: 0.0 $MAX: 1.0
109 float y; // $MIN: 0.0 $MAX: 1.0
111
113{
114 // three curves (c, ., .) with max number of nodes
115 // the other two are reserved, maybe we'll have cam rgb at some point.
117 int basecurve_nodes[3]; // $MIN: 0 $MAX: MAXNODES $DEFAULT: 0
118 int basecurve_type[3]; // $MIN: 0 $MAX: MONOTONE_HERMITE $DEFAULT: MONOTONE_HERMITE
119 int exposure_fusion; /* number of exposure fusion steps
120 $DEFAULT: 0 $DESCRIPTION: "fusion" */
121 float exposure_stops; /* number of stops between fusion images
122 $MIN: 0.01 $MAX: 4.0 $DEFAULT: 1.0 $DESCRIPTION: "exposure shift" */
123 float exposure_bias; /* whether to do exposure-fusion with over or under-exposure
124 $MIN: -1.0 $MAX: 1.0 $DEFAULT: 1.0 $DESCRIPTION: "exposure bias" */
125 dt_iop_rgb_norms_t preserve_colors; /* $DEFAULT: DT_RGB_NORM_LUMINANCE $DESCRIPTION: "preserve colors" */
127
129{
130 // three curves (c, ., .) with max number of nodes
131 // the other two are reserved, maybe we'll have cam rgb at some point.
135 int exposure_fusion; // number of exposure fusion steps
136 float exposure_stops; // number of stops between fusion images
137 float exposure_bias; // whether to do exposure-fusion with over or under-exposure
139
141{
142 // three curves (c, ., .) with max number of nodes
143 // the other two are reserved, maybe we'll have cam rgb at some point.
147 int exposure_fusion; // number of exposure fusion steps
148 float exposure_stops; // number of stops between fusion images
150
151// same but semantics/defaults changed
153
155{
156 // three curves (c, ., .) with max number of nodes
157 // the other two are reserved, maybe we'll have cam rgb at some point.
162
168
169int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
170 void *new_params, const int new_version)
171{
172 if(old_version == 1 && new_version == 6)
173 {
176
177 // start with a fresh copy of default parameters
178 // unfortunately default_params aren't inited at this stage.
180 { { 0.0, 0.0 }, { 1.0, 1.0 } },
181 },
182 { 2, 3, 3 },
184 for(int k = 0; k < 6; k++) n->basecurve[0][k].x = o->tonecurve_x[k];
185 for(int k = 0; k < 6; k++) n->basecurve[0][k].y = o->tonecurve_y[k];
186 n->basecurve_nodes[0] = 6;
187 n->basecurve_type[0] = CUBIC_SPLINE;
188 n->exposure_fusion = 0;
189 n->exposure_stops = 1;
190 n->exposure_bias = 1.0;
191 n->preserve_colors = DT_RGB_NORM_NONE;
192 return 0;
193 }
194 if(old_version == 2 && new_version == 6)
195 {
198 memcpy(n, o, sizeof(dt_iop_basecurve_params2_t));
199 n->exposure_fusion = 0;
200 n->exposure_stops = 1;
201 n->exposure_bias = 1.0;
202 n->preserve_colors = DT_RGB_NORM_NONE;
203 return 0;
204 }
205 if(old_version == 3 && new_version == 6)
206 {
209 memcpy(n, o, sizeof(dt_iop_basecurve_params3_t));
210 n->exposure_stops = (o->exposure_fusion == 0 && o->exposure_stops == 0) ? 1.0f : o->exposure_stops;
211 n->exposure_bias = 1.0;
212 n->preserve_colors = DT_RGB_NORM_NONE;
213 return 0;
214 }
215 if(old_version == 4 && new_version == 6)
216 {
219 memcpy(n, o, sizeof(dt_iop_basecurve_params4_t));
220 n->exposure_bias = 1.0;
221 n->preserve_colors = DT_RGB_NORM_NONE;
222 return 0;
223 }
224 if(old_version == 5 && new_version == 6)
225 {
228 memcpy(n, o, sizeof(dt_iop_basecurve_params5_t));
229 n->preserve_colors = DT_RGB_NORM_NONE;
230 return 0;
231 }
232 return 1;
233}
234
252
253static const char neutral[] = N_("neutral");
254static const char canon_eos[] = N_("canon eos like");
255static const char canon_eos_alt[] = N_("canon eos like alternate");
256static const char nikon[] = N_("nikon like");
257static const char nikon_alt[] = N_("nikon like alternate");
258static const char sony_alpha[] = N_("sony alpha like");
259static const char pentax[] = N_("pentax like");
260static const char ricoh[] = N_("ricoh like");
261static const char olympus[] = N_("olympus like");
262static const char olympus_alt[] = N_("olympus like alternate");
263static const char panasonic[] = N_("panasonic like");
264static const char leica[] = N_("leica like");
265static const char kodak_easyshare[] = N_("kodak easyshare like");
266static const char konica_minolta[] = N_("konica minolta like");
267static const char samsung[] = N_("samsung like");
268static const char fujifilm[] = N_("fujifilm like");
269static const char nokia[] = N_("nokia like");
270
282
283#define m MONOTONE_HERMITE
284
286 // copy paste your measured basecurve line at the top here, like so (note the exif data and the last 1):
287 // clang-format off
288
289 // nikon d750 by Edouard Gomez
290 {"Nikon D750", "NIKON CORPORATION", "NIKON D750", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.018124, 0.026126}, {0.143357, 0.370145}, {0.330116, 0.730507}, {0.457952, 0.853462}, {0.734950, 0.965061}, {0.904758, 0.985699}, {1.000000, 1.000000}}}, {8}, {m}, 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1},
291 // contributed by Stefan Kauerauf
292 {"Nikon D5100", "NIKON CORPORATION", "NIKON D5100", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.001113, 0.000506}, {0.002842, 0.001338}, {0.005461, 0.002470}, {0.011381, 0.006099}, {0.013303, 0.007758}, {0.034638, 0.041119}, {0.044441, 0.063882}, {0.070338, 0.139639}, {0.096068, 0.210915}, {0.137693, 0.310295}, {0.206041, 0.432674}, {0.255508, 0.504447}, {0.302770, 0.569576}, {0.425625, 0.726755}, {0.554526, 0.839541}, {0.621216, 0.882839}, {0.702662, 0.927072}, {0.897426, 0.990984}, {1.000000, 1.000000}}}, {20}, {m}, 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1},
293 // nikon d7000 by Edouard Gomez
294 {"Nikon D7000", "NIKON CORPORATION", "NIKON D7000", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.001943, 0.003040}, {0.019814, 0.028810}, {0.080784, 0.210476}, {0.145700, 0.383873}, {0.295961, 0.654041}, {0.651915, 0.952819}, {1.000000, 1.000000}}}, {8}, {m}, 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1},
295 // nikon d7200 standard by Ralf Brown (firmware 1.00)
296 {"Nikon D7200", "NIKON CORPORATION", "NIKON D7200", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.001604, 0.001334}, {0.007401, 0.005237}, {0.009474, 0.006890}, {0.017348, 0.017176}, {0.032782, 0.044336}, {0.048033, 0.086548}, {0.075803, 0.168331}, {0.109539, 0.273539}, {0.137373, 0.364645}, {0.231651, 0.597511}, {0.323797, 0.736475}, {0.383796, 0.805797}, {0.462284, 0.872247}, {0.549844, 0.918328}, {0.678855, 0.962361}, {0.817445, 0.990406}, {1.000000, 1.000000}}}, {18}, {m}, 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1},
297 // nikon d7500 by Anders Bennehag (firmware C 1.00, LD 2.016)
298 {"NIKON D7500", "NIKON CORPORATION", "NIKON D7500", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.000892, 0.001062}, {0.002280, 0.001768}, {0.013983, 0.011368}, {0.032597, 0.044700}, {0.050065, 0.097131}, {0.084129, 0.219954}, {0.120975, 0.336806}, {0.170730, 0.473752}, {0.258677, 0.647113}, {0.409997, 0.827417}, {0.499979, 0.889468}, {0.615564, 0.941960}, {0.665272, 0.957736}, {0.832126, 0.991968}, {1.000000, 1.000000}}}, {16}, {m}, 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1},
299 // sony rx100m2 by Günther R.
300 { "Sony DSC-RX100M2", "SONY", "DSC-RX100M2", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.015106, 0.008116 }, { 0.070077, 0.093725 }, { 0.107484, 0.170723 }, { 0.191528, 0.341093 }, { 0.257996, 0.458453 }, { 0.305381, 0.537267 }, { 0.326367, 0.569257 }, { 0.448067, 0.723742 }, { 0.509627, 0.777966 }, { 0.676751, 0.898797 }, { 1.000000, 1.000000 } } }, { 12 }, { m } , 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1 },
301 // contributed by matthias bodenbinder
302 { "Canon EOS 6D", "Canon", "Canon EOS 6D", 0, FLT_MAX, { { { { 0.000000, 0.002917 }, { 0.000751, 0.001716 }, { 0.006011, 0.004438 }, { 0.020286, 0.021725 }, { 0.048084, 0.085918 }, { 0.093914, 0.233804 }, { 0.162284, 0.431375 }, { 0.257701, 0.629218 }, { 0.384673, 0.800332 }, { 0.547709, 0.917761 }, { 0.751315, 0.988132 }, { 1.000000, 0.999943 } } }, { 12 }, { m } , 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1 },
303 // contributed by Dan Torop
304 { "Fujifilm X100S", "Fujifilm", "X100S", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.009145, 0.007905 }, { 0.026570, 0.032201 }, { 0.131526, 0.289717 }, { 0.175858, 0.395263 }, { 0.350981, 0.696899 }, { 0.614997, 0.959451 }, { 1.000000, 1.000000 } } }, { 8 }, { m } , 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1 },
305 { "Fujifilm X100T", "Fujifilm", "X100T", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.009145, 0.007905 }, { 0.026570, 0.032201 }, { 0.131526, 0.289717 }, { 0.175858, 0.395263 }, { 0.350981, 0.696899 }, { 0.614997, 0.959451 }, { 1.000000, 1.000000 } } }, { 8 }, { m } , 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1 },
306 // contributed by Johannes Hanika
307 { "Canon EOS 5D Mark II", "Canon", "Canon EOS 5D Mark II", 0, FLT_MAX, { { { { 0.000000, 0.000366 }, { 0.006560, 0.003504 }, { 0.027310, 0.029834 }, { 0.045915, 0.070230 }, { 0.206554, 0.539895 }, { 0.442337, 0.872409 }, { 0.673263, 0.971703 }, { 1.000000, 0.999832 } } }, { 8 }, { m } , 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1 },
308 // contributed by chrik5
309 { "Pentax K-5", "Pentax", "Pentax K-5", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.004754, 0.002208 }, { 0.009529, 0.004214 }, { 0.023713, 0.013508 }, { 0.031866, 0.020352 }, { 0.046734, 0.034063 }, { 0.059989, 0.052413 }, { 0.088415, 0.096030 }, { 0.136610, 0.190629 }, { 0.174480, 0.256484 }, { 0.205192, 0.307430 }, { 0.228896, 0.348447 }, { 0.286411, 0.428680 }, { 0.355314, 0.513527 }, { 0.440014, 0.607651 }, { 0.567096, 0.732791 }, { 0.620597, 0.775968 }, { 0.760355, 0.881828 }, { 0.875139, 0.960682 }, { 1.000000, 1.000000 } } }, { 20 }, { m } , 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1 },
310 // contributed by Togan Muftuoglu - ed: slope is too aggressive on shadows
311 //{ "Nikon D90", "NIKON", "D90", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.015520, 0.012248 }, { 0.097950, 0.251013 }, { 0.301515, 0.621951 }, { 0.415513, 0.771384 }, { 0.547326, 0.843079 }, { 0.819769, 0.956678 }, { 1.000000, 1.000000 } } }, { 8 }, { m } }, 0, 1 },
312 // contributed by Edouard Gomez
313 {"Nikon D90", "NIKON CORPORATION", "NIKON D90", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.011702, 0.012659}, {0.122918, 0.289973}, {0.153642, 0.342731}, {0.246855, 0.510114}, {0.448958, 0.733820}, {0.666759, 0.894290}, {1.000000, 1.000000}}}, {8}, {m}, 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1},
314 // contributed by Pascal Obry
315 { "Nikon D800", "NIKON", "NIKON D800", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.001773, 0.001936 }, { 0.009671, 0.009693 }, { 0.016754, 0.020617 }, { 0.024884, 0.037309 }, { 0.048174, 0.107768 }, { 0.056932, 0.139532 }, { 0.085504, 0.233303 }, { 0.130378, 0.349747 }, { 0.155476, 0.405445 }, { 0.175245, 0.445918 }, { 0.217657, 0.516873 }, { 0.308475, 0.668608 }, { 0.375381, 0.754058 }, { 0.459858, 0.839909 }, { 0.509567, 0.881543 }, { 0.654394, 0.960877 }, { 0.783380, 0.999161 }, { 0.859310, 1.000000 }, { 1.000000, 1.000000 } } }, { 20 }, { m } , 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1 },
316 // contributed by Lukas Schrangl
317 {"Olympus OM-D E-M10 II", "OLYMPUS CORPORATION ", "E-M10MarkII ", 0, FLT_MAX, {{{{0.000000, 0.000000}, {0.005707, 0.004764}, {0.018944, 0.024456}, {0.054501, 0.129992}, {0.075665, 0.211873}, {0.119641, 0.365771}, {0.173148, 0.532024}, {0.247979, 0.668989}, {0.357597, 0.780138}, {0.459003, 0.839829}, {0.626844, 0.904426}, {0.769425, 0.948541}, {0.820429, 0.964715}, {1.000000, 1.000000}}}, {14}, {m}, 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1},
318 // clang-format on
319};
321
323 // clang-format off
324 // smoother cubic spline curve
325 { N_("cubic spline"), "", "", 0, FLT_MAX, { { { { 0.0, 0.0}, { 1.0, 1.0 }, { 0., 0.}, { 0., 0.}, { 0., 0.}, { 0., 0.}, { 0., 0.}, { 0., 0.} } }, { 2 }, { CUBIC_SPLINE }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
326 { neutral, "", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.005000, 0.002500 }, { 0.150000, 0.300000 }, { 0.400000, 0.700000 }, { 0.750000, 0.950000 }, { 1.000000, 1.000000 } } }, { 6 }, { m } , 0, 0, 0, DT_RGB_NORM_LUMINANCE}, 0, 1 },
327 { canon_eos, "Canon", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.028226, 0.029677 }, { 0.120968, 0.232258 }, { 0.459677, 0.747581 }, { 0.858871, 0.967742 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
328 { canon_eos_alt, "Canon", "EOS 5D Mark%", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.026210, 0.029677 }, { 0.108871, 0.232258 }, { 0.350806, 0.747581 }, { 0.669355, 0.967742 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
329 { nikon, "NIKON", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.036290, 0.036532 }, { 0.120968, 0.228226 }, { 0.459677, 0.759678 }, { 0.858871, 0.983468 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
330 { nikon_alt, "NIKON", "%D____%", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.012097, 0.007322 }, { 0.072581, 0.130742 }, { 0.310484, 0.729291 }, { 0.611321, 0.951613 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
331 { sony_alpha, "SONY", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.031949, 0.036532 }, { 0.105431, 0.228226 }, { 0.434505, 0.759678 }, { 0.855738, 0.983468 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
332 { pentax, "PENTAX", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.032258, 0.024596 }, { 0.120968, 0.166419 }, { 0.205645, 0.328527 }, { 0.604839, 0.790171 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
333 { ricoh, "RICOH", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.032259, 0.024596 }, { 0.120968, 0.166419 }, { 0.205645, 0.328527 }, { 0.604839, 0.790171 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
334 { olympus, "OLYMPUS", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.033962, 0.028226 }, { 0.249057, 0.439516 }, { 0.501887, 0.798387 }, { 0.750943, 0.955645 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
335 { olympus_alt, "OLYMPUS", "E-M%", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.012097, 0.010322 }, { 0.072581, 0.167742 }, { 0.310484, 0.711291 }, { 0.645161, 0.956855 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
336 { panasonic, "Panasonic", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.036290, 0.024596 }, { 0.120968, 0.166419 }, { 0.205645, 0.328527 }, { 0.604839, 0.790171 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
337 { leica, "Leica", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.036291, 0.024596 }, { 0.120968, 0.166419 }, { 0.205645, 0.328527 }, { 0.604839, 0.790171 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
338 { kodak_easyshare, "EASTMAN KODAK COMPANY", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.044355, 0.020967 }, { 0.133065, 0.154322 }, { 0.209677, 0.300301 }, { 0.572581, 0.753477 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
339 { konica_minolta, "MINOLTA", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.020161, 0.010322 }, { 0.112903, 0.167742 }, { 0.500000, 0.711291 }, { 0.899194, 0.956855 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
340 { samsung, "SAMSUNG", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.040323, 0.029677 }, { 0.133065, 0.232258 }, { 0.447581, 0.747581 }, { 0.842742, 0.967742 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
341 { fujifilm, "FUJIFILM", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.028226, 0.029677 }, { 0.104839, 0.232258 }, { 0.387097, 0.747581 }, { 0.754032, 0.967742 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
342 { nokia, "Nokia", "", 0, FLT_MAX, { { { { 0.000000, 0.000000 }, { 0.041825, 0.020161 }, { 0.117871, 0.153226 }, { 0.319392, 0.500000 }, { 0.638783, 0.842742 }, { 1.000000, 1.000000 } } }, { 6 }, { m }, 0, 0, 0, DT_RGB_NORM_LUMINANCE }, 0, 0 },
343 // clang-format on
344};
345#undef m
346static const int basecurve_presets_cnt = sizeof(basecurve_presets) / sizeof(basecurve_preset_t);
347
349{
350 dt_draw_curve_t *curve; // curve for pixelpipe piece and pixel processing
353 float table[0x10000]; // precomputed look-up table for tone curve
354 float unbounded_coeffs[3]; // approximation for extrapolation
360
361
362const char *name()
363{
364 return _("base curve");
365}
366
367const char **description(struct dt_iop_module_t *self)
368{
369 return dt_iop_set_description(self, _("apply a view transform based on personal or camera manufacturer look,\n"
370 "for corrective purposes, to prepare images for display"),
371 _("corrective"),
372 _("linear, RGB, display-referred"),
373 _("non-linear, RGB"),
374 _("non-linear, RGB, display-referred"));
375}
376
378{
379 return IOP_GROUP_TONES;
380}
381
386
388{
389 return IOP_CS_RGB;
390}
391
392static void set_presets(dt_iop_module_so_t *self, const basecurve_preset_t *presets, int count, gboolean camera)
393{
394 const gboolean autoapply_percamera = FALSE;
395
396 const gboolean force_autoapply = (autoapply_percamera || !camera);
397
398 // transform presets above to db entries.
399 for(int k = 0; k < count; k++)
400 {
401 // disable exposure fusion if not explicitly inited in params struct definition above:
402 dt_iop_basecurve_params_t tmp = presets[k].params;
403 if(tmp.exposure_fusion == 0 && tmp.exposure_stops == 0.0f)
404 {
405 tmp.exposure_fusion = 0;
406 tmp.exposure_stops = 1.0f;
407 tmp.exposure_bias = 1.0f;
408 }
409 // add the preset.
410 dt_gui_presets_add_generic(_(presets[k].name), self->op, self->version(),
411 &tmp, sizeof(dt_iop_basecurve_params_t), 1);
412 // and restrict it to model, maker, iso, and raw images
413 dt_gui_presets_update_mml(_(presets[k].name), self->op, self->version(),
414 presets[k].maker, presets[k].model, "");
415 dt_gui_presets_update_iso(_(presets[k].name), self->op, self->version(),
416 presets[k].iso_min, presets[k].iso_max);
417 dt_gui_presets_update_ldr(_(presets[k].name), self->op, self->version(), FOR_RAW);
418 // make it auto-apply for matching images:
419 dt_gui_presets_update_autoapply(_(presets[k].name), self->op, self->version(),
420 force_autoapply ? 1 : presets[k].autoapply);
421 // hide all non-matching presets in case the model string is set.
422 // When force_autoapply was given always filter (as these are per-camera presets)
423 dt_gui_presets_update_filter(_(presets[k].name), self->op, self->version(), camera || presets[k].filter);
424 }
425}
426
438
439static inline __attribute__((always_inline)) float exposure_increment(float stops, int e, float fusion, float bias)
440{
441 float offset = stops * fusion * (bias - 1.0f) / 2.0f;
442 return powf(2.0f, stops * e + offset);
443}
444
445void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
446{
447 const dt_iop_roi_t *const roi_in = &piece->roi_in;
449
450 if(d->exposure_fusion)
451 {
452 const int rad = MIN(roi_in->width, (int)ceilf(256 * roi_in->scale));
453
454 tiling->factor = 6.666f; // in + out + col[] + comb[] + 2*tmp
455 tiling->maxbuf = 1.0f;
456 tiling->overhead = 0;
457 tiling->xalign = 1;
458 tiling->yalign = 1;
459 tiling->overlap = rad;
460 }
461 else
462 {
463 tiling->factor = 2.0f; // in + out
464 tiling->maxbuf = 1.0f;
465 tiling->overhead = 0;
466 tiling->xalign = 1;
467 tiling->yalign = 1;
468 tiling->overlap = 0;
469 }
470}
471
472// See comments of opencl version in data/kernels/basecurve.cl for description of the meaning of "legacy"
474static inline void apply_legacy_curve(
475 const float *const in,
476 float *const out,
477 const int width,
478 const int height,
479 const float mul,
480 const float *const table,
481 const float *const unbounded_coeffs)
482{
483 const size_t npixels = (size_t)width * height;
485 for(size_t k = 0; k < 4*npixels; k += 4)
486 {
487 for(int i = 0; i < 3; i++)
488 {
489 const float f = in[k+i] * mul;
490 // use base curve for values < 1, else use extrapolation.
491 if(f < 1.0f)
492 out[k+i] = fmaxf(table[CLAMP((int)(f * 0x10000ul), 0, 0xffff)], 0.f);
493 else
494 out[k+i] = fmaxf(dt_iop_eval_exp(unbounded_coeffs, f), 0.f);
495 }
496 out[k+3] = in[k+3];
497 }
498}
499
500// See description of the equivalent OpenCL function in data/kernels/basecurve.cl
502static inline void apply_curve(
503 const float *const in,
504 float *const out,
505 const int width,
506 const int height,
507 const int preserve_colors,
508 const float mul,
509 const float *const table,
510 const float *const unbounded_coeffs,
511 const dt_iop_order_iccprofile_info_t *const work_profile)
512{
513 const size_t npixels = (size_t)width * height;
515 for(size_t k = 0; k < 4*npixels; k += 4)
516 {
517 float ratio = 1.f;
518 // FIXME: Determine if we can get rid of the conditionals within this function in some way to improve performance.
519 // However, solving this one is much harder than the conditional for legacy vs. current
520 const float lum = mul * dt_rgb_norm(in+k, preserve_colors, work_profile);
521 if(lum > 0.f)
522 {
523 const float curve_lum = (lum < 1.0f)
524 ? table[CLAMP((int)(lum * 0x10000ul), 0, 0xffff)]
526 ratio = mul * curve_lum / lum;
527 }
528 for(size_t c = 0; c < 3; c++)
529 {
530 out[k+c] = fmaxf(ratio * in[k+c], 0.f);
531 }
532 out[k+3] = in[k+3];
533 }
534}
535
537static inline void compute_features(
538 float *const col,
539 const int wd,
540 const int ht)
541{
542 // features are product of
543 // 1) well exposedness
544 // 2) saturation
545 // 3) local contrast (handled in laplacian form later)
546 const size_t npixels = (size_t)wd * ht;
548 for(size_t x = 0; x < 4*npixels; x += 4)
549 {
550 const float max = MAX(col[x], MAX(col[x+1], col[x+2]));
551 const float min = MIN(col[x], MIN(col[x+1], col[x+2]));
552 const float sat = .1f + .1f*(max-min)/MAX(1e-4f, max);
553
554 const float c = 0.54f;
555 float v = fabsf(col[x]-c);
556 v = MAX(fabsf(col[x+1]-c), v);
557 v = MAX(fabsf(col[x+2]-c), v);
558 const float var = 0.5;
559 const float exp = .2f + dt_fast_expf(-v*v/(var*var));
560 col[x+3] = sat * exp;
561 }
562}
563
565static inline int gauss_blur(
566 const float *const input,
567 float *const output,
568 const size_t wd,
569 const size_t ht)
570{
571 const float w[5] = { 1.f / 16.f, 4.f / 16.f, 6.f / 16.f, 4.f / 16.f, 1.f / 16.f };
572 float *tmp = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * wd * ht, 0);
573 if(IS_NULL_PTR(tmp)) return 1;
574
575 memset(tmp, 0, sizeof(float) * 4 * wd * ht);
577 for(int j=0;j<ht;j++)
578 { // horizontal pass
579 // left borders
580 for(int i=0;i<2;i++) for(int c=0;c<4;c++)
581 for(int ii=-2;ii<=2;ii++)
582 tmp[4*(j*wd+i)+c] += input[4*(j*wd+MAX(-i-ii,i+ii))+c] * w[ii+2];
583 // most pixels
584 for(int i=2;i<wd-2;i++) for(int c=0;c<4;c++)
585 for(int ii=-2;ii<=2;ii++)
586 tmp[4*(j*wd+i)+c] += input[4*(j*wd+i+ii)+c] * w[ii+2];
587 // right borders
588 for(int i=wd-2;i<wd;i++) for(int c=0;c<4;c++)
589 for(int ii=-2;ii<=2;ii++)
590 tmp[4*(j*wd+i)+c] += input[4*(j*wd+MIN(i+ii, wd-(i+ii-wd+1) ))+c] * w[ii+2];
591 }
592 memset(output, 0, sizeof(float) * 4 * wd * ht);
594 for(int i=0;i<wd;i++)
595 { // vertical pass
596 for(int j=0;j<2;j++) for(int c=0;c<4;c++)
597 for(int jj=-2;jj<=2;jj++)
598 output[4*(j*wd+i)+c] += tmp[4*(MAX(-j-jj,j+jj)*wd+i)+c] * w[jj+2];
599 for(int j=2;j<ht-2;j++) for(int c=0;c<4;c++)
600 for(int jj=-2;jj<=2;jj++)
601 output[4*(j*wd+i)+c] += tmp[4*((j+jj)*wd+i)+c] * w[jj+2];
602 for(int j=ht-2;j<ht;j++) for(int c=0;c<4;c++)
603 for(int jj=-2;jj<=2;jj++)
604 output[4*(j*wd+i)+c] += tmp[4*(MIN(j+jj, ht-(j+jj-ht+1))*wd+i)+c] * w[jj+2];
605 }
607 return 0;
608}
609
611static inline int gauss_expand(
612 const float *const input, // coarse input
613 float *const fine, // upsampled, blurry output
614 const size_t wd, // fine res
615 const size_t ht)
616{
617 const size_t cw = (wd-1)/2+1;
618 // fill numbers in even pixels, zero odd ones
619 memset(fine, 0, sizeof(float) * 4 * wd * ht);
620 __OMP_PARALLEL_FOR__(collapse(2))
621 for(int j=0;j<ht;j+=2)
622 for(int i=0;i<wd;i+=2)
623 for(int c=0;c<4;c++)
624 fine[4*(j*wd+i)+c] = 4.0f * input[4*(j/2*cw + i/2)+c];
625
626 // convolve with same kernel weights mul by 4:
627 if(gauss_blur(fine, fine, wd, ht)) return 1;
628 return 0;
629}
630
631// XXX FIXME: we'll need to pad up the image to get a good boundary condition!
632// XXX FIXME: downsampling will not result in an energy conserving pattern (every 4 pixels one sample)
633// XXX FIXME: neither will a mirror boundary condition (mirrors in subsampled values at random density)
634// TODO: copy laplacian code from local laplacian filters, it's faster.
635static inline int gauss_reduce(
636 const float *const input, // fine input buffer
637 float *const coarse, // coarse scale, blurred input buf
638 float *const detail, // detail/laplacian, fine scale, or 0
639 const size_t wd,
640 const size_t ht)
641{
642 // blur, store only coarse res
643 const size_t cw = (wd-1)/2+1, ch = (ht-1)/2+1;
644
645 float *blurred = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * wd * ht, 0);
646 if(IS_NULL_PTR(blurred)) return 1;
647
648 if(gauss_blur(input, blurred, wd, ht))
649 {
651 return 1;
652 }
653 for(size_t j=0;j<ch;j++) for(size_t i=0;i<cw;i++)
654 for(int c=0;c<4;c++) coarse[4*(j*cw+i)+c] = blurred[4*(2*j*wd+2*i)+c];
656
657 if(!IS_NULL_PTR(detail))
658 {
659 // compute laplacian/details: expand coarse buffer into detail
660 // buffer subtract expanded buffer from input in place
661 if(gauss_expand(coarse, detail, wd, ht)) return 1;
662 for(size_t k=0;k<wd*ht*4;k++)
663 detail[k] = input[k] - detail[k];
664 }
665 return 0;
666}
667
669int process_fusion(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
670 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
671{
672 const float *const in = (const float *)ivoid;
673 float *const out = (float *)ovoid;
675 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_iop_work_profile_info(piece->module, piece->module->dev->iop);
676 int err = 0;
677
678 // allocate temporary buffer for wavelet transform + blending
679 const int wd = roi_in->width, ht = roi_in->height;
680 int num_levels = 8;
681 float **col = calloc(num_levels, sizeof(float *));
682 float **comb = calloc(num_levels, sizeof(float *));
683 if(IS_NULL_PTR(col) || IS_NULL_PTR(comb))
684 {
685 err = 1;
686 goto error;
687 }
688 int w = wd, h = ht;
689 const int rad = MIN(wd, (int)ceilf(256 * roi_in->scale));
690 int step = 1;
691 for(int k = 0; k < num_levels; k++)
692 {
693 // coarsest step is some % of image width.
694 col[k] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * w * h, 0);
695 if(col[k] == NULL)
696 {
697 err = 1;
698 goto error;
699 }
700 comb[k] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * w * h, 0);
701 if(comb[k] == NULL)
702 {
703 err = 1;
704 goto error;
705 }
706
707 memset(comb[k], 0, sizeof(float) * 4 * w * h);
708 w = (w - 1) / 2 + 1;
709 h = (h - 1) / 2 + 1;
710 step *= 2;
711 if(step > rad || w < 4 || h < 4)
712 {
713 num_levels = k + 1;
714 break;
715 }
716 }
717
718 for(int e = 0; e < d->exposure_fusion + 1; e++)
719 {
720 // for every exposure fusion image:
721 // push by some ev, apply base curve:
722 if(d->preserve_colors == DT_RGB_NORM_NONE)
723 apply_legacy_curve(in, col[0], wd, ht, exposure_increment(d->exposure_stops, e, d->exposure_fusion, d->exposure_bias),
724 d->table, d->unbounded_coeffs);
725 else
726 apply_curve(in, col[0], wd, ht, d->preserve_colors, exposure_increment(d->exposure_stops, e, d->exposure_fusion, d->exposure_bias),
727 d->table, d->unbounded_coeffs, work_profile);
728
729 // compute features
730 compute_features(col[0], wd, ht);
731
732 // create gaussian pyramid of colour buffer
733 w = wd;
734 h = ht;
735 if(gauss_reduce(col[0], col[1], out, w, h))
736 {
737 err = 1;
738 goto error;
739 }
741 for(size_t k = 0; k < 4ul * wd * ht; k += 4)
742 col[0][k + 3] *= .1f + sqrtf(out[k] * out[k] + out[k + 1] * out[k + 1] + out[k + 2] * out[k + 2]);
743
744// #define DEBUG_VIS2
745#ifdef DEBUG_VIS2 // transform weights in channels
746 for(size_t k = 0; k < 4ul * w * h; k += 4) col[0][k + e] = col[0][k + 3];
747#endif
748
749// #define DEBUG_VIS
750#ifdef DEBUG_VIS // DEBUG visualise weight buffer
751 for(size_t k = 0; k < 4ul * w * h; k += 4) comb[0][k + e] = col[0][k + 3];
752 continue;
753#endif
754
755 for(int k = 1; k < num_levels; k++)
756 {
757 if(gauss_reduce(col[k - 1], col[k], 0, w, h))
758 {
759 err = 1;
760 goto error;
761 }
762 w = (w - 1) / 2 + 1;
763 h = (h - 1) / 2 + 1;
764 }
765
766 // update pyramid coarse to fine
767 for(int k = num_levels - 1; k >= 0; k--)
768 {
769 w = wd;
770 h = ht;
771 for(int i = 0; i < k; i++)
772 {
773 w = (w - 1) / 2 + 1;
774 h = (h - 1) / 2 + 1;
775 }
776 // abuse output buffer as temporary memory:
777 if(k != num_levels - 1)
778 {
779 if(gauss_expand(col[k + 1], out, w, h))
780 {
781 err = 1;
782 goto error;
783 }
784 }
786 for(size_t x = 0; x < (size_t)4 * h * w; x += 4)
787 {
788 // blend images into output pyramid
789 if(k == num_levels - 1) // blend gaussian base
790#ifdef DEBUG_VIS2
791 ;
792#else
793 {
794 for(int c = 0; c < 3; c++)
795 comb[k][x + c] += col[k][x + 3] * col[k][x + c];
796 }
797#endif
798 else // laplacian
799 {
800 for(int c = 0; c < 3; c++)
801 comb[k][x + c] += col[k][x + 3] * (col[k][x + c] - out[x + c]);
802 }
803 comb[k][x + 3] += col[k][x + 3];
804 }
805 }
806 }
807
808#ifndef DEBUG_VIS // DEBUG: switch off when visualising weight buf
809 // normalise and reconstruct output pyramid buffer coarse to fine
810 for(int k = num_levels - 1; k >= 0; k--)
811 {
812 w = wd;
813 h = ht;
814 for(int i = 0; i < k; i++)
815 {
816 w = (w - 1) / 2 + 1;
817 h = (h - 1) / 2 + 1;
818 }
819
820 // normalise both gaussian base and laplacians:
822 for(size_t i = 0; i < (size_t)4 * w * h; i += 4)
823 if(comb[k][i + 3] > 1e-8f)
824 for(int c = 0; c < 3; c++) comb[k][i + c] /= comb[k][i + 3];
825
826 if(k < num_levels - 1)
827 { // reconstruct output image
828 if(gauss_expand(comb[k + 1], out, w, h))
829 {
830 err = 1;
831 goto error;
832 }
834 for(size_t x = 0; x < (size_t)4 * h * w; x += 4)
835 {
836 for(int c = 0; c < 3; c++)
837 comb[k][x + c] += out[x + c];
838 }
839 }
840 }
841#endif
842 // copy output buffer
844 for(size_t k = 0; k < (size_t)4 * wd * ht; k += 4)
845 {
846 out[k + 0] = fmaxf(comb[0][k + 0], 0.f);
847 out[k + 1] = fmaxf(comb[0][k + 1], 0.f);
848 out[k + 2] = fmaxf(comb[0][k + 2], 0.f);
849 out[k + 3] = in[k + 3]; // pass on 4th channel
850 }
851
852error:;
853 // free temp buffers
854 for(int k = 0; k < num_levels; k++)
855 {
858 }
859 dt_free(col);
860 dt_free(comb);
861 return err;
862}
863
864void process_lut(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
865 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
866{
867 const float *const in = (const float *)ivoid;
868 float *const out = (float *)ovoid;
869 //const int ch = piece->dsc_in.channels; <-- it appears someone was trying to make this handle monochrome data,
870 //however the for loops only handled RGBA - FIXME, determine what possible data formats and channel
871 //configurations we might encounter here and handle those too
873 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_iop_work_profile_info(piece->module, piece->module->dev->iop);
874
875 const int wd = roi_in->width, ht = roi_in->height;
876
877 // Compared to previous implementation, we've at least moved this conditional outside of the image processing loops
878 // so that it is evaluated only once. See FIXME comments in apply_curve for more potential performance improvements
879 if(d->preserve_colors == DT_RGB_NORM_NONE)
880 apply_legacy_curve(in, out, wd, ht, 1.0, d->table, d->unbounded_coeffs);
881 else
882 apply_curve(in, out, wd, ht, d->preserve_colors, 1.0, d->table, d->unbounded_coeffs, work_profile);
883}
884
885
886int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
887 void *const ovoid)
888{
889 const dt_iop_roi_t *const roi_in = &piece->roi_in;
890 const dt_iop_roi_t *const roi_out = &piece->roi_out;
892
893 // are we doing exposure fusion?
894 if(d->exposure_fusion)
895 return process_fusion(self, piece, ivoid, ovoid, roi_in, roi_out);
896 else
897 process_lut(self, piece, ivoid, ovoid, roi_in, roi_out);
898 return 0;
899}
900
903{
906
907 d->exposure_fusion = p->exposure_fusion;
908 d->exposure_stops = p->exposure_stops;
909 d->exposure_bias = p->exposure_bias;
910 d->preserve_colors = p->preserve_colors;
911
912 const int ch = 0;
913 // take care of possible change of curve type or number of nodes (not yet implemented in UI)
914 if(d->basecurve_type != p->basecurve_type[ch] || d->basecurve_nodes != p->basecurve_nodes[ch])
915 {
916 if(d->curve) // catch initial init_pipe case
917 dt_draw_curve_destroy(d->curve);
918 d->curve = dt_draw_curve_new(0.0, 1.0, p->basecurve_type[ch]);
919 d->basecurve_nodes = p->basecurve_nodes[ch];
920 d->basecurve_type = p->basecurve_type[ch];
921 for(int k = 0; k < p->basecurve_nodes[ch]; k++)
922 {
923 // printf("p->basecurve[%i][%i].x = %f;\n", ch, k, p->basecurve[ch][k].x);
924 // printf("p->basecurve[%i][%i].y = %f;\n", ch, k, p->basecurve[ch][k].y);
925 (void)dt_draw_curve_add_point(d->curve, p->basecurve[ch][k].x, p->basecurve[ch][k].y);
926 }
927 }
928 else
929 {
930 for(int k = 0; k < p->basecurve_nodes[ch]; k++)
931 dt_draw_curve_set_point(d->curve, k, p->basecurve[ch][k].x, p->basecurve[ch][k].y);
932 }
933 dt_draw_curve_calc_values(d->curve, 0.0f, 1.0f, 0x10000, NULL, d->table);
934
935 // now the extrapolation stuff:
936 const float xm = p->basecurve[0][p->basecurve_nodes[0] - 1].x;
937 const float x[4] = { 0.7f * xm, 0.8f * xm, 0.9f * xm, 1.0f * xm };
938 const float y[4] = { d->table[CLAMP((int)(x[0] * 0x10000ul), 0, 0xffff)],
939 d->table[CLAMP((int)(x[1] * 0x10000ul), 0, 0xffff)],
940 d->table[CLAMP((int)(x[2] * 0x10000ul), 0, 0xffff)],
941 d->table[CLAMP((int)(x[3] * 0x10000ul), 0, 0xffff)] };
942 dt_iop_estimate_exp(x, y, 4, d->unbounded_coeffs);
943}
944
946{
947 // create part of the pixelpipe
949 piece->data_size = sizeof(dt_iop_basecurve_data_t);
950}
951
953{
954 /* init_pipe() may have failed to allocate, and cleanup runs regardless. */
955 if(IS_NULL_PTR(piece->data)) return;
956 // clean up everything again.
958 if(d->curve) dt_draw_curve_destroy(d->curve);
959 dt_free_align(piece->data);
960 piece->data = NULL;
961}
962
963void gui_update(struct dt_iop_module_t *self)
964{
967
968 gtk_widget_set_visible(g->exposure_step, p->exposure_fusion != 0);
969 gtk_widget_set_visible(g->exposure_bias, p->exposure_fusion != 0);
970
971 // gui curve is read directly from params during expose event.
972 gtk_widget_queue_draw(self->gui->widget);
973}
974
975static float eval_grey(float x)
976{
977 // "log base" is a combined scaling and offset change so that x->[0,1], with
978 // the left side of the histogram expanded (slider->right) or not (slider left, linear)
979 return x;
980}
981
983{
984 dt_iop_default_init(module);
985 dt_iop_basecurve_params_t *d = module->default_params;
986 d->basecurve[0][1].x = d->basecurve[0][1].y = 1.0;
987 d->basecurve_nodes[0] = 2;
988}
989
990static gboolean dt_iop_basecurve_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
991{
992 gtk_widget_queue_draw(widget);
993 return TRUE;
994}
995
996static gboolean dt_iop_basecurve_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
997{
998 gtk_widget_queue_draw(widget);
999 return TRUE;
1000}
1001
1002static float to_log(const float x, const float base)
1003{
1004 if(base > 0.0f)
1005 return logf(x * base + 1.0f) / logf(base + 1.0f);
1006 else
1007 return x;
1008}
1009
1010static float to_lin(const float x, const float base)
1011{
1012 if(base > 0.0f)
1013 return (powf(base - 1.0f, x) - 1.0f) / base;
1014 else
1015 return x;
1016}
1017
1018static gboolean dt_iop_basecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1019{
1020 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1023
1024 int nodes = p->basecurve_nodes[0];
1025 dt_iop_basecurve_node_t *basecurve = p->basecurve[0];
1026 if(c->minmax_curve_type != p->basecurve_type[0] || c->minmax_curve_nodes != p->basecurve_nodes[0])
1027 {
1028 dt_draw_curve_destroy(c->minmax_curve);
1029 c->minmax_curve = dt_draw_curve_new(0.0, 1.0, p->basecurve_type[0]);
1030 c->minmax_curve_nodes = p->basecurve_nodes[0];
1031 c->minmax_curve_type = p->basecurve_type[0];
1032 for(int k = 0; k < p->basecurve_nodes[0]; k++)
1033 (void)dt_draw_curve_add_point(c->minmax_curve, p->basecurve[0][k].x, p->basecurve[0][k].y);
1034 }
1035 else
1036 {
1037 for(int k = 0; k < p->basecurve_nodes[0]; k++)
1038 dt_draw_curve_set_point(c->minmax_curve, k, p->basecurve[0][k].x, p->basecurve[0][k].y);
1039 }
1040 dt_draw_curve_t *minmax_curve = c->minmax_curve;
1041 dt_draw_curve_calc_values(minmax_curve, 0.0, 1.0, DT_IOP_TONECURVE_RES, c->draw_xs, c->draw_ys);
1042
1043 float unbounded_coeffs[3];
1044 const float xm = basecurve[nodes - 1].x;
1045 {
1046 const float x[4] = { 0.7f * xm, 0.8f * xm, 0.9f * xm, 1.0f * xm };
1047 const float y[4] = { c->draw_ys[CLAMP((int)(x[0] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1048 c->draw_ys[CLAMP((int)(x[1] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1049 c->draw_ys[CLAMP((int)(x[2] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1050 c->draw_ys[CLAMP((int)(x[3] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)] };
1052 }
1053
1054 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1055 GtkAllocation allocation;
1056 gtk_widget_get_allocation(widget, &allocation);
1057 int width = allocation.width, height = allocation.height;
1058 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1059 cairo_t *cr = cairo_create(cst);
1060 // clear bg
1061 cairo_set_source_rgb(cr, .2, .2, .2);
1062 cairo_paint(cr);
1063
1064 cairo_translate(cr, inset, inset);
1065 width -= 2 * inset;
1066 height -= 2 * inset;
1067
1068#if 0
1069 // draw shadow around
1070 float alpha = 1.0f;
1071 for(int k=0; k<inset; k++)
1072 {
1073 cairo_rectangle(cr, -k, -k, width + 2*k, height + 2*k);
1074 cairo_set_source_rgba(cr, 0, 0, 0, alpha);
1075 alpha *= 0.6f;
1076 cairo_fill(cr);
1077 }
1078#else
1079 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
1080 cairo_set_source_rgb(cr, .1, .1, .1);
1081 cairo_rectangle(cr, 0, 0, width, height);
1082 cairo_stroke(cr);
1083#endif
1084
1085 cairo_set_source_rgb(cr, .3, .3, .3);
1086 cairo_rectangle(cr, 0, 0, width, height);
1087 cairo_fill(cr);
1088
1089 cairo_translate(cr, 0, height);
1090 if(c->selected >= 0)
1091 {
1092 char text[30];
1093 // draw information about current selected node
1094 PangoLayout *layout;
1095 PangoRectangle ink;
1096 PangoFontDescription *desc = pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
1097 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
1098 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
1099 layout = pango_cairo_create_layout(cr);
1100 pango_layout_set_font_description(layout, desc);
1101
1102 const float x_node_value = basecurve[c->selected].x * 100;
1103 const float y_node_value = basecurve[c->selected].y * 100;
1104 const float d_node_value = y_node_value - x_node_value;
1105 // scale conservatively to 100% of width:
1106 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
1107 pango_layout_set_text(layout, text, -1);
1108 pango_layout_get_pixel_extents(layout, &ink, NULL);
1109 pango_font_description_set_absolute_size(desc, (double)width / ink.width * PANGO_SCALE);
1110 pango_layout_set_font_description(layout, desc);
1111
1112 snprintf(text, sizeof(text), "%.2f / %.2f ( %+.2f)", x_node_value, y_node_value, d_node_value);
1113
1114 cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
1115 pango_layout_set_text(layout, text, -1);
1116 pango_layout_get_pixel_extents(layout, &ink, NULL);
1117 cairo_move_to(cr, 0.98f * width - ink.width - ink.x, -0.02 * height - ink.height - ink.y);
1118 pango_cairo_show_layout(cr, layout);
1119 cairo_stroke(cr);
1120 pango_font_description_free(desc);
1121 g_object_unref(layout);
1122 }
1123 cairo_scale(cr, 1.0f, -1.0f);
1124
1125 // draw grid
1126 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
1127 cairo_set_source_rgb(cr, .1, .1, .1);
1128 if(c->loglogscale)
1129 dt_draw_loglog_grid(cr, 4, 0, 0, width, height, c->loglogscale + 1.0f);
1130 else
1131 dt_draw_grid(cr, 4, 0, 0, width, height);
1132
1133 // draw nodes positions
1134 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1135 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
1136 for(int k = 0; k < nodes; k++)
1137 {
1138 const float x = to_log(basecurve[k].x, c->loglogscale), y = to_log(basecurve[k].y, c->loglogscale);
1139 cairo_arc(cr, x * width, y * height, DT_PIXEL_APPLY_DPI(3), 0, 2. * M_PI);
1140 cairo_stroke(cr);
1141 }
1142
1143 // draw selected cursor
1144 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1145
1146 if(c->selected >= 0)
1147 {
1148 cairo_set_source_rgb(cr, .9, .9, .9);
1149 const float x = to_log(basecurve[c->selected].x, c->loglogscale),
1150 y = to_log(basecurve[c->selected].y, c->loglogscale);
1151 cairo_arc(cr, x * width, y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
1152 cairo_stroke(cr);
1153 }
1154
1155 // draw curve
1156 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1157 cairo_set_source_rgb(cr, .9, .9, .9);
1158 // cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
1159 cairo_move_to(cr, 0, height * to_log(c->draw_ys[0], c->loglogscale));
1160 for(int k = 1; k < DT_IOP_TONECURVE_RES; k++)
1161 {
1162 const float xx = k / (DT_IOP_TONECURVE_RES - 1.0f);
1163 if(xx > xm)
1164 {
1165 const float yy = dt_iop_eval_exp(unbounded_coeffs, xx);
1166 const float x = to_log(xx, c->loglogscale), y = to_log(yy, c->loglogscale);
1167 cairo_line_to(cr, x * width, height * y);
1168 }
1169 else
1170 {
1171 const float yy = c->draw_ys[k];
1172 const float x = to_log(xx, c->loglogscale), y = to_log(yy, c->loglogscale);
1173 cairo_line_to(cr, x * width, height * y);
1174 }
1175 }
1176 cairo_stroke(cr);
1177
1178 cairo_destroy(cr);
1179 cairo_set_source_surface(crf, cst, 0, 0);
1180 cairo_paint(crf);
1181 cairo_surface_destroy(cst);
1182 return TRUE;
1183}
1184
1185static inline int _add_node(dt_iop_basecurve_node_t *basecurve, int *nodes, float x, float y)
1186{
1187 int selected = -1;
1188 if(basecurve[0].x > x)
1189 selected = 0;
1190 else
1191 {
1192 for(int k = 1; k < *nodes; k++)
1193 {
1194 if(basecurve[k].x > x)
1195 {
1196 selected = k;
1197 break;
1198 }
1199 }
1200 }
1201 if(selected == -1) selected = *nodes;
1202 for(int i = *nodes; i > selected; i--)
1203 {
1204 basecurve[i].x = basecurve[i - 1].x;
1205 basecurve[i].y = basecurve[i - 1].y;
1206 }
1207 // found a new point
1208 basecurve[selected].x = x;
1209 basecurve[selected].y = y;
1210 (*nodes)++;
1211 return selected;
1212}
1213
1215{
1218
1219 int ch = 0;
1220 int nodes = p->basecurve_nodes[ch];
1221 dt_iop_basecurve_node_t *basecurve = p->basecurve[ch];
1222
1223 if(nodes <= 2) return;
1224
1225 const float mx = basecurve[c->selected].x;
1226
1227 // delete vertex if order has changed
1228 // for all points, x coordinate of point must be strictly larger than
1229 // the x coordinate of the previous point
1230 if((c->selected > 0 && (basecurve[c->selected - 1].x >= mx))
1231 || (c->selected < nodes - 1 && (basecurve[c->selected + 1].x <= mx)))
1232 {
1233 for(int k = c->selected; k < nodes - 1; k++)
1234 {
1235 basecurve[k].x = basecurve[k + 1].x;
1236 basecurve[k].y = basecurve[k + 1].y;
1237 }
1238 c->selected = -2; // avoid re-insertion of that point immediately after this
1239 p->basecurve_nodes[ch]--;
1240 }
1241}
1242
1243static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state);
1244
1245static gboolean dt_iop_basecurve_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1246{
1247 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1250 int ch = 0;
1251 int nodes = p->basecurve_nodes[ch];
1252 dt_iop_basecurve_node_t *basecurve = p->basecurve[ch];
1253
1254 GtkAllocation allocation;
1255 gtk_widget_get_allocation(widget, &allocation);
1256 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1257 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
1258 const double old_m_x = c->mouse_x;
1259 const double old_m_y = c->mouse_y;
1260 c->mouse_x = event->x - inset;
1261 c->mouse_y = event->y - inset;
1262
1263 const float mx = CLAMP(c->mouse_x, 0, width) / (float)width;
1264 const float my = 1.0f - CLAMP(c->mouse_y, 0, height) / (float)height;
1265 const float linx = to_lin(mx, c->loglogscale), liny = to_lin(my, c->loglogscale);
1266
1267 if(event->state & GDK_BUTTON1_MASK)
1268 {
1269 // got a vertex selected:
1270 if(c->selected >= 0)
1271 {
1272 // this is used to translate mause position in loglogscale to make this behavior unified with linear scale.
1273 const float translate_mouse_x = old_m_x / width - to_log(basecurve[c->selected].x, c->loglogscale);
1274 const float translate_mouse_y = 1 - old_m_y / height - to_log(basecurve[c->selected].y, c->loglogscale);
1275 // dx & dy are in linear coordinates
1276 const float dx = to_lin(c->mouse_x / width - translate_mouse_x, c->loglogscale)
1277 - to_lin(old_m_x / width - translate_mouse_x, c->loglogscale);
1278 const float dy = to_lin(1 - c->mouse_y / height - translate_mouse_y, c->loglogscale)
1279 - to_lin(1 - old_m_y / height - translate_mouse_y, c->loglogscale);
1280
1281 return _move_point_internal(self, widget, dx, dy, event->state);
1282 }
1283 else if(nodes < MAXNODES && c->selected >= -1)
1284 {
1285 // no vertex was close, create a new one!
1286 c->selected = _add_node(basecurve, &p->basecurve_nodes[ch], linx, liny);
1287 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1288 }
1289 }
1290 else
1291 {
1292 // minimum area around the node to select it:
1293 float min = .04f;
1294 min *= min; // comparing against square
1295 int nearest = -1;
1296 for(int k = 0; k < nodes; k++)
1297 {
1298 float dist
1299 = (my - to_log(basecurve[k].y, c->loglogscale)) * (my - to_log(basecurve[k].y, c->loglogscale))
1300 + (mx - to_log(basecurve[k].x, c->loglogscale)) * (mx - to_log(basecurve[k].x, c->loglogscale));
1301 if(dist < min)
1302 {
1303 min = dist;
1304 nearest = k;
1305 }
1306 }
1307 c->selected = nearest;
1308 }
1309 if(c->selected >= 0) gtk_widget_grab_focus(widget);
1310 gtk_widget_queue_draw(widget);
1311 return TRUE;
1312}
1313
1314static gboolean dt_iop_basecurve_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1315{
1316 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1320
1321 int ch = 0;
1322 int nodes = p->basecurve_nodes[ch];
1323 dt_iop_basecurve_node_t *basecurve = p->basecurve[ch];
1324
1325 if(event->button == 1)
1326 {
1327 if(event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, DT_PRIMARY_MASK)
1328 && nodes < MAXNODES && c->selected == -1)
1329 {
1330 // 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
1331 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1332 GtkAllocation allocation;
1333 gtk_widget_get_allocation(widget, &allocation);
1334 int width = allocation.width - 2 * inset;
1335 c->mouse_x = event->x - inset;
1336 c->mouse_y = event->y - inset;
1337
1338 const float mx = CLAMP(c->mouse_x, 0, width) / (float)width;
1339 const float linx = to_lin(mx, c->loglogscale);
1340
1341 // don't add a node too close to others in x direction, it can crash dt
1342 int selected = -1;
1343 if(basecurve[0].x > linx)
1344 selected = 0;
1345 else
1346 {
1347 for(int k = 1; k < nodes; k++)
1348 {
1349 if(basecurve[k].x > linx)
1350 {
1351 selected = k;
1352 break;
1353 }
1354 }
1355 }
1356 if(selected == -1) selected = nodes;
1357 // > 0 -> check distance to left neighbour
1358 // < nodes -> check distance to right neighbour
1359 if(!((selected > 0 && linx - basecurve[selected - 1].x <= 0.025) ||
1360 (selected < nodes && basecurve[selected].x - linx <= 0.025)))
1361 {
1362 // evaluate the curve at the current x position
1363 const float y = dt_draw_curve_calc_value(c->minmax_curve, linx);
1364
1365 if(y >= 0.0 && y <= 1.0) // never add something outside the viewport, you couldn't change it afterwards
1366 {
1367 // create a new node
1368 selected = _add_node(basecurve, &p->basecurve_nodes[ch], linx, y);
1369
1370 // maybe set the new one as being selected
1371 float min = .04f;
1372 min *= min; // comparing against square
1373 for(int k = 0; k < nodes; k++)
1374 {
1375 float other_y = to_log(basecurve[k].y, c->loglogscale);
1376 float dist = (y - other_y) * (y - other_y);
1377 if(dist < min) c->selected = selected;
1378 }
1379
1380 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1381 gtk_widget_queue_draw(self->gui->widget);
1382 }
1383 }
1384 return TRUE;
1385 }
1386 else if(event->type == GDK_2BUTTON_PRESS)
1387 {
1388 // reset current curve
1389 p->basecurve_nodes[ch] = d->basecurve_nodes[ch];
1390 p->basecurve_type[ch] = d->basecurve_type[ch];
1391 for(int k = 0; k < d->basecurve_nodes[ch]; k++)
1392 {
1393 p->basecurve[ch][k].x = d->basecurve[ch][k].x;
1394 p->basecurve[ch][k].y = d->basecurve[ch][k].y;
1395 }
1396 c->selected = -2; // avoid motion notify re-inserting immediately.
1397 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1398 gtk_widget_queue_draw(self->gui->widget);
1399 return TRUE;
1400 }
1401 }
1402 else if(event->button == 3 && c->selected >= 0)
1403 {
1404 if(c->selected == 0 || c->selected == nodes - 1)
1405 {
1406 float reset_value = c->selected == 0 ? 0 : 1;
1407 basecurve[c->selected].y = basecurve[c->selected].x = reset_value;
1408 gtk_widget_queue_draw(self->gui->widget);
1409 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1410 return TRUE;
1411 }
1412
1413 for(int k = c->selected; k < nodes - 1; k++)
1414 {
1415 basecurve[k].x = basecurve[k + 1].x;
1416 basecurve[k].y = basecurve[k + 1].y;
1417 }
1418 basecurve[nodes - 1].x = basecurve[nodes - 1].y = 0;
1419 c->selected = -2; // avoid re-insertion of that point immediately after this
1420 p->basecurve_nodes[ch]--;
1421 gtk_widget_queue_draw(self->gui->widget);
1422 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1423 return TRUE;
1424 }
1425 return FALSE;
1426}
1427
1428static gboolean area_resized(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1429{
1430 GtkAllocation allocation;
1431 gtk_widget_get_allocation(widget, &allocation);
1432 GtkRequisition r;
1433 r.width = allocation.width;
1434 r.height = allocation.width;
1435 gtk_widget_get_preferred_size(widget, &r, NULL);
1436 return TRUE;
1437}
1438
1439static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
1440{
1443
1444 int ch = 0;
1445 dt_iop_basecurve_node_t *basecurve = p->basecurve[ch];
1446
1447 basecurve[c->selected].x = CLAMP(basecurve[c->selected].x + dx, 0.0f, 1.0f);
1448 basecurve[c->selected].y = CLAMP(basecurve[c->selected].y + dy, 0.0f, 1.0f);
1449
1450 dt_iop_basecurve_sanity_check(self, widget);
1451
1452 gtk_widget_queue_draw(widget);
1453 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1454 return TRUE;
1455}
1456
1457#define BASECURVE_DEFAULT_STEP (0.001f)
1458
1459static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
1460{
1461 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1463
1464 if(c->selected < 0) return TRUE;
1465
1466 gdouble delta_y;
1467 if(dt_gui_get_scroll_delta(event, &delta_y))
1468 {
1469 delta_y *= -BASECURVE_DEFAULT_STEP;
1470 return _move_point_internal(self, widget, 0.0, delta_y, event->state);
1471 }
1472
1473 return TRUE;
1474}
1475
1476static gboolean dt_iop_basecurve_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
1477{
1478 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1480
1481 if(c->selected < 0) return TRUE;
1482 guint key = dt_keys_mainpad_alternatives(event->keyval);
1483
1484 int handled = 0;
1485 float dx = 0.0f, dy = 0.0f;
1486 if(key == GDK_KEY_Up)
1487 {
1488 handled = 1;
1490 }
1491 else if(key == GDK_KEY_Down)
1492 {
1493 handled = 1;
1495 }
1496 else if(key == GDK_KEY_Right)
1497 {
1498 handled = 1;
1500 }
1501 else if(key == GDK_KEY_Left)
1502 {
1503 handled = 1;
1505 }
1506
1507 if(!handled) return FALSE;
1508
1509 return _move_point_internal(self, widget, dx, dy, event->state);
1510}
1511
1512#undef BASECURVE_DEFAULT_STEP
1513
1514void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1515{
1518
1519 if(w == g->fusion)
1520 {
1521 int prev = *(int *)previous;
1522 if(p->exposure_fusion != 0 && prev == 0)
1523 {
1524 gtk_widget_set_visible(g->exposure_step, TRUE);
1525 gtk_widget_set_visible(g->exposure_bias, TRUE);
1526 }
1527 if(p->exposure_fusion == 0 && prev != 0)
1528 {
1529 gtk_widget_set_visible(g->exposure_step, FALSE);
1530 gtk_widget_set_visible(g->exposure_bias, FALSE);
1531 }
1532 }
1533}
1534
1535static void logbase_callback(GtkWidget *slider, gpointer user_data)
1536{
1537 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1539 g->loglogscale = eval_grey(dt_bauhaus_slider_get(g->logbase));
1540 gtk_widget_queue_draw(GTK_WIDGET(g->area));
1541}
1542
1543void gui_init(struct dt_iop_module_t *self)
1544{
1547
1548 c->minmax_curve = dt_draw_curve_new(0.0, 1.0, p->basecurve_type[0]);
1549 c->minmax_curve_type = p->basecurve_type[0];
1550 c->minmax_curve_nodes = p->basecurve_nodes[0];
1551 for(int k = 0; k < p->basecurve_nodes[0]; k++)
1552 (void)dt_draw_curve_add_point(c->minmax_curve, p->basecurve[0][k].x, p->basecurve[0][k].y);
1553 c->mouse_x = c->mouse_y = -1.0;
1554 c->selected = -1;
1555 c->loglogscale = 0;
1556 self->gui->timeout_handle = 0;
1557
1558 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1559
1560 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
1561 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
1562 gtk_widget_set_tooltip_text(GTK_WIDGET(c->area), _("abscissa: input, ordinate: output. works on RGB channels"));
1563 g_object_set_data(G_OBJECT(c->area), "iop-instance", self);
1564 gtk_box_pack_start(GTK_BOX(self->gui->widget),
1565 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
1566 "plugins/darkroom/basecurve/graphheight", 280, 100),
1567 FALSE, FALSE, 0);
1568
1569 c->cmb_preserve_colors = dt_bauhaus_combobox_from_params(self, "preserve_colors");
1570 gtk_widget_set_tooltip_text(c->cmb_preserve_colors, _("method to preserve colors when applying contrast"));
1571
1572 c->fusion = dt_bauhaus_combobox_from_params(self, "exposure_fusion");
1573 dt_bauhaus_combobox_add(c->fusion, _("none"));
1574 dt_bauhaus_combobox_add(c->fusion, _("two exposures"));
1575 dt_bauhaus_combobox_add(c->fusion, _("three exposures"));
1576 gtk_widget_set_tooltip_text(c->fusion, _("fuse this image stopped up/down a couple of times with itself, to "
1577 "compress high dynamic range. expose for the highlights before use."));
1578
1579 c->exposure_step = dt_bauhaus_slider_from_params(self, "exposure_stops");
1580 dt_bauhaus_slider_set_digits(c->exposure_step, 3);
1581 gtk_widget_set_tooltip_text(c->exposure_step, _("how many stops to shift the individual exposures apart"));
1582 gtk_widget_set_no_show_all(c->exposure_step, TRUE);
1583 gtk_widget_set_visible(c->exposure_step, p->exposure_fusion != 0 ? TRUE : FALSE);
1584
1585 // initially set to 1 (consistency with previous versions), but double-click resets to 0
1586 // to get a quick way to reach 0 with the mouse.
1587 c->exposure_bias = dt_bauhaus_slider_from_params(self, "exposure_bias");
1588 dt_bauhaus_slider_set_default(c->exposure_bias, 0.0f);
1589 dt_bauhaus_slider_set_digits(c->exposure_bias, 3);
1590 gtk_widget_set_tooltip_text(c->exposure_bias, _("whether to shift exposure up or down "
1591 "(-1: reduce highlight, +1: reduce shadows)"));
1592 gtk_widget_set_no_show_all(c->exposure_bias, TRUE);
1593 gtk_widget_set_visible(c->exposure_bias, p->exposure_fusion != 0 ? TRUE : FALSE);
1594 c->logbase = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0f, 40.0f, 0, 0.0f, 2);
1595 dt_bauhaus_widget_set_label(c->logbase, N_("scale for graph"));
1596 gtk_box_pack_start(GTK_BOX(self->gui->widget), c->logbase , TRUE, TRUE, 0); g_signal_connect(G_OBJECT(c->logbase), "value-changed", G_CALLBACK(logbase_callback), self);
1597
1598 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK | dt_widget_scroll_mask()
1599 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1600 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1601 gtk_widget_set_can_focus(GTK_WIDGET(c->area), TRUE);
1602 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(dt_iop_basecurve_draw), self);
1603 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(dt_iop_basecurve_button_press), self);
1604 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(dt_iop_basecurve_motion_notify), self);
1605 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(dt_iop_basecurve_leave_notify), self);
1606 g_signal_connect(G_OBJECT(c->area), "enter-notify-event", G_CALLBACK(dt_iop_basecurve_enter_notify), self);
1607 g_signal_connect(G_OBJECT(c->area), "configure-event", G_CALLBACK(area_resized), self);
1608 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(_scrolled), self);
1609 g_signal_connect(G_OBJECT(c->area), "key-press-event", G_CALLBACK(dt_iop_basecurve_key_press), self);
1610}
1611
1613{
1615 dt_draw_curve_destroy(c->minmax_curve);
1616
1618}
1619
1620// clang-format off
1621// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1622// vim: shiftwidth=2 expandtab tabstop=2 cindent
1623// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1624// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const basecurve_preset_t basecurve_presets[]
Definition basecurve.c:322
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition basecurve.c:901
void init(dt_iop_module_t *module)
Definition basecurve.c:982
static gboolean area_resized(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition basecurve.c:1428
const char ** description(struct dt_iop_module_t *self)
Definition basecurve.c:367
static gboolean dt_iop_basecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
Definition basecurve.c:1018
int default_group()
Definition basecurve.c:377
static const char sony_alpha[]
Definition basecurve.c:258
static const basecurve_preset_t basecurve_camera_presets[]
Definition basecurve.c:285
static const int basecurve_presets_cnt
Definition basecurve.c:346
static int gauss_reduce(const float *const input, float *const coarse, float *const detail, const size_t wd, const size_t ht)
Definition basecurve.c:635
static void set_presets(dt_iop_module_so_t *self, const basecurve_preset_t *presets, int count, gboolean camera)
Definition basecurve.c:392
__DT_CLONE_TARGETS__ int process_fusion(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition basecurve.c:669
static __DT_CLONE_TARGETS__ int gauss_expand(const float *const input, float *const fine, const size_t wd, const size_t ht)
Definition basecurve.c:611
static const char nokia[]
Definition basecurve.c:269
static gboolean dt_iop_basecurve_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition basecurve.c:990
static __DT_CLONE_TARGETS__ void apply_legacy_curve(const float *const in, float *const out, const int width, const int height, const float mul, const float *const table, const float *const unbounded_coeffs)
Definition basecurve.c:474
#define DT_GUI_CURVE_EDITOR_INSET
Definition basecurve.c:98
static const char fujifilm[]
Definition basecurve.c:268
static const char pentax[]
Definition basecurve.c:259
static float eval_grey(float x)
Definition basecurve.c:975
static gboolean dt_iop_basecurve_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
Definition basecurve.c:1476
static gboolean dt_iop_basecurve_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition basecurve.c:996
static const char nikon_alt[]
Definition basecurve.c:257
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition basecurve.c:945
#define BASECURVE_DEFAULT_STEP
Definition basecurve.c:1457
static __DT_CLONE_TARGETS__ void compute_features(float *const col, const int wd, const int ht)
Definition basecurve.c:537
static const char olympus[]
Definition basecurve.c:261
static void dt_iop_basecurve_sanity_check(dt_iop_module_t *self, GtkWidget *widget)
Definition basecurve.c:1214
const char * name()
Definition basecurve.c:362
void gui_update(struct dt_iop_module_t *self)
Definition basecurve.c:963
static const char neutral[]
Definition basecurve.c:253
void gui_init(struct dt_iop_module_t *self)
Definition basecurve.c:1543
dt_iop_basecurve_params3_t dt_iop_basecurve_params4_t
Definition basecurve.c:152
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition basecurve.c:1514
#define m
Definition basecurve.c:283
void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
Definition basecurve.c:445
static const char konica_minolta[]
Definition basecurve.c:266
static const char leica[]
Definition basecurve.c:264
static __DT_CLONE_TARGETS__ void apply_curve(const float *const in, float *const out, const int width, const int height, const int preserve_colors, const float mul, const float *const table, const float *const unbounded_coeffs, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition basecurve.c:502
static const char canon_eos_alt[]
Definition basecurve.c:255
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition basecurve.c:387
#define MAXNODES
Definition basecurve.c:101
int flags()
Definition basecurve.c:382
void gui_cleanup(struct dt_iop_module_t *self)
Definition basecurve.c:1612
static const int basecurve_camera_presets_cnt
Definition basecurve.c:320
void init_presets(dt_iop_module_so_t *self)
Definition basecurve.c:427
struct dt_iop_basecurve_node_t dt_iop_basecurve_node_t
#define DT_IOP_TONECURVE_RES
Definition basecurve.c:100
int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition basecurve.c:886
static gboolean dt_iop_basecurve_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition basecurve.c:1314
static void logbase_callback(GtkWidget *slider, gpointer user_data)
Definition basecurve.c:1535
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition basecurve.c:952
static const char canon_eos[]
Definition basecurve.c:254
static const char ricoh[]
Definition basecurve.c:260
static gboolean dt_iop_basecurve_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition basecurve.c:1245
static float to_log(const float x, const float base)
Definition basecurve.c:1002
static const char olympus_alt[]
Definition basecurve.c:262
static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
Definition basecurve.c:1459
struct dt_iop_basecurve_params_t dt_iop_basecurve_params_t
static int _add_node(dt_iop_basecurve_node_t *basecurve, int *nodes, float x, float y)
Definition basecurve.c:1185
static const char nikon[]
Definition basecurve.c:256
void process_lut(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition basecurve.c:864
static const char samsung[]
Definition basecurve.c:267
static const char panasonic[]
Definition basecurve.c:263
static __DT_CLONE_TARGETS__ int gauss_blur(const float *const input, float *const output, const size_t wd, const size_t ht)
Definition basecurve.c:565
static float to_lin(const float x, const float base)
Definition basecurve.c:1010
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
Definition basecurve.c:169
static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
Definition basecurve.c:1439
static const char kodak_easyshare[]
Definition basecurve.c:265
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3343
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1491
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
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
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_RGB
static const float x
const float f
static dt_aligned_pixel_t float *const const float unbounded_coeffs[3][3]
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
#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
@ DT_RGB_NORM_LUMINANCE
#define dt_database_start_transaction()
Definition database.h:290
#define dt_database_release_transaction()
Definition database.h:291
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
__DT_CLONE_TARGETS__ dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
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 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 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_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 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)
void dt_gui_presets_update_autoapply(const char *name, dt_dev_operation_t op, const int32_t version, const int autoapply)
#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
@ 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_slider_from_params(dt_iop_module_t *self, const char *param)
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
void *const ovoid
static void dt_iop_estimate_exp(const float *const x, const float *const y, const int num, float *coeff)
static float dt_iop_eval_exp(const float *const coeff, const float x)
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.
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
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.
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
const float uint32_t state[4]
const float r
const char * model
Definition basecurve.c:275
const char * name
Definition basecurve.c:273
const char * maker
Definition basecurve.c:274
dt_iop_basecurve_params_t params
Definition basecurve.c:278
struct dt_iop_module_t *void * data
dt_draw_curve_t * curve
Definition basecurve.c:350
float table[0x10000]
Definition basecurve.c:353
GtkWidget * cmb_preserve_colors
Definition basecurve.c:242
GtkDrawingArea * area
Definition basecurve.c:240
dt_draw_curve_t * minmax_curve
Definition basecurve.c:237
dt_iop_basecurve_node_t basecurve[3][20]
Definition basecurve.c:158
dt_iop_basecurve_node_t basecurve[3][20]
Definition basecurve.c:144
dt_iop_basecurve_node_t basecurve[3][20]
Definition basecurve.c:132
dt_iop_rgb_norms_t preserve_colors
Definition basecurve.c:125
dt_iop_basecurve_node_t basecurve[3][20]
Definition basecurve.c:116
GtkWidget * widget
Definition imageop_gui.h:47
dt_dev_operation_t op
Definition imageop.h:235
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
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
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
GdkEventMask dt_widget_scroll_mask(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)