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
64#include "common/darktable.h"
65#include "gui/gdkkeys.h"
66#include "config.h"
67#endif
68#include "bauhaus/bauhaus.h"
70#include "common/debug.h"
71#include "common/math.h"
72#include "common/opencl.h"
73#include "common/rgb_norms.h"
74#include "control/control.h"
75#include "develop/develop.h"
76#include "develop/imageop.h"
78#include "develop/imageop_gui.h"
79#include "develop/tiling.h"
80#include "dtgtk/drawingarea.h"
81#include "gui/draw.h"
82#include "gui/gtk.h"
83#include "gui/presets.h"
84
85#include "iop/iop_api.h"
86
87#include <assert.h>
88#include <gtk/gtk.h>
89#include <inttypes.h>
90#include <stdlib.h>
91#include <string.h>
92
93#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(5)
94#define DT_GUI_CURVE_INFL .3f
95#define DT_IOP_TONECURVE_RES 256
96#define MAXNODES 20
97
98
100
102{
103 float x; // $MIN: 0.0 $MAX: 1.0
104 float y; // $MIN: 0.0 $MAX: 1.0
106
108{
109 // three curves (c, ., .) with max number of nodes
110 // the other two are reserved, maybe we'll have cam rgb at some point.
112 int basecurve_nodes[3]; // $MIN: 0 $MAX: MAXNODES $DEFAULT: 0
113 int basecurve_type[3]; // $MIN: 0 $MAX: MONOTONE_HERMITE $DEFAULT: MONOTONE_HERMITE
114 int exposure_fusion; /* number of exposure fusion steps
115 $DEFAULT: 0 $DESCRIPTION: "fusion" */
116 float exposure_stops; /* number of stops between fusion images
117 $MIN: 0.01 $MAX: 4.0 $DEFAULT: 1.0 $DESCRIPTION: "exposure shift" */
118 float exposure_bias; /* whether to do exposure-fusion with over or under-exposure
119 $MIN: -1.0 $MAX: 1.0 $DEFAULT: 1.0 $DESCRIPTION: "exposure bias" */
120 dt_iop_rgb_norms_t preserve_colors; /* $DEFAULT: DT_RGB_NORM_LUMINANCE $DESCRIPTION: "preserve colors" */
122
124{
125 // three curves (c, ., .) with max number of nodes
126 // the other two are reserved, maybe we'll have cam rgb at some point.
130 int exposure_fusion; // number of exposure fusion steps
131 float exposure_stops; // number of stops between fusion images
132 float exposure_bias; // whether to do exposure-fusion with over or under-exposure
134
136{
137 // three curves (c, ., .) with max number of nodes
138 // the other two are reserved, maybe we'll have cam rgb at some point.
142 int exposure_fusion; // number of exposure fusion steps
143 float exposure_stops; // number of stops between fusion images
145
146// same but semantics/defaults changed
148
150{
151 // three curves (c, ., .) with max number of nodes
152 // the other two are reserved, maybe we'll have cam rgb at some point.
157
163
164int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
165 void *new_params, const int new_version)
166{
167 if(old_version == 1 && new_version == 6)
168 {
171
172 // start with a fresh copy of default parameters
173 // unfortunately default_params aren't inited at this stage.
175 { { 0.0, 0.0 }, { 1.0, 1.0 } },
176 },
177 { 2, 3, 3 },
179 for(int k = 0; k < 6; k++) n->basecurve[0][k].x = o->tonecurve_x[k];
180 for(int k = 0; k < 6; k++) n->basecurve[0][k].y = o->tonecurve_y[k];
181 n->basecurve_nodes[0] = 6;
182 n->basecurve_type[0] = CUBIC_SPLINE;
183 n->exposure_fusion = 0;
184 n->exposure_stops = 1;
185 n->exposure_bias = 1.0;
186 n->preserve_colors = DT_RGB_NORM_NONE;
187 return 0;
188 }
189 if(old_version == 2 && new_version == 6)
190 {
193 memcpy(n, o, sizeof(dt_iop_basecurve_params2_t));
194 n->exposure_fusion = 0;
195 n->exposure_stops = 1;
196 n->exposure_bias = 1.0;
197 n->preserve_colors = DT_RGB_NORM_NONE;
198 return 0;
199 }
200 if(old_version == 3 && new_version == 6)
201 {
204 memcpy(n, o, sizeof(dt_iop_basecurve_params3_t));
205 n->exposure_stops = (o->exposure_fusion == 0 && o->exposure_stops == 0) ? 1.0f : o->exposure_stops;
206 n->exposure_bias = 1.0;
207 n->preserve_colors = DT_RGB_NORM_NONE;
208 return 0;
209 }
210 if(old_version == 4 && new_version == 6)
211 {
214 memcpy(n, o, sizeof(dt_iop_basecurve_params4_t));
215 n->exposure_bias = 1.0;
216 n->preserve_colors = DT_RGB_NORM_NONE;
217 return 0;
218 }
219 if(old_version == 5 && new_version == 6)
220 {
223 memcpy(n, o, sizeof(dt_iop_basecurve_params5_t));
224 n->preserve_colors = DT_RGB_NORM_NONE;
225 return 0;
226 }
227 return 1;
228}
229
247
248static const char neutral[] = N_("neutral");
249static const char canon_eos[] = N_("canon eos like");
250static const char canon_eos_alt[] = N_("canon eos like alternate");
251static const char nikon[] = N_("nikon like");
252static const char nikon_alt[] = N_("nikon like alternate");
253static const char sony_alpha[] = N_("sony alpha like");
254static const char pentax[] = N_("pentax like");
255static const char ricoh[] = N_("ricoh like");
256static const char olympus[] = N_("olympus like");
257static const char olympus_alt[] = N_("olympus like alternate");
258static const char panasonic[] = N_("panasonic like");
259static const char leica[] = N_("leica like");
260static const char kodak_easyshare[] = N_("kodak easyshare like");
261static const char konica_minolta[] = N_("konica minolta like");
262static const char samsung[] = N_("samsung like");
263static const char fujifilm[] = N_("fujifilm like");
264static const char nokia[] = N_("nokia like");
265
277
278#define m MONOTONE_HERMITE
279
281 // copy paste your measured basecurve line at the top here, like so (note the exif data and the last 1):
282 // clang-format off
283
284 // nikon d750 by Edouard Gomez
285 {"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},
286 // contributed by Stefan Kauerauf
287 {"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},
288 // nikon d7000 by Edouard Gomez
289 {"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},
290 // nikon d7200 standard by Ralf Brown (firmware 1.00)
291 {"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},
292 // nikon d7500 by Anders Bennehag (firmware C 1.00, LD 2.016)
293 {"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},
294 // sony rx100m2 by Günther R.
295 { "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 },
296 // contributed by matthias bodenbinder
297 { "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 },
298 // contributed by Dan Torop
299 { "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 },
300 { "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 },
301 // contributed by Johannes Hanika
302 { "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 },
303 // contributed by chrik5
304 { "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 },
305 // contributed by Togan Muftuoglu - ed: slope is too aggressive on shadows
306 //{ "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 },
307 // contributed by Edouard Gomez
308 {"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},
309 // contributed by Pascal Obry
310 { "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 },
311 // contributed by Lukas Schrangl
312 {"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},
313 // clang-format on
314};
316
318 // clang-format off
319 // smoother cubic spline curve
320 { 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 },
321 { 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 },
322 { 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 },
323 { 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 },
324 { 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 },
325 { 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 },
326 { 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 },
327 { 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 },
328 { 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 },
329 { 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 },
330 { 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 },
331 { 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 },
332 { 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 },
333 { 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 },
334 { 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 },
335 { 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 },
336 { 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 },
337 { 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 },
338 // clang-format on
339};
340#undef m
341static const int basecurve_presets_cnt = sizeof(basecurve_presets) / sizeof(basecurve_preset_t);
342
344{
345 dt_draw_curve_t *curve; // curve for pixelpipe piece and pixel processing
348 float table[0x10000]; // precomputed look-up table for tone curve
349 float unbounded_coeffs[3]; // approximation for extrapolation
355
356
357const char *name()
358{
359 return _("base curve");
360}
361
362const char **description(struct dt_iop_module_t *self)
363{
364 return dt_iop_set_description(self, _("apply a view transform based on personal or camera manufacturer look,\n"
365 "for corrective purposes, to prepare images for display"),
366 _("corrective"),
367 _("linear, RGB, display-referred"),
368 _("non-linear, RGB"),
369 _("non-linear, RGB, display-referred"));
370}
371
373{
374 return IOP_GROUP_TONES;
375}
376
381
383{
384 return IOP_CS_RGB;
385}
386
387static void set_presets(dt_iop_module_so_t *self, const basecurve_preset_t *presets, int count, gboolean camera)
388{
389 const gboolean autoapply_percamera = FALSE;
390
391 const gboolean force_autoapply = (autoapply_percamera || !camera);
392
393 // transform presets above to db entries.
394 for(int k = 0; k < count; k++)
395 {
396 // disable exposure fusion if not explicitly inited in params struct definition above:
397 dt_iop_basecurve_params_t tmp = presets[k].params;
398 if(tmp.exposure_fusion == 0 && tmp.exposure_stops == 0.0f)
399 {
400 tmp.exposure_fusion = 0;
401 tmp.exposure_stops = 1.0f;
402 tmp.exposure_bias = 1.0f;
403 }
404 // add the preset.
405 dt_gui_presets_add_generic(_(presets[k].name), self->op, self->version(),
406 &tmp, sizeof(dt_iop_basecurve_params_t), 1,
408 // and restrict it to model, maker, iso, and raw images
409 dt_gui_presets_update_mml(_(presets[k].name), self->op, self->version(),
410 presets[k].maker, presets[k].model, "");
411 dt_gui_presets_update_iso(_(presets[k].name), self->op, self->version(),
412 presets[k].iso_min, presets[k].iso_max);
413 dt_gui_presets_update_ldr(_(presets[k].name), self->op, self->version(), FOR_RAW);
414 // make it auto-apply for matching images:
415 dt_gui_presets_update_autoapply(_(presets[k].name), self->op, self->version(),
416 force_autoapply ? 1 : presets[k].autoapply);
417 // hide all non-matching presets in case the model string is set.
418 // When force_autoapply was given always filter (as these are per-camera presets)
419 dt_gui_presets_update_filter(_(presets[k].name), self->op, self->version(), camera || presets[k].filter);
420 }
421}
422
434
435static inline __attribute__((always_inline)) float exposure_increment(float stops, int e, float fusion, float bias)
436{
437 float offset = stops * fusion * (bias - 1.0f) / 2.0f;
438 return powf(2.0f, stops * e + offset);
439}
440
441void 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)
442{
443 const dt_iop_roi_t *const roi_in = &piece->roi_in;
445
446 if(d->exposure_fusion)
447 {
448 const int rad = MIN(roi_in->width, (int)ceilf(256 * roi_in->scale));
449
450 tiling->factor = 6.666f; // in + out + col[] + comb[] + 2*tmp
451 tiling->maxbuf = 1.0f;
452 tiling->overhead = 0;
453 tiling->xalign = 1;
454 tiling->yalign = 1;
455 tiling->overlap = rad;
456 }
457 else
458 {
459 tiling->factor = 2.0f; // in + out
460 tiling->maxbuf = 1.0f;
461 tiling->overhead = 0;
462 tiling->xalign = 1;
463 tiling->yalign = 1;
464 tiling->overlap = 0;
465 }
466}
467
468// See comments of opencl version in data/kernels/basecurve.cl for description of the meaning of "legacy"
470static inline void apply_legacy_curve(
471 const float *const in,
472 float *const out,
473 const int width,
474 const int height,
475 const float mul,
476 const float *const table,
477 const float *const unbounded_coeffs)
478{
479 const size_t npixels = (size_t)width * height;
481 for(size_t k = 0; k < 4*npixels; k += 4)
482 {
483 for(int i = 0; i < 3; i++)
484 {
485 const float f = in[k+i] * mul;
486 // use base curve for values < 1, else use extrapolation.
487 if(f < 1.0f)
488 out[k+i] = fmaxf(table[CLAMP((int)(f * 0x10000ul), 0, 0xffff)], 0.f);
489 else
490 out[k+i] = fmaxf(dt_iop_eval_exp(unbounded_coeffs, f), 0.f);
491 }
492 out[k+3] = in[k+3];
493 }
494}
495
496// See description of the equivalent OpenCL function in data/kernels/basecurve.cl
498static inline void apply_curve(
499 const float *const in,
500 float *const out,
501 const int width,
502 const int height,
503 const int preserve_colors,
504 const float mul,
505 const float *const table,
506 const float *const unbounded_coeffs,
507 const dt_iop_order_iccprofile_info_t *const work_profile)
508{
509 const size_t npixels = (size_t)width * height;
511 for(size_t k = 0; k < 4*npixels; k += 4)
512 {
513 float ratio = 1.f;
514 // FIXME: Determine if we can get rid of the conditionals within this function in some way to improve performance.
515 // However, solving this one is much harder than the conditional for legacy vs. current
516 const float lum = mul * dt_rgb_norm(in+k, preserve_colors, work_profile);
517 if(lum > 0.f)
518 {
519 const float curve_lum = (lum < 1.0f)
520 ? table[CLAMP((int)(lum * 0x10000ul), 0, 0xffff)]
522 ratio = mul * curve_lum / lum;
523 }
524 for(size_t c = 0; c < 3; c++)
525 {
526 out[k+c] = fmaxf(ratio * in[k+c], 0.f);
527 }
528 out[k+3] = in[k+3];
529 }
530}
531
533static inline void compute_features(
534 float *const col,
535 const int wd,
536 const int ht)
537{
538 // features are product of
539 // 1) well exposedness
540 // 2) saturation
541 // 3) local contrast (handled in laplacian form later)
542 const size_t npixels = (size_t)wd * ht;
544 for(size_t x = 0; x < 4*npixels; x += 4)
545 {
546 const float max = MAX(col[x], MAX(col[x+1], col[x+2]));
547 const float min = MIN(col[x], MIN(col[x+1], col[x+2]));
548 const float sat = .1f + .1f*(max-min)/MAX(1e-4f, max);
549
550 const float c = 0.54f;
551 float v = fabsf(col[x]-c);
552 v = MAX(fabsf(col[x+1]-c), v);
553 v = MAX(fabsf(col[x+2]-c), v);
554 const float var = 0.5;
555 const float exp = .2f + dt_fast_expf(-v*v/(var*var));
556 col[x+3] = sat * exp;
557 }
558}
559
561static inline int gauss_blur(
562 const float *const input,
563 float *const output,
564 const size_t wd,
565 const size_t ht)
566{
567 const float w[5] = { 1.f / 16.f, 4.f / 16.f, 6.f / 16.f, 4.f / 16.f, 1.f / 16.f };
568 float *tmp = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * wd * ht, 0);
569 if(IS_NULL_PTR(tmp)) return 1;
570
571 memset(tmp, 0, sizeof(float) * 4 * wd * ht);
573 for(int j=0;j<ht;j++)
574 { // horizontal pass
575 // left borders
576 for(int i=0;i<2;i++) for(int c=0;c<4;c++)
577 for(int ii=-2;ii<=2;ii++)
578 tmp[4*(j*wd+i)+c] += input[4*(j*wd+MAX(-i-ii,i+ii))+c] * w[ii+2];
579 // most pixels
580 for(int i=2;i<wd-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+i+ii)+c] * w[ii+2];
583 // right borders
584 for(int i=wd-2;i<wd;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+MIN(i+ii, wd-(i+ii-wd+1) ))+c] * w[ii+2];
587 }
588 memset(output, 0, sizeof(float) * 4 * wd * ht);
590 for(int i=0;i<wd;i++)
591 { // vertical pass
592 for(int j=0;j<2;j++) for(int c=0;c<4;c++)
593 for(int jj=-2;jj<=2;jj++)
594 output[4*(j*wd+i)+c] += tmp[4*(MAX(-j-jj,j+jj)*wd+i)+c] * w[jj+2];
595 for(int j=2;j<ht-2;j++) for(int c=0;c<4;c++)
596 for(int jj=-2;jj<=2;jj++)
597 output[4*(j*wd+i)+c] += tmp[4*((j+jj)*wd+i)+c] * w[jj+2];
598 for(int j=ht-2;j<ht;j++) for(int c=0;c<4;c++)
599 for(int jj=-2;jj<=2;jj++)
600 output[4*(j*wd+i)+c] += tmp[4*(MIN(j+jj, ht-(j+jj-ht+1))*wd+i)+c] * w[jj+2];
601 }
603 return 0;
604}
605
607static inline int gauss_expand(
608 const float *const input, // coarse input
609 float *const fine, // upsampled, blurry output
610 const size_t wd, // fine res
611 const size_t ht)
612{
613 const size_t cw = (wd-1)/2+1;
614 // fill numbers in even pixels, zero odd ones
615 memset(fine, 0, sizeof(float) * 4 * wd * ht);
616 __OMP_PARALLEL_FOR__(collapse(2))
617 for(int j=0;j<ht;j+=2)
618 for(int i=0;i<wd;i+=2)
619 for(int c=0;c<4;c++)
620 fine[4*(j*wd+i)+c] = 4.0f * input[4*(j/2*cw + i/2)+c];
621
622 // convolve with same kernel weights mul by 4:
623 if(gauss_blur(fine, fine, wd, ht)) return 1;
624 return 0;
625}
626
627// XXX FIXME: we'll need to pad up the image to get a good boundary condition!
628// XXX FIXME: downsampling will not result in an energy conserving pattern (every 4 pixels one sample)
629// XXX FIXME: neither will a mirror boundary condition (mirrors in subsampled values at random density)
630// TODO: copy laplacian code from local laplacian filters, it's faster.
631static inline int gauss_reduce(
632 const float *const input, // fine input buffer
633 float *const coarse, // coarse scale, blurred input buf
634 float *const detail, // detail/laplacian, fine scale, or 0
635 const size_t wd,
636 const size_t ht)
637{
638 // blur, store only coarse res
639 const size_t cw = (wd-1)/2+1, ch = (ht-1)/2+1;
640
641 float *blurred = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * wd * ht, 0);
642 if(IS_NULL_PTR(blurred)) return 1;
643
644 if(gauss_blur(input, blurred, wd, ht))
645 {
647 return 1;
648 }
649 for(size_t j=0;j<ch;j++) for(size_t i=0;i<cw;i++)
650 for(int c=0;c<4;c++) coarse[4*(j*cw+i)+c] = blurred[4*(2*j*wd+2*i)+c];
652
653 if(!IS_NULL_PTR(detail))
654 {
655 // compute laplacian/details: expand coarse buffer into detail
656 // buffer subtract expanded buffer from input in place
657 if(gauss_expand(coarse, detail, wd, ht)) return 1;
658 for(size_t k=0;k<wd*ht*4;k++)
659 detail[k] = input[k] - detail[k];
660 }
661 return 0;
662}
663
665int process_fusion(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
666 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
667{
668 const float *const in = (const float *)ivoid;
669 float *const out = (float *)ovoid;
671 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_iop_work_profile_info(piece->module, piece->module->dev->iop);
672 int err = 0;
673
674 // allocate temporary buffer for wavelet transform + blending
675 const int wd = roi_in->width, ht = roi_in->height;
676 int num_levels = 8;
677 float **col = calloc(num_levels, sizeof(float *));
678 float **comb = calloc(num_levels, sizeof(float *));
679 if(IS_NULL_PTR(col) || IS_NULL_PTR(comb))
680 {
681 err = 1;
682 goto error;
683 }
684 int w = wd, h = ht;
685 const int rad = MIN(wd, (int)ceilf(256 * roi_in->scale));
686 int step = 1;
687 for(int k = 0; k < num_levels; k++)
688 {
689 // coarsest step is some % of image width.
690 col[k] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * w * h, 0);
691 if(col[k] == NULL)
692 {
693 err = 1;
694 goto error;
695 }
696 comb[k] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * w * h, 0);
697 if(comb[k] == NULL)
698 {
699 err = 1;
700 goto error;
701 }
702
703 memset(comb[k], 0, sizeof(float) * 4 * w * h);
704 w = (w - 1) / 2 + 1;
705 h = (h - 1) / 2 + 1;
706 step *= 2;
707 if(step > rad || w < 4 || h < 4)
708 {
709 num_levels = k + 1;
710 break;
711 }
712 }
713
714 for(int e = 0; e < d->exposure_fusion + 1; e++)
715 {
716 // for every exposure fusion image:
717 // push by some ev, apply base curve:
718 if(d->preserve_colors == DT_RGB_NORM_NONE)
719 apply_legacy_curve(in, col[0], wd, ht, exposure_increment(d->exposure_stops, e, d->exposure_fusion, d->exposure_bias),
720 d->table, d->unbounded_coeffs);
721 else
722 apply_curve(in, col[0], wd, ht, d->preserve_colors, exposure_increment(d->exposure_stops, e, d->exposure_fusion, d->exposure_bias),
723 d->table, d->unbounded_coeffs, work_profile);
724
725 // compute features
726 compute_features(col[0], wd, ht);
727
728 // create gaussian pyramid of colour buffer
729 w = wd;
730 h = ht;
731 if(gauss_reduce(col[0], col[1], out, w, h))
732 {
733 err = 1;
734 goto error;
735 }
737 for(size_t k = 0; k < 4ul * wd * ht; k += 4)
738 col[0][k + 3] *= .1f + sqrtf(out[k] * out[k] + out[k + 1] * out[k + 1] + out[k + 2] * out[k + 2]);
739
740// #define DEBUG_VIS2
741#ifdef DEBUG_VIS2 // transform weights in channels
742 for(size_t k = 0; k < 4ul * w * h; k += 4) col[0][k + e] = col[0][k + 3];
743#endif
744
745// #define DEBUG_VIS
746#ifdef DEBUG_VIS // DEBUG visualise weight buffer
747 for(size_t k = 0; k < 4ul * w * h; k += 4) comb[0][k + e] = col[0][k + 3];
748 continue;
749#endif
750
751 for(int k = 1; k < num_levels; k++)
752 {
753 if(gauss_reduce(col[k - 1], col[k], 0, w, h))
754 {
755 err = 1;
756 goto error;
757 }
758 w = (w - 1) / 2 + 1;
759 h = (h - 1) / 2 + 1;
760 }
761
762 // update pyramid coarse to fine
763 for(int k = num_levels - 1; k >= 0; k--)
764 {
765 w = wd;
766 h = ht;
767 for(int i = 0; i < k; i++)
768 {
769 w = (w - 1) / 2 + 1;
770 h = (h - 1) / 2 + 1;
771 }
772 // abuse output buffer as temporary memory:
773 if(k != num_levels - 1)
774 {
775 if(gauss_expand(col[k + 1], out, w, h))
776 {
777 err = 1;
778 goto error;
779 }
780 }
782 for(size_t x = 0; x < (size_t)4 * h * w; x += 4)
783 {
784 // blend images into output pyramid
785 if(k == num_levels - 1) // blend gaussian base
786#ifdef DEBUG_VIS2
787 ;
788#else
789 {
790 for(int c = 0; c < 3; c++)
791 comb[k][x + c] += col[k][x + 3] * col[k][x + c];
792 }
793#endif
794 else // laplacian
795 {
796 for(int c = 0; c < 3; c++)
797 comb[k][x + c] += col[k][x + 3] * (col[k][x + c] - out[x + c]);
798 }
799 comb[k][x + 3] += col[k][x + 3];
800 }
801 }
802 }
803
804#ifndef DEBUG_VIS // DEBUG: switch off when visualising weight buf
805 // normalise and reconstruct output pyramid buffer coarse to fine
806 for(int k = num_levels - 1; k >= 0; k--)
807 {
808 w = wd;
809 h = ht;
810 for(int i = 0; i < k; i++)
811 {
812 w = (w - 1) / 2 + 1;
813 h = (h - 1) / 2 + 1;
814 }
815
816 // normalise both gaussian base and laplacians:
818 for(size_t i = 0; i < (size_t)4 * w * h; i += 4)
819 if(comb[k][i + 3] > 1e-8f)
820 for(int c = 0; c < 3; c++) comb[k][i + c] /= comb[k][i + 3];
821
822 if(k < num_levels - 1)
823 { // reconstruct output image
824 if(gauss_expand(comb[k + 1], out, w, h))
825 {
826 err = 1;
827 goto error;
828 }
830 for(size_t x = 0; x < (size_t)4 * h * w; x += 4)
831 {
832 for(int c = 0; c < 3; c++)
833 comb[k][x + c] += out[x + c];
834 }
835 }
836 }
837#endif
838 // copy output buffer
840 for(size_t k = 0; k < (size_t)4 * wd * ht; k += 4)
841 {
842 out[k + 0] = fmaxf(comb[0][k + 0], 0.f);
843 out[k + 1] = fmaxf(comb[0][k + 1], 0.f);
844 out[k + 2] = fmaxf(comb[0][k + 2], 0.f);
845 out[k + 3] = in[k + 3]; // pass on 4th channel
846 }
847
848error:;
849 // free temp buffers
850 for(int k = 0; k < num_levels; k++)
851 {
854 }
855 dt_free(col);
856 dt_free(comb);
857 return err;
858}
859
860void process_lut(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
861 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
862{
863 const float *const in = (const float *)ivoid;
864 float *const out = (float *)ovoid;
865 //const int ch = piece->dsc_in.channels; <-- it appears someone was trying to make this handle monochrome data,
866 //however the for loops only handled RGBA - FIXME, determine what possible data formats and channel
867 //configurations we might encounter here and handle those too
869 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_iop_work_profile_info(piece->module, piece->module->dev->iop);
870
871 const int wd = roi_in->width, ht = roi_in->height;
872
873 // Compared to previous implementation, we've at least moved this conditional outside of the image processing loops
874 // so that it is evaluated only once. See FIXME comments in apply_curve for more potential performance improvements
875 if(d->preserve_colors == DT_RGB_NORM_NONE)
876 apply_legacy_curve(in, out, wd, ht, 1.0, d->table, d->unbounded_coeffs);
877 else
878 apply_curve(in, out, wd, ht, d->preserve_colors, 1.0, d->table, d->unbounded_coeffs, work_profile);
879}
880
881
882int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
883 void *const ovoid)
884{
885 const dt_iop_roi_t *const roi_in = &piece->roi_in;
886 const dt_iop_roi_t *const roi_out = &piece->roi_out;
888
889 // are we doing exposure fusion?
890 if(d->exposure_fusion)
891 return process_fusion(self, piece, ivoid, ovoid, roi_in, roi_out);
892 else
893 process_lut(self, piece, ivoid, ovoid, roi_in, roi_out);
894 return 0;
895}
896
899{
902
903 d->exposure_fusion = p->exposure_fusion;
904 d->exposure_stops = p->exposure_stops;
905 d->exposure_bias = p->exposure_bias;
906 d->preserve_colors = p->preserve_colors;
907
908 const int ch = 0;
909 // take care of possible change of curve type or number of nodes (not yet implemented in UI)
910 if(d->basecurve_type != p->basecurve_type[ch] || d->basecurve_nodes != p->basecurve_nodes[ch])
911 {
912 if(d->curve) // catch initial init_pipe case
913 dt_draw_curve_destroy(d->curve);
914 d->curve = dt_draw_curve_new(0.0, 1.0, p->basecurve_type[ch]);
915 d->basecurve_nodes = p->basecurve_nodes[ch];
916 d->basecurve_type = p->basecurve_type[ch];
917 for(int k = 0; k < p->basecurve_nodes[ch]; k++)
918 {
919 // printf("p->basecurve[%i][%i].x = %f;\n", ch, k, p->basecurve[ch][k].x);
920 // printf("p->basecurve[%i][%i].y = %f;\n", ch, k, p->basecurve[ch][k].y);
921 (void)dt_draw_curve_add_point(d->curve, p->basecurve[ch][k].x, p->basecurve[ch][k].y);
922 }
923 }
924 else
925 {
926 for(int k = 0; k < p->basecurve_nodes[ch]; k++)
927 dt_draw_curve_set_point(d->curve, k, p->basecurve[ch][k].x, p->basecurve[ch][k].y);
928 }
929 dt_draw_curve_calc_values(d->curve, 0.0f, 1.0f, 0x10000, NULL, d->table);
930
931 // now the extrapolation stuff:
932 const float xm = p->basecurve[0][p->basecurve_nodes[0] - 1].x;
933 const float x[4] = { 0.7f * xm, 0.8f * xm, 0.9f * xm, 1.0f * xm };
934 const float y[4] = { d->table[CLAMP((int)(x[0] * 0x10000ul), 0, 0xffff)],
935 d->table[CLAMP((int)(x[1] * 0x10000ul), 0, 0xffff)],
936 d->table[CLAMP((int)(x[2] * 0x10000ul), 0, 0xffff)],
937 d->table[CLAMP((int)(x[3] * 0x10000ul), 0, 0xffff)] };
938 dt_iop_estimate_exp(x, y, 4, d->unbounded_coeffs);
939}
940
942{
943 // create part of the pixelpipe
945 piece->data_size = sizeof(dt_iop_basecurve_data_t);
946}
947
949{
950 // clean up everything again.
952 if(d->curve) dt_draw_curve_destroy(d->curve);
953 dt_free_align(piece->data);
954 piece->data = NULL;
955}
956
957void gui_update(struct dt_iop_module_t *self)
958{
961
962 gtk_widget_set_visible(g->exposure_step, p->exposure_fusion != 0);
963 gtk_widget_set_visible(g->exposure_bias, p->exposure_fusion != 0);
964
966 // gui curve is read directly from params during expose event.
967 gtk_widget_queue_draw(self->widget);
968}
969
970static float eval_grey(float x)
971{
972 // "log base" is a combined scaling and offset change so that x->[0,1], with
973 // the left side of the histogram expanded (slider->right) or not (slider left, linear)
974 return x;
975}
976
978{
979 dt_iop_default_init(module);
980 dt_iop_basecurve_params_t *d = module->default_params;
981 d->basecurve[0][1].x = d->basecurve[0][1].y = 1.0;
982 d->basecurve_nodes[0] = 2;
983}
984
985static gboolean dt_iop_basecurve_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
986{
987 gtk_widget_queue_draw(widget);
988 return TRUE;
989}
990
991static gboolean dt_iop_basecurve_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
992{
993 gtk_widget_queue_draw(widget);
994 return TRUE;
995}
996
997static float to_log(const float x, const float base)
998{
999 if(base > 0.0f)
1000 return logf(x * base + 1.0f) / logf(base + 1.0f);
1001 else
1002 return x;
1003}
1004
1005static float to_lin(const float x, const float base)
1006{
1007 if(base > 0.0f)
1008 return (powf(base - 1.0f, x) - 1.0f) / base;
1009 else
1010 return x;
1011}
1012
1013static gboolean dt_iop_basecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1014{
1015 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1018
1019 int nodes = p->basecurve_nodes[0];
1020 dt_iop_basecurve_node_t *basecurve = p->basecurve[0];
1021 if(c->minmax_curve_type != p->basecurve_type[0] || c->minmax_curve_nodes != p->basecurve_nodes[0])
1022 {
1023 dt_draw_curve_destroy(c->minmax_curve);
1024 c->minmax_curve = dt_draw_curve_new(0.0, 1.0, p->basecurve_type[0]);
1025 c->minmax_curve_nodes = p->basecurve_nodes[0];
1026 c->minmax_curve_type = p->basecurve_type[0];
1027 for(int k = 0; k < p->basecurve_nodes[0]; k++)
1028 (void)dt_draw_curve_add_point(c->minmax_curve, p->basecurve[0][k].x, p->basecurve[0][k].y);
1029 }
1030 else
1031 {
1032 for(int k = 0; k < p->basecurve_nodes[0]; k++)
1033 dt_draw_curve_set_point(c->minmax_curve, k, p->basecurve[0][k].x, p->basecurve[0][k].y);
1034 }
1035 dt_draw_curve_t *minmax_curve = c->minmax_curve;
1036 dt_draw_curve_calc_values(minmax_curve, 0.0, 1.0, DT_IOP_TONECURVE_RES, c->draw_xs, c->draw_ys);
1037
1038 float unbounded_coeffs[3];
1039 const float xm = basecurve[nodes - 1].x;
1040 {
1041 const float x[4] = { 0.7f * xm, 0.8f * xm, 0.9f * xm, 1.0f * xm };
1042 const float y[4] = { c->draw_ys[CLAMP((int)(x[0] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1043 c->draw_ys[CLAMP((int)(x[1] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1044 c->draw_ys[CLAMP((int)(x[2] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)],
1045 c->draw_ys[CLAMP((int)(x[3] * DT_IOP_TONECURVE_RES), 0, DT_IOP_TONECURVE_RES - 1)] };
1047 }
1048
1049 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1050 GtkAllocation allocation;
1051 gtk_widget_get_allocation(widget, &allocation);
1052 int width = allocation.width, height = allocation.height;
1053 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1054 cairo_t *cr = cairo_create(cst);
1055 // clear bg
1056 cairo_set_source_rgb(cr, .2, .2, .2);
1057 cairo_paint(cr);
1058
1059 cairo_translate(cr, inset, inset);
1060 width -= 2 * inset;
1061 height -= 2 * inset;
1062
1063#if 0
1064 // draw shadow around
1065 float alpha = 1.0f;
1066 for(int k=0; k<inset; k++)
1067 {
1068 cairo_rectangle(cr, -k, -k, width + 2*k, height + 2*k);
1069 cairo_set_source_rgba(cr, 0, 0, 0, alpha);
1070 alpha *= 0.6f;
1071 cairo_fill(cr);
1072 }
1073#else
1074 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
1075 cairo_set_source_rgb(cr, .1, .1, .1);
1076 cairo_rectangle(cr, 0, 0, width, height);
1077 cairo_stroke(cr);
1078#endif
1079
1080 cairo_set_source_rgb(cr, .3, .3, .3);
1081 cairo_rectangle(cr, 0, 0, width, height);
1082 cairo_fill(cr);
1083
1084 cairo_translate(cr, 0, height);
1085 if(c->selected >= 0)
1086 {
1087 char text[30];
1088 // draw information about current selected node
1089 PangoLayout *layout;
1090 PangoRectangle ink;
1091 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
1092 pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
1093 pango_font_description_set_absolute_size(desc, PANGO_SCALE);
1094 layout = pango_cairo_create_layout(cr);
1095 pango_layout_set_font_description(layout, desc);
1096
1097 const float x_node_value = basecurve[c->selected].x * 100;
1098 const float y_node_value = basecurve[c->selected].y * 100;
1099 const float d_node_value = y_node_value - x_node_value;
1100 // scale conservatively to 100% of width:
1101 snprintf(text, sizeof(text), "100.00 / 100.00 ( +100.00)");
1102 pango_layout_set_text(layout, text, -1);
1103 pango_layout_get_pixel_extents(layout, &ink, NULL);
1104 pango_font_description_set_absolute_size(desc, (double)width / ink.width * PANGO_SCALE);
1105 pango_layout_set_font_description(layout, desc);
1106
1107 snprintf(text, sizeof(text), "%.2f / %.2f ( %+.2f)", x_node_value, y_node_value, d_node_value);
1108
1109 cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
1110 pango_layout_set_text(layout, text, -1);
1111 pango_layout_get_pixel_extents(layout, &ink, NULL);
1112 cairo_move_to(cr, 0.98f * width - ink.width - ink.x, -0.02 * height - ink.height - ink.y);
1113 pango_cairo_show_layout(cr, layout);
1114 cairo_stroke(cr);
1115 pango_font_description_free(desc);
1116 g_object_unref(layout);
1117 }
1118 cairo_scale(cr, 1.0f, -1.0f);
1119
1120 // draw grid
1121 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
1122 cairo_set_source_rgb(cr, .1, .1, .1);
1123 if(c->loglogscale)
1124 dt_draw_loglog_grid(cr, 4, 0, 0, width, height, c->loglogscale + 1.0f);
1125 else
1126 dt_draw_grid(cr, 4, 0, 0, width, height);
1127
1128 // draw nodes positions
1129 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1130 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
1131 for(int k = 0; k < nodes; k++)
1132 {
1133 const float x = to_log(basecurve[k].x, c->loglogscale), y = to_log(basecurve[k].y, c->loglogscale);
1134 cairo_arc(cr, x * width, y * height, DT_PIXEL_APPLY_DPI(3), 0, 2. * M_PI);
1135 cairo_stroke(cr);
1136 }
1137
1138 // draw selected cursor
1139 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1140
1141 if(c->selected >= 0)
1142 {
1143 cairo_set_source_rgb(cr, .9, .9, .9);
1144 const float x = to_log(basecurve[c->selected].x, c->loglogscale),
1145 y = to_log(basecurve[c->selected].y, c->loglogscale);
1146 cairo_arc(cr, x * width, y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
1147 cairo_stroke(cr);
1148 }
1149
1150 // draw curve
1151 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1152 cairo_set_source_rgb(cr, .9, .9, .9);
1153 // cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
1154 cairo_move_to(cr, 0, height * to_log(c->draw_ys[0], c->loglogscale));
1155 for(int k = 1; k < DT_IOP_TONECURVE_RES; k++)
1156 {
1157 const float xx = k / (DT_IOP_TONECURVE_RES - 1.0f);
1158 if(xx > xm)
1159 {
1160 const float yy = dt_iop_eval_exp(unbounded_coeffs, xx);
1161 const float x = to_log(xx, c->loglogscale), y = to_log(yy, c->loglogscale);
1162 cairo_line_to(cr, x * width, height * y);
1163 }
1164 else
1165 {
1166 const float yy = c->draw_ys[k];
1167 const float x = to_log(xx, c->loglogscale), y = to_log(yy, c->loglogscale);
1168 cairo_line_to(cr, x * width, height * y);
1169 }
1170 }
1171 cairo_stroke(cr);
1172
1173 cairo_destroy(cr);
1174 cairo_set_source_surface(crf, cst, 0, 0);
1175 cairo_paint(crf);
1176 cairo_surface_destroy(cst);
1177 return TRUE;
1178}
1179
1180static inline int _add_node(dt_iop_basecurve_node_t *basecurve, int *nodes, float x, float y)
1181{
1182 int selected = -1;
1183 if(basecurve[0].x > x)
1184 selected = 0;
1185 else
1186 {
1187 for(int k = 1; k < *nodes; k++)
1188 {
1189 if(basecurve[k].x > x)
1190 {
1191 selected = k;
1192 break;
1193 }
1194 }
1195 }
1196 if(selected == -1) selected = *nodes;
1197 for(int i = *nodes; i > selected; i--)
1198 {
1199 basecurve[i].x = basecurve[i - 1].x;
1200 basecurve[i].y = basecurve[i - 1].y;
1201 }
1202 // found a new point
1203 basecurve[selected].x = x;
1204 basecurve[selected].y = y;
1205 (*nodes)++;
1206 return selected;
1207}
1208
1210{
1213
1214 int ch = 0;
1215 int nodes = p->basecurve_nodes[ch];
1216 dt_iop_basecurve_node_t *basecurve = p->basecurve[ch];
1217
1218 if(nodes <= 2) return;
1219
1220 const float mx = basecurve[c->selected].x;
1221
1222 // delete vertex if order has changed
1223 // for all points, x coordinate of point must be strictly larger than
1224 // the x coordinate of the previous point
1225 if((c->selected > 0 && (basecurve[c->selected - 1].x >= mx))
1226 || (c->selected < nodes - 1 && (basecurve[c->selected + 1].x <= mx)))
1227 {
1228 for(int k = c->selected; k < nodes - 1; k++)
1229 {
1230 basecurve[k].x = basecurve[k + 1].x;
1231 basecurve[k].y = basecurve[k + 1].y;
1232 }
1233 c->selected = -2; // avoid re-insertion of that point immediately after this
1234 p->basecurve_nodes[ch]--;
1235 }
1236}
1237
1238static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state);
1239
1240static gboolean dt_iop_basecurve_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1241{
1242 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1245 int ch = 0;
1246 int nodes = p->basecurve_nodes[ch];
1247 dt_iop_basecurve_node_t *basecurve = p->basecurve[ch];
1248
1249 GtkAllocation allocation;
1250 gtk_widget_get_allocation(widget, &allocation);
1251 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1252 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
1253 const double old_m_x = c->mouse_x;
1254 const double old_m_y = c->mouse_y;
1255 c->mouse_x = event->x - inset;
1256 c->mouse_y = event->y - inset;
1257
1258 const float mx = CLAMP(c->mouse_x, 0, width) / (float)width;
1259 const float my = 1.0f - CLAMP(c->mouse_y, 0, height) / (float)height;
1260 const float linx = to_lin(mx, c->loglogscale), liny = to_lin(my, c->loglogscale);
1261
1262 if(event->state & GDK_BUTTON1_MASK)
1263 {
1264 // got a vertex selected:
1265 if(c->selected >= 0)
1266 {
1267 // this is used to translate mause position in loglogscale to make this behavior unified with linear scale.
1268 const float translate_mouse_x = old_m_x / width - to_log(basecurve[c->selected].x, c->loglogscale);
1269 const float translate_mouse_y = 1 - old_m_y / height - to_log(basecurve[c->selected].y, c->loglogscale);
1270 // dx & dy are in linear coordinates
1271 const float dx = to_lin(c->mouse_x / width - translate_mouse_x, c->loglogscale)
1272 - to_lin(old_m_x / width - translate_mouse_x, c->loglogscale);
1273 const float dy = to_lin(1 - c->mouse_y / height - translate_mouse_y, c->loglogscale)
1274 - to_lin(1 - old_m_y / height - translate_mouse_y, c->loglogscale);
1275
1276 return _move_point_internal(self, widget, dx, dy, event->state);
1277 }
1278 else if(nodes < MAXNODES && c->selected >= -1)
1279 {
1280 // no vertex was close, create a new one!
1281 c->selected = _add_node(basecurve, &p->basecurve_nodes[ch], linx, liny);
1283 }
1284 }
1285 else
1286 {
1287 // minimum area around the node to select it:
1288 float min = .04f;
1289 min *= min; // comparing against square
1290 int nearest = -1;
1291 for(int k = 0; k < nodes; k++)
1292 {
1293 float dist
1294 = (my - to_log(basecurve[k].y, c->loglogscale)) * (my - to_log(basecurve[k].y, c->loglogscale))
1295 + (mx - to_log(basecurve[k].x, c->loglogscale)) * (mx - to_log(basecurve[k].x, c->loglogscale));
1296 if(dist < min)
1297 {
1298 min = dist;
1299 nearest = k;
1300 }
1301 }
1302 c->selected = nearest;
1303 }
1304 if(c->selected >= 0) gtk_widget_grab_focus(widget);
1305 gtk_widget_queue_draw(widget);
1306 return TRUE;
1307}
1308
1309static gboolean dt_iop_basecurve_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1310{
1311 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1315
1316 int ch = 0;
1317 int nodes = p->basecurve_nodes[ch];
1318 dt_iop_basecurve_node_t *basecurve = p->basecurve[ch];
1319
1320 if(event->button == 1)
1321 {
1322 if(event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, GDK_CONTROL_MASK)
1323 && nodes < MAXNODES && c->selected == -1)
1324 {
1325 // 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
1326 const int inset = DT_GUI_CURVE_EDITOR_INSET;
1327 GtkAllocation allocation;
1328 gtk_widget_get_allocation(widget, &allocation);
1329 int width = allocation.width - 2 * inset;
1330 c->mouse_x = event->x - inset;
1331 c->mouse_y = event->y - inset;
1332
1333 const float mx = CLAMP(c->mouse_x, 0, width) / (float)width;
1334 const float linx = to_lin(mx, c->loglogscale);
1335
1336 // don't add a node too close to others in x direction, it can crash dt
1337 int selected = -1;
1338 if(basecurve[0].x > linx)
1339 selected = 0;
1340 else
1341 {
1342 for(int k = 1; k < nodes; k++)
1343 {
1344 if(basecurve[k].x > linx)
1345 {
1346 selected = k;
1347 break;
1348 }
1349 }
1350 }
1351 if(selected == -1) selected = nodes;
1352 // > 0 -> check distance to left neighbour
1353 // < nodes -> check distance to right neighbour
1354 if(!((selected > 0 && linx - basecurve[selected - 1].x <= 0.025) ||
1355 (selected < nodes && basecurve[selected].x - linx <= 0.025)))
1356 {
1357 // evaluate the curve at the current x position
1358 const float y = dt_draw_curve_calc_value(c->minmax_curve, linx);
1359
1360 if(y >= 0.0 && y <= 1.0) // never add something outside the viewport, you couldn't change it afterwards
1361 {
1362 // create a new node
1363 selected = _add_node(basecurve, &p->basecurve_nodes[ch], linx, y);
1364
1365 // maybe set the new one as being selected
1366 float min = .04f;
1367 min *= min; // comparing against square
1368 for(int k = 0; k < nodes; k++)
1369 {
1370 float other_y = to_log(basecurve[k].y, c->loglogscale);
1371 float dist = (y - other_y) * (y - other_y);
1372 if(dist < min) c->selected = selected;
1373 }
1374
1376 gtk_widget_queue_draw(self->widget);
1377 }
1378 }
1379 return TRUE;
1380 }
1381 else if(event->type == GDK_2BUTTON_PRESS)
1382 {
1383 // reset current curve
1384 p->basecurve_nodes[ch] = d->basecurve_nodes[ch];
1385 p->basecurve_type[ch] = d->basecurve_type[ch];
1386 for(int k = 0; k < d->basecurve_nodes[ch]; k++)
1387 {
1388 p->basecurve[ch][k].x = d->basecurve[ch][k].x;
1389 p->basecurve[ch][k].y = d->basecurve[ch][k].y;
1390 }
1391 c->selected = -2; // avoid motion notify re-inserting immediately.
1393 gtk_widget_queue_draw(self->widget);
1394 return TRUE;
1395 }
1396 }
1397 else if(event->button == 3 && c->selected >= 0)
1398 {
1399 if(c->selected == 0 || c->selected == nodes - 1)
1400 {
1401 float reset_value = c->selected == 0 ? 0 : 1;
1402 basecurve[c->selected].y = basecurve[c->selected].x = reset_value;
1403 gtk_widget_queue_draw(self->widget);
1405 return TRUE;
1406 }
1407
1408 for(int k = c->selected; k < nodes - 1; k++)
1409 {
1410 basecurve[k].x = basecurve[k + 1].x;
1411 basecurve[k].y = basecurve[k + 1].y;
1412 }
1413 basecurve[nodes - 1].x = basecurve[nodes - 1].y = 0;
1414 c->selected = -2; // avoid re-insertion of that point immediately after this
1415 p->basecurve_nodes[ch]--;
1416 gtk_widget_queue_draw(self->widget);
1418 return TRUE;
1419 }
1420 return FALSE;
1421}
1422
1423static gboolean area_resized(GtkWidget *widget, GdkEvent *event, gpointer user_data)
1424{
1425 GtkAllocation allocation;
1426 gtk_widget_get_allocation(widget, &allocation);
1427 GtkRequisition r;
1428 r.width = allocation.width;
1429 r.height = allocation.width;
1430 gtk_widget_get_preferred_size(widget, &r, NULL);
1431 return TRUE;
1432}
1433
1434static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
1435{
1438
1439 int ch = 0;
1440 dt_iop_basecurve_node_t *basecurve = p->basecurve[ch];
1441
1442 basecurve[c->selected].x = CLAMP(basecurve[c->selected].x + dx, 0.0f, 1.0f);
1443 basecurve[c->selected].y = CLAMP(basecurve[c->selected].y + dy, 0.0f, 1.0f);
1444
1445 dt_iop_basecurve_sanity_check(self, widget);
1446
1447 gtk_widget_queue_draw(widget);
1449 return TRUE;
1450}
1451
1452#define BASECURVE_DEFAULT_STEP (0.001f)
1453
1454static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
1455{
1456 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1458
1459 if(c->selected < 0) return TRUE;
1460
1461 gdouble delta_y;
1462 if(dt_gui_get_scroll_delta(event, &delta_y))
1463 {
1464 delta_y *= -BASECURVE_DEFAULT_STEP;
1465 return _move_point_internal(self, widget, 0.0, delta_y, event->state);
1466 }
1467
1468 return TRUE;
1469}
1470
1471static gboolean dt_iop_basecurve_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
1472{
1473 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1475
1476 if(c->selected < 0) return TRUE;
1477 guint key = dt_keys_mainpad_alternatives(event->keyval);
1478
1479 int handled = 0;
1480 float dx = 0.0f, dy = 0.0f;
1481 if(key == GDK_KEY_Up)
1482 {
1483 handled = 1;
1485 }
1486 else if(key == GDK_KEY_Down)
1487 {
1488 handled = 1;
1490 }
1491 else if(key == GDK_KEY_Right)
1492 {
1493 handled = 1;
1495 }
1496 else if(key == GDK_KEY_Left)
1497 {
1498 handled = 1;
1500 }
1501
1502 if(!handled) return FALSE;
1503
1504 return _move_point_internal(self, widget, dx, dy, event->state);
1505}
1506
1507#undef BASECURVE_DEFAULT_STEP
1508
1509void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1510{
1513
1514 if(w == g->fusion)
1515 {
1516 int prev = *(int *)previous;
1517 if(p->exposure_fusion != 0 && prev == 0)
1518 {
1519 gtk_widget_set_visible(g->exposure_step, TRUE);
1520 gtk_widget_set_visible(g->exposure_bias, TRUE);
1521 }
1522 if(p->exposure_fusion == 0 && prev != 0)
1523 {
1524 gtk_widget_set_visible(g->exposure_step, FALSE);
1525 gtk_widget_set_visible(g->exposure_bias, FALSE);
1526 }
1527 }
1528}
1529
1530static void logbase_callback(GtkWidget *slider, gpointer user_data)
1531{
1532 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1534 g->loglogscale = eval_grey(dt_bauhaus_slider_get(g->logbase));
1535 gtk_widget_queue_draw(GTK_WIDGET(g->area));
1536}
1537
1538void gui_init(struct dt_iop_module_t *self)
1539{
1542
1543 c->minmax_curve = dt_draw_curve_new(0.0, 1.0, p->basecurve_type[0]);
1544 c->minmax_curve_type = p->basecurve_type[0];
1545 c->minmax_curve_nodes = p->basecurve_nodes[0];
1546 for(int k = 0; k < p->basecurve_nodes[0]; k++)
1547 (void)dt_draw_curve_add_point(c->minmax_curve, p->basecurve[0][k].x, p->basecurve[0][k].y);
1548 c->mouse_x = c->mouse_y = -1.0;
1549 c->selected = -1;
1550 c->loglogscale = 0;
1551 self->timeout_handle = 0;
1552
1553 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1554
1555 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
1556 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
1557 gtk_widget_set_tooltip_text(GTK_WIDGET(c->area), _("abscissa: input, ordinate: output. works on RGB channels"));
1558 g_object_set_data(G_OBJECT(c->area), "iop-instance", self);
1559 gtk_box_pack_start(GTK_BOX(self->widget),
1560 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
1561 "plugins/darkroom/basecurve/graphheight", 280, 100),
1562 FALSE, FALSE, 0);
1563
1564 c->cmb_preserve_colors = dt_bauhaus_combobox_from_params(self, "preserve_colors");
1565 gtk_widget_set_tooltip_text(c->cmb_preserve_colors, _("method to preserve colors when applying contrast"));
1566
1567 c->fusion = dt_bauhaus_combobox_from_params(self, "exposure_fusion");
1568 dt_bauhaus_combobox_add(c->fusion, _("none"));
1569 dt_bauhaus_combobox_add(c->fusion, _("two exposures"));
1570 dt_bauhaus_combobox_add(c->fusion, _("three exposures"));
1571 gtk_widget_set_tooltip_text(c->fusion, _("fuse this image stopped up/down a couple of times with itself, to "
1572 "compress high dynamic range. expose for the highlights before use."));
1573
1574 c->exposure_step = dt_bauhaus_slider_from_params(self, "exposure_stops");
1575 dt_bauhaus_slider_set_digits(c->exposure_step, 3);
1576 gtk_widget_set_tooltip_text(c->exposure_step, _("how many stops to shift the individual exposures apart"));
1577 gtk_widget_set_no_show_all(c->exposure_step, TRUE);
1578 gtk_widget_set_visible(c->exposure_step, p->exposure_fusion != 0 ? TRUE : FALSE);
1579
1580 // initially set to 1 (consistency with previous versions), but double-click resets to 0
1581 // to get a quick way to reach 0 with the mouse.
1582 c->exposure_bias = dt_bauhaus_slider_from_params(self, "exposure_bias");
1583 dt_bauhaus_slider_set_default(c->exposure_bias, 0.0f);
1584 dt_bauhaus_slider_set_digits(c->exposure_bias, 3);
1585 gtk_widget_set_tooltip_text(c->exposure_bias, _("whether to shift exposure up or down "
1586 "(-1: reduce highlight, +1: reduce shadows)"));
1587 gtk_widget_set_no_show_all(c->exposure_bias, TRUE);
1588 gtk_widget_set_visible(c->exposure_bias, p->exposure_fusion != 0 ? TRUE : FALSE);
1589 c->logbase = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.0f, 40.0f, 0, 0.0f, 2);
1590 dt_bauhaus_widget_set_label(c->logbase, N_("scale for graph"));
1591 gtk_box_pack_start(GTK_BOX(self->widget), c->logbase , TRUE, TRUE, 0); g_signal_connect(G_OBJECT(c->logbase), "value-changed", G_CALLBACK(logbase_callback), self);
1592
1593 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask
1594 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1595 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1596 gtk_widget_set_can_focus(GTK_WIDGET(c->area), TRUE);
1597 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(dt_iop_basecurve_draw), self);
1598 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(dt_iop_basecurve_button_press), self);
1599 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(dt_iop_basecurve_motion_notify), self);
1600 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(dt_iop_basecurve_leave_notify), self);
1601 g_signal_connect(G_OBJECT(c->area), "enter-notify-event", G_CALLBACK(dt_iop_basecurve_enter_notify), self);
1602 g_signal_connect(G_OBJECT(c->area), "configure-event", G_CALLBACK(area_resized), self);
1603 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(_scrolled), self);
1604 g_signal_connect(G_OBJECT(c->area), "key-press-event", G_CALLBACK(dt_iop_basecurve_key_press), self);
1605}
1606
1608{
1610 dt_draw_curve_destroy(c->minmax_curve);
1612
1614}
1615
1616// clang-format off
1617// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1618// vim: shiftwidth=2 expandtab tabstop=2 cindent
1619// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1620// clang-format on
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:317
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:897
void init(dt_iop_module_t *module)
Definition basecurve.c:977
static gboolean area_resized(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition basecurve.c:1423
const char ** description(struct dt_iop_module_t *self)
Definition basecurve.c:362
static gboolean dt_iop_basecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
Definition basecurve.c:1013
int default_group()
Definition basecurve.c:372
static const char sony_alpha[]
Definition basecurve.c:253
static const basecurve_preset_t basecurve_camera_presets[]
Definition basecurve.c:280
static const int basecurve_presets_cnt
Definition basecurve.c:341
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:631
static void set_presets(dt_iop_module_so_t *self, const basecurve_preset_t *presets, int count, gboolean camera)
Definition basecurve.c:387
__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:665
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:607
static const char nokia[]
Definition basecurve.c:264
static gboolean dt_iop_basecurve_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition basecurve.c:985
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:470
#define DT_GUI_CURVE_EDITOR_INSET
Definition basecurve.c:93
static const char fujifilm[]
Definition basecurve.c:263
static const char pentax[]
Definition basecurve.c:254
static float eval_grey(float x)
Definition basecurve.c:970
static gboolean dt_iop_basecurve_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
Definition basecurve.c:1471
static gboolean dt_iop_basecurve_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition basecurve.c:991
static const char nikon_alt[]
Definition basecurve.c:252
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition basecurve.c:941
#define BASECURVE_DEFAULT_STEP
Definition basecurve.c:1452
static __DT_CLONE_TARGETS__ void compute_features(float *const col, const int wd, const int ht)
Definition basecurve.c:533
static const char olympus[]
Definition basecurve.c:256
static void dt_iop_basecurve_sanity_check(dt_iop_module_t *self, GtkWidget *widget)
Definition basecurve.c:1209
const char * name()
Definition basecurve.c:357
void gui_update(struct dt_iop_module_t *self)
Definition basecurve.c:957
static const char neutral[]
Definition basecurve.c:248
void gui_init(struct dt_iop_module_t *self)
Definition basecurve.c:1538
dt_iop_basecurve_params3_t dt_iop_basecurve_params4_t
Definition basecurve.c:147
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition basecurve.c:1509
#define m
Definition basecurve.c:278
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:441
static const char konica_minolta[]
Definition basecurve.c:261
static const char leica[]
Definition basecurve.c:259
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:498
static const char canon_eos_alt[]
Definition basecurve.c:250
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition basecurve.c:382
#define MAXNODES
Definition basecurve.c:96
int flags()
Definition basecurve.c:377
void gui_cleanup(struct dt_iop_module_t *self)
Definition basecurve.c:1607
static const int basecurve_camera_presets_cnt
Definition basecurve.c:315
void init_presets(dt_iop_module_so_t *self)
Definition basecurve.c:423
struct dt_iop_basecurve_node_t dt_iop_basecurve_node_t
#define DT_IOP_TONECURVE_RES
Definition basecurve.c:95
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:882
static gboolean dt_iop_basecurve_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition basecurve.c:1309
static void logbase_callback(GtkWidget *slider, gpointer user_data)
Definition basecurve.c:1530
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition basecurve.c:948
static const char canon_eos[]
Definition basecurve.c:249
static const char ricoh[]
Definition basecurve.c:255
static gboolean dt_iop_basecurve_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition basecurve.c:1240
static float to_log(const float x, const float base)
Definition basecurve.c:997
static const char olympus_alt[]
Definition basecurve.c:257
static gboolean _scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
Definition basecurve.c:1454
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:1180
static const char nikon[]
Definition basecurve.c:251
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:860
static const char samsung[]
Definition basecurve.c:262
static const char panasonic[]
Definition basecurve.c:258
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:561
static float to_lin(const float x, const float base)
Definition basecurve.c:1005
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:164
static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, float dx, float dy, guint state)
Definition basecurve.c:1434
static const char kodak_easyshare[]
Definition basecurve.c:260
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3534
void dt_bauhaus_slider_set_default(GtkWidget *widget, float def)
Definition bauhaus.c:1640
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3483
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1780
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2016
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ DEVELOP_BLEND_CS_RGB_DISPLAY
Definition blend.h:59
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
@ IOP_CS_RGB
const dt_aligned_pixel_t f
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * key
#define CUBIC_SPLINE
Definition curve_tools.h:33
#define MONOTONE_HERMITE
Definition curve_tools.h:35
darktable_t darktable
Definition darktable.c:181
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
static float dt_rgb_norm(const float4 in, const int norm, const int work_profile, constant dt_colorspaces_iccprofile_info_cl_t *profile_info, read_only image2d_t lut)
@ DT_RGB_NORM_NONE
@ DT_RGB_NORM_LUMINANCE
#define dt_database_start_transaction(db)
Definition database.h:77
#define dt_database_release_transaction(db)
Definition database.h:78
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:41
static void dt_draw_curve_calc_values(dt_draw_curve_t *c, const float min, const float max, const int res, float *x, float *y)
Definition draw.h:309
static void dt_draw_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:143
static float dt_draw_curve_calc_value(dt_draw_curve_t *c, const float x)
Definition draw.h:345
static void dt_draw_curve_destroy(dt_draw_curve_t *c)
Definition draw.h:282
static void dt_draw_curve_set_point(dt_draw_curve_t *c, const int num, const float x, const float y)
Definition draw.h:288
static int dt_draw_curve_add_point(dt_draw_curve_t *c, const float x, const float y)
Definition draw.h:364
static dt_draw_curve_t * dt_draw_curve_new(const float min, const float max, unsigned int type)
Definition draw.h:266
static void dt_draw_loglog_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom, const float base)
Definition draw.h:191
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
Definition gtk.c:302
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
Definition gtk.c:2836
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
void dt_gui_presets_update_filter(const char *name, dt_dev_operation_t op, const int32_t version, const int filter)
void dt_gui_presets_update_ldr(const char *name, dt_dev_operation_t op, const int32_t version, const int ldrflag)
void dt_gui_presets_update_iso(const char *name, dt_dev_operation_t op, const int32_t version, const float min, const float max)
void dt_gui_presets_update_mml(const char *name, dt_dev_operation_t op, const int32_t version, const char *maker, const char *model, const char *lens)
void dt_gui_presets_update_autoapply(const char *name, dt_dev_operation_t op, const int32_t version, const int autoapply)
void dt_gui_presets_add_generic(const char *name, dt_dev_operation_t op, const int32_t version, const void *params, const int32_t params_size, const int32_t enabled, const dt_develop_blend_colorspace_t blend_cst)
@ FOR_RAW
Definition gui/presets.h:41
#define DT_GUI_MODULE(x)
void dt_gui_throttle_cancel(gpointer source)
void dt_gui_throttle_queue(gpointer source, dt_gui_throttle_callback_t callback, gpointer user_data)
void dt_iop_throttled_history_update(gpointer data)
Definition imageop.c:3135
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:316
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
#define IOP_GUI_FREE
Definition imageop.h:602
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:168
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:169
@ IOP_GROUP_TONES
Definition imageop.h:137
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void *const ovoid
static void dt_iop_estimate_exp(const float *const x, const float *const y, const int num, float *coeff)
static float dt_iop_eval_exp(const float *const coeff, const float x)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
static const float x
static dt_aligned_pixel_t float *const const float unbounded_coeffs[3][3]
const float v
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define M_PI
Definition math.h:45
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float r
const char * model
Definition basecurve.c:270
const char * name
Definition basecurve.c:268
const char * maker
Definition basecurve.c:269
dt_iop_basecurve_params_t params
Definition basecurve.c:273
struct dt_gui_gtk_t * gui
Definition darktable.h:775
const struct dt_database_t * db
Definition darktable.h:779
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_develop_t * develop
Definition darktable.h:770
PangoFontDescription * pango_font_desc
Definition bauhaus.h:274
struct dt_iop_module_t *void * data
gint scroll_mask
Definition gtk.h:224
dt_draw_curve_t * curve
Definition basecurve.c:345
float table[0x10000]
Definition basecurve.c:348
GtkWidget * cmb_preserve_colors
Definition basecurve.c:237
GtkDrawingArea * area
Definition basecurve.c:235
dt_draw_curve_t * minmax_curve
Definition basecurve.c:232
dt_iop_basecurve_node_t basecurve[3][20]
Definition basecurve.c:153
dt_iop_basecurve_node_t basecurve[3][20]
Definition basecurve.c:139
dt_iop_basecurve_node_t basecurve[3][20]
Definition basecurve.c:127
dt_iop_rgb_norms_t preserve_colors
Definition basecurve.c:120
dt_iop_basecurve_node_t basecurve[3][20]
Definition basecurve.c:111
GModule *dt_dev_operation_t op
Definition imageop.h:230
dt_iop_params_t * default_params
Definition imageop.h:307
GtkWidget * widget
Definition imageop.h:337
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
guint timeout_handle
Definition imageop.h:371
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29