Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
globaltonemap.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2012 Henrik Andersson.
4 Copyright (C) 2012-2013, 2016 johannes hanika.
5 Copyright (C) 2012 Richard Wonka.
6 Copyright (C) 2012-2014, 2016, 2019 Tobias Ellinghaus.
7 Copyright (C) 2012-2017 Ulrich Pegelow.
8 Copyright (C) 2014-2016 Roman Lebedev.
9 Copyright (C) 2015 Pedro Côrte-Real.
10 Copyright (C) 2017 Heiko Bauke.
11 Copyright (C) 2018-2021, 2023, 2025-2026 Aurélien PIERRE.
12 Copyright (C) 2018 Edgardo Hoszowski.
13 Copyright (C) 2018 Maurizio Paglia.
14 Copyright (C) 2018, 2020-2021 Pascal Obry.
15 Copyright (C) 2018 rawfiner.
16 Copyright (C) 2019 Andreas Schneider.
17 Copyright (C) 2019 Diederik ter Rahe.
18 Copyright (C) 2020 Aldric Renaudin.
19 Copyright (C) 2020-2021 Chris Elston.
20 Copyright (C) 2020, 2022 Diederik Ter Rahe.
21 Copyright (C) 2020-2021 Hubert Kowalski.
22 Copyright (C) 2020 Ralf Brown.
23 Copyright (C) 2022 Hanno Schwalm.
24 Copyright (C) 2022 Martin Bařinka.
25 Copyright (C) 2022 Philipp Lutz.
26
27 darktable is free software: you can redistribute it and/or modify
28 it under the terms of the GNU General Public License as published by
29 the Free Software Foundation, either version 3 of the License, or
30 (at your option) any later version.
31
32 darktable is distributed in the hope that it will be useful,
33 but WITHOUT ANY WARRANTY; without even the implied warranty of
34 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 GNU General Public License for more details.
36
37 You should have received a copy of the GNU General Public License
38 along with darktable. If not, see <http://www.gnu.org/licenses/>.
39*/
40#ifdef HAVE_CONFIG_H
41#include "config.h"
42#endif
43#include "widgets/bauhaus.h"
46#include "pixel/bilateral.h"
48#include "develop/develop.h"
49#include "develop/imageop.h"
51#include "develop/imageop_gui.h"
52#include "develop/tiling.h"
53
54#include "iop/iop_api.h"
55#include <assert.h>
56#include <stdlib.h>
57#include <string.h>
58
59#include <gtk/gtk.h>
60#include <inttypes.h>
61
62#define REDUCESIZE 64
63
65
66typedef enum _iop_operator_t
67{
68 OPERATOR_REINHARD, // $DESCRIPTION: "reinhard"
69 OPERATOR_FILMIC, // $DESCRIPTION: "filmic"
70 OPERATOR_DRAGO // $DESCRIPTION: "drago"
72
74{
75 _iop_operator_t operator; // $DEFAULT: OPERATOR_DRAGO
76 struct
77 {
78 float bias; // $MIN: 0.5 $MAX: 1 $DEFAULT: 0.85 $DESCRIPTION: "bias"
79 float max_light; // cd/m2 $MIN: 1 $MAX: 500 $DEFAULT: 100.0 $DESCRIPTION: "target"
81 float detail; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0
83
85{
87 struct
88 {
89 float bias;
90 float max_light; // cd/m2
92 float detail;
94
107
108const char *name()
109{
110 return _("global tonemap");
111}
112
113const char *deprecated_msg()
114{
115 return _("this module is deprecated. please use the filmic rgb module instead.");
116}
117
122
124{
125 return IOP_GROUP_TONES;
126}
127
129{
130 return IOP_CS_LAB;
131}
132
135{
136 default_input_format(self, pipe, piece, dsc);
137 dsc->channels = 4;
138 dsc->datatype = TYPE_FLOAT;
139}
140
141int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
142 void *new_params, const int new_version)
143{
144 if(old_version < 3 && new_version == 3)
145 {
148
149 // only appended detail, 0 is no-op
150 memcpy(n, o, sizeof(dt_iop_global_tonemap_params_t) - sizeof(float));
151 n->detail = 0.0f;
152 return 0;
153 }
154 return 1;
155}
156
158static inline void process_reinhard(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece,
159 const void *const ivoid, void *const ovoid,
160 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
162{
163 float *in = (float *)ivoid;
164 float *out = (float *)ovoid;
165 const int ch = 4;
167 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
168 {
169 float *inp = in + ch * k;
170 float *outp = out + ch * k;
171 float l = inp[0] / 100.0;
172 outp[0] = 100.0 * (l / (1.0f + l));
173 outp[1] = inp[1];
174 outp[2] = inp[2];
175 }
176}
177
179static inline void process_drago(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
180 const dt_dev_pixelpipe_iop_t *piece,
181 const void *const ivoid, void *const ovoid,
182 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
184{
186 float *in = (float *)ivoid;
187 float *out = (float *)ovoid;
188 const int ch = 4;
189
190 /* precalcs */
191 const float eps = 0.0001f;
192 float lwmax;
193 float tmp_lwmax = NAN;
194
195 // Drago needs the absolute Lmax value of the image. In pixelpipe FULL we can not reliably get this value
196 // as the pixelpipe might only see part of the image (region of interest). Therefore we try to get lwmax from
197 // the PREVIEW pixelpipe which luckily stores it for us.
198 if(self->dev->gui_attached && !IS_NULL_PTR(g) && !dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
199 {
201 const uint64_t hash = g->hash;
203
204 // note that the case 'hash == 0' on first invocation in a session implies that g->lwmax
205 // is NAN which initiates special handling below to avoid inconsistent results. in all
206 // other cases we make sure that the preview pipe has left us with proper readings for
207 // lwmax. if data are not yet there we need to wait (with timeout).
208 if(hash != piece->global_hash)
209 dt_control_log(_("inconsistent output"));
210
212 tmp_lwmax = g->lwmax;
214 }
215
216 // in all other cases we calculate lwmax here
217 if(isnan(tmp_lwmax))
218 {
219 lwmax = eps;
220 __OMP_PARALLEL_FOR__(reduction(max : lwmax) )
221 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
222 {
223 const float *inp = in + ch * k;
224 lwmax = fmaxf(lwmax, (inp[0] * 0.01f));
225 }
226 }
227 else
228 {
229 lwmax = tmp_lwmax;
230 }
231
232 // PREVIEW pixelpipe stores lwmax
233 if(self->dev->gui_attached && !IS_NULL_PTR(g) && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
234 {
235 uint64_t hash = piece->global_hash;
237 g->lwmax = lwmax;
238 g->hash = hash;
240 }
241
242 const float ldc = data->drago.max_light * 0.01 / log10f(lwmax + 1);
243 const float bl = logf(fmaxf(eps, data->drago.bias)) / logf(0.5);
245 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
246 {
247 float *inp = in + ch * k;
248 float *outp = out + ch * k;
249 float lw = inp[0] * 0.01f;
250 outp[0] = 100.0f
251 * (ldc * logf(fmaxf(eps, lw + 1.0f)) / logf(fmaxf(eps, 2.0f + (powf(lw / lwmax, bl)) * 8.0f)));
252 outp[1] = inp[1];
253 outp[2] = inp[2];
254 }
255}
256
258static inline void process_filmic(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece,
259 const void *const ivoid, void *const ovoid,
260 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
262{
263 float *in = (float *)ivoid;
264 float *out = (float *)ovoid;
265 const int ch = 4;
267 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
268 {
269 float *inp = in + ch * k;
270 float *outp = out + ch * k;
271 float l = inp[0] / 100.0;
272 float x = fmaxf(0.0f, l - 0.004f);
273 outp[0] = 100.0 * ((x * (6.2 * x + .5)) / (x * (6.2 * x + 1.7) + 0.06));
274 outp[1] = inp[1];
275 outp[2] = inp[2];
276 }
277}
278
279int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
280 void *const ovoid)
281{
282 const dt_iop_roi_t *const roi_in = &piece->roi_in;
283 const dt_iop_roi_t *const roi_out = &piece->roi_out;
285 const float scale = dt_dev_get_module_scale(pipe, roi_in);
286 const float sigma_r = 8.0f; // does not depend on scale
287 const float iw = piece->buf_in.width / scale;
288 const float ih = piece->buf_in.height / scale;
289 const float sigma_s = fminf(iw, ih) * 0.03f;
290 dt_bilateral_t *b = NULL;
291 if(data->detail != 0.0f)
292 {
293 b = dt_bilateral_init(roi_in->width, roi_in->height, sigma_s, sigma_r);
294 if(IS_NULL_PTR(b)) return 1;
295 // get detail from unchanged input buffer
296 dt_bilateral_splat(b, (float *)ivoid);
297 }
298
299 switch(data->operator)
300 {
302 process_reinhard(self, piece, ivoid, ovoid, roi_in, roi_out, data);
303 break;
304 case OPERATOR_DRAGO:
305 process_drago(self, pipe, piece, ivoid, ovoid, roi_in, roi_out, data);
306 break;
307 case OPERATOR_FILMIC:
308 process_filmic(self, piece, ivoid, ovoid, roi_in, roi_out, data);
309 break;
310 }
311
312 if(data->detail != 0.0f)
313 {
315 // and apply it to output buffer after logscale
316 dt_bilateral_slice_to_output(b, (float *)ivoid, (float *)ovoid, data->detail);
318 }
319
320 if(pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
321 return 0;
322}
323
324void 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)
325{
326 const dt_iop_roi_t *const roi_in = &piece->roi_in;
328
329 const float scale = dt_dev_get_module_scale(pipe, roi_in);
330 const float iw = piece->buf_in.width / scale;
331 const float ih = piece->buf_in.height / scale;
332 const float sigma_s = fminf(iw, ih) * 0.03f;
333 const float sigma_r = 8.0f;
334 const int detail = (d->detail != 0.0f);
335
336 const int width = roi_in->width;
337 const int height = roi_in->height;
338 const int channels = 4;
339
340 const size_t basebuffer = sizeof(float) * channels * width * height;
341
342 tiling->factor = 2.0f + (detail ? (float)dt_bilateral_memory_use2(width, height, sigma_s, sigma_r) / basebuffer : 0.0f);
343 tiling->maxbuf
344 = (detail ? MAX(1.0f, (float)dt_bilateral_singlebuffer_size2(width, height, sigma_s, sigma_r) / basebuffer) : 1.0f);
345 tiling->overhead = 0;
346 tiling->overlap = (detail ? ceilf(4 * sigma_s) : 0);
347 tiling->xalign = 1;
348 tiling->yalign = 1;
349 return;
350}
351
354{
357
358 d->operator= p->operator;
359 d->drago.bias = p->drago.bias;
360 d->drago.max_light = p->drago.max_light;
361 d->detail = p->detail;
362
363 // drago needs the maximum L-value of the whole image so it must not use tiling
364 if(d->operator == OPERATOR_DRAGO) piece->process_tiling_ready = 0;
365}
366
372
374{
375 dt_free_align(piece->data);
376 piece->data = NULL;
377}
378
379void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
380{
383
384 if(IS_NULL_PTR(w) || w == g->operator)
385 {
386 gtk_widget_set_visible(g->drago.bias, p->operator == OPERATOR_DRAGO);
387 gtk_widget_set_visible(g->drago.max_light, p->operator == OPERATOR_DRAGO);
388 }
389}
390
391void gui_update(struct dt_iop_module_t *self)
392{
394
395 gui_changed(self, NULL, 0);
396
398 g->lwmax = NAN;
399 g->hash = 0;
401}
402
403void gui_init(struct dt_iop_module_t *self)
404{
406
407 g->lwmax = NAN;
408 g->hash = 0;
409
410 g->operator = dt_bauhaus_combobox_from_params(self, N_("operator"));
411 gtk_widget_set_tooltip_text(g->operator, _("the global tonemap operator"));
412
413 g->drago.bias = dt_bauhaus_slider_from_params(self, "drago.bias");
414 gtk_widget_set_tooltip_text(g->drago.bias, _("the bias for tonemapper controls the linearity, "
415 "the higher the more details in blacks"));
416
417 g->drago.max_light = dt_bauhaus_slider_from_params(self, "drago.max_light");
418 gtk_widget_set_tooltip_text(g->drago.max_light, _("the target light for tonemapper specified as cd/m2"));
419
420 g->detail = dt_bauhaus_slider_from_params(self, N_("detail"));
422}
423
424void gui_cleanup(struct dt_iop_module_t *self)
425{
427}
428
429// clang-format off
430// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
431// vim: shiftwidth=2 expandtab tabstop=2 cindent
432// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
433// clang-format on
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3343
void dt_bilateral_free(dt_bilateral_t *b)
Definition bilateral.c:432
__DT_CLONE_TARGETS__ void dt_bilateral_splat(const dt_bilateral_t *b, const float *const in)
Definition bilateral.c:183
dt_bilateral_t * dt_bilateral_init(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:157
__DT_CLONE_TARGETS__ void dt_bilateral_slice_to_output(const dt_bilateral_t *const b, const float *const in, float *out, const float detail)
Definition bilateral.c:396
void dt_bilateral_blur(const dt_bilateral_t *b)
Definition bilateral.c:341
size_t dt_bilateral_memory_use2(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateralcl.c:72
float sigma_s
Definition bilateral.h:3
size_t dt_bilateral_singlebuffer_size2(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateralcl.c:82
float sigma_r
Definition bilateral.h:3
@ IOP_CS_LAB
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_iop_params_t
Definition dev_history.h:43
void default_input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:402
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
@ TYPE_FLOAT
Definition format.h:56
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)
int default_group()
static __DT_CLONE_TARGETS__ void process_reinhard(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, dt_iop_global_tonemap_data_t *data)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
_iop_operator_t
@ OPERATOR_FILMIC
@ OPERATOR_REINHARD
@ OPERATOR_DRAGO
const char * name()
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
void gui_init(struct dt_iop_module_t *self)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
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)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
void input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
int flags()
void gui_cleanup(struct dt_iop_module_t *self)
static __DT_CLONE_TARGETS__ void process_drago(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, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, dt_iop_global_tonemap_data_t *data)
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)
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
const char * deprecated_msg()
static __DT_CLONE_TARGETS__ void process_filmic(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, dt_iop_global_tonemap_data_t *data)
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:134
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
@ 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
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 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
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 eps
Definition rcd.c:81
#define lw
Definition retouch.c:1078
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:167
unsigned int channels
Definition format.h:83
dt_iop_buffer_type_t datatype
Definition format.h:85
struct dt_iop_global_tonemap_data_t::@43 drago
struct dt_iop_global_tonemap_gui_data_t::@44 drago
struct dt_iop_global_tonemap_params_t::@42 drago
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MAX(a, b)
Definition thinplate.c:29
Telling the user something happened.