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 "bauhaus/bauhaus.h"
44#include "common/bilateral.h"
45#include "common/bilateralcl.h"
46#include "common/math.h"
47#include "common/opencl.h"
48#include "control/control.h"
49#include "develop/develop.h"
50#include "develop/imageop.h"
52#include "develop/imageop_gui.h"
53#include "develop/tiling.h"
54
55#include "gui/gtk.h"
56#include "iop/iop_api.h"
57#include <assert.h>
58#include <stdlib.h>
59#include <string.h>
60
61#include <gtk/gtk.h>
62#include <inttypes.h>
63
64#define REDUCESIZE 64
65
67
68typedef enum _iop_operator_t
69{
70 OPERATOR_REINHARD, // $DESCRIPTION: "reinhard"
71 OPERATOR_FILMIC, // $DESCRIPTION: "filmic"
72 OPERATOR_DRAGO // $DESCRIPTION: "drago"
74
76{
77 _iop_operator_t operator; // $DEFAULT: OPERATOR_DRAGO
78 struct
79 {
80 float bias; // $MIN: 0.5 $MAX: 1 $DEFAULT: 0.85 $DESCRIPTION: "bias"
81 float max_light; // cd/m2 $MIN: 1 $MAX: 500 $DEFAULT: 100.0 $DESCRIPTION: "target"
83 float detail; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0
85
87{
89 struct
90 {
91 float bias;
92 float max_light; // cd/m2
94 float detail;
96
109
110const char *name()
111{
112 return _("global tonemap");
113}
114
115const char *deprecated_msg()
116{
117 return _("this module is deprecated. please use the filmic rgb module instead.");
118}
119
124
126{
127 return IOP_GROUP_TONES;
128}
129
131{
132 return IOP_CS_LAB;
133}
134
137{
138 default_input_format(self, pipe, piece, dsc);
139 dsc->channels = 4;
140 dsc->datatype = TYPE_FLOAT;
141}
142
143int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
144 void *new_params, const int new_version)
145{
146 if(old_version < 3 && new_version == 3)
147 {
150
151 // only appended detail, 0 is no-op
152 memcpy(n, o, sizeof(dt_iop_global_tonemap_params_t) - sizeof(float));
153 n->detail = 0.0f;
154 return 0;
155 }
156 return 1;
157}
158
160static inline void process_reinhard(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece,
161 const void *const ivoid, void *const ovoid,
162 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
164{
165 float *in = (float *)ivoid;
166 float *out = (float *)ovoid;
167 const int ch = 4;
169 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
170 {
171 float *inp = in + ch * k;
172 float *outp = out + ch * k;
173 float l = inp[0] / 100.0;
174 outp[0] = 100.0 * (l / (1.0f + l));
175 outp[1] = inp[1];
176 outp[2] = inp[2];
177 }
178}
179
181static inline void process_drago(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
182 const dt_dev_pixelpipe_iop_t *piece,
183 const void *const ivoid, void *const ovoid,
184 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
186{
188 float *in = (float *)ivoid;
189 float *out = (float *)ovoid;
190 const int ch = 4;
191
192 /* precalcs */
193 const float eps = 0.0001f;
194 float lwmax;
195 float tmp_lwmax = NAN;
196
197 // Drago needs the absolute Lmax value of the image. In pixelpipe FULL we can not reliably get this value
198 // as the pixelpipe might only see part of the image (region of interest). Therefore we try to get lwmax from
199 // the PREVIEW pixelpipe which luckily stores it for us.
200 if(self->dev->gui_attached && !IS_NULL_PTR(g) && !dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
201 {
203 const uint64_t hash = g->hash;
205
206 // note that the case 'hash == 0' on first invocation in a session implies that g->lwmax
207 // is NAN which initiates special handling below to avoid inconsistent results. in all
208 // other cases we make sure that the preview pipe has left us with proper readings for
209 // lwmax. if data are not yet there we need to wait (with timeout).
210 if(hash != piece->global_hash)
211 dt_control_log(_("inconsistent output"));
212
214 tmp_lwmax = g->lwmax;
216 }
217
218 // in all other cases we calculate lwmax here
219 if(isnan(tmp_lwmax))
220 {
221 lwmax = eps;
222 __OMP_PARALLEL_FOR__(reduction(max : lwmax) )
223 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
224 {
225 const float *inp = in + ch * k;
226 lwmax = fmaxf(lwmax, (inp[0] * 0.01f));
227 }
228 }
229 else
230 {
231 lwmax = tmp_lwmax;
232 }
233
234 // PREVIEW pixelpipe stores lwmax
235 if(self->dev->gui_attached && !IS_NULL_PTR(g) && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
236 {
237 uint64_t hash = piece->global_hash;
239 g->lwmax = lwmax;
240 g->hash = hash;
242 }
243
244 const float ldc = data->drago.max_light * 0.01 / log10f(lwmax + 1);
245 const float bl = logf(fmaxf(eps, data->drago.bias)) / logf(0.5);
247 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
248 {
249 float *inp = in + ch * k;
250 float *outp = out + ch * k;
251 float lw = inp[0] * 0.01f;
252 outp[0] = 100.0f
253 * (ldc * logf(fmaxf(eps, lw + 1.0f)) / logf(fmaxf(eps, 2.0f + (powf(lw / lwmax, bl)) * 8.0f)));
254 outp[1] = inp[1];
255 outp[2] = inp[2];
256 }
257}
258
260static inline void process_filmic(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece,
261 const void *const ivoid, void *const ovoid,
262 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
264{
265 float *in = (float *)ivoid;
266 float *out = (float *)ovoid;
267 const int ch = 4;
269 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
270 {
271 float *inp = in + ch * k;
272 float *outp = out + ch * k;
273 float l = inp[0] / 100.0;
274 float x = fmaxf(0.0f, l - 0.004f);
275 outp[0] = 100.0 * ((x * (6.2 * x + .5)) / (x * (6.2 * x + 1.7) + 0.06));
276 outp[1] = inp[1];
277 outp[2] = inp[2];
278 }
279}
280
281int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
282 void *const ovoid)
283{
284 const dt_iop_roi_t *const roi_in = &piece->roi_in;
285 const dt_iop_roi_t *const roi_out = &piece->roi_out;
287 const float scale = dt_dev_get_module_scale(pipe, roi_in);
288 const float sigma_r = 8.0f; // does not depend on scale
289 const float iw = piece->buf_in.width / scale;
290 const float ih = piece->buf_in.height / scale;
291 const float sigma_s = fminf(iw, ih) * 0.03f;
292 dt_bilateral_t *b = NULL;
293 if(data->detail != 0.0f)
294 {
295 b = dt_bilateral_init(roi_in->width, roi_in->height, sigma_s, sigma_r);
296 if(IS_NULL_PTR(b)) return 1;
297 // get detail from unchanged input buffer
298 dt_bilateral_splat(b, (float *)ivoid);
299 }
300
301 switch(data->operator)
302 {
304 process_reinhard(self, piece, ivoid, ovoid, roi_in, roi_out, data);
305 break;
306 case OPERATOR_DRAGO:
307 process_drago(self, pipe, piece, ivoid, ovoid, roi_in, roi_out, data);
308 break;
309 case OPERATOR_FILMIC:
310 process_filmic(self, piece, ivoid, ovoid, roi_in, roi_out, data);
311 break;
312 }
313
314 if(data->detail != 0.0f)
315 {
317 // and apply it to output buffer after logscale
318 dt_bilateral_slice_to_output(b, (float *)ivoid, (float *)ovoid, data->detail);
320 }
321
322 if(pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
323 return 0;
324}
325
326void 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)
327{
328 const dt_iop_roi_t *const roi_in = &piece->roi_in;
330
331 const float scale = dt_dev_get_module_scale(pipe, roi_in);
332 const float iw = piece->buf_in.width / scale;
333 const float ih = piece->buf_in.height / scale;
334 const float sigma_s = fminf(iw, ih) * 0.03f;
335 const float sigma_r = 8.0f;
336 const int detail = (d->detail != 0.0f);
337
338 const int width = roi_in->width;
339 const int height = roi_in->height;
340 const int channels = 4;
341
342 const size_t basebuffer = sizeof(float) * channels * width * height;
343
344 tiling->factor = 2.0f + (detail ? (float)dt_bilateral_memory_use2(width, height, sigma_s, sigma_r) / basebuffer : 0.0f);
345 tiling->maxbuf
346 = (detail ? MAX(1.0f, (float)dt_bilateral_singlebuffer_size2(width, height, sigma_s, sigma_r) / basebuffer) : 1.0f);
347 tiling->overhead = 0;
348 tiling->overlap = (detail ? ceilf(4 * sigma_s) : 0);
349 tiling->xalign = 1;
350 tiling->yalign = 1;
351 return;
352}
353
356{
359
360 d->operator= p->operator;
361 d->drago.bias = p->drago.bias;
362 d->drago.max_light = p->drago.max_light;
363 d->detail = p->detail;
364
365 // drago needs the maximum L-value of the whole image so it must not use tiling
366 if(d->operator == OPERATOR_DRAGO) piece->process_tiling_ready = 0;
367}
368
374
376{
377 dt_free_align(piece->data);
378 piece->data = NULL;
379}
380
381void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
382{
385
386 if(IS_NULL_PTR(w) || w == g->operator)
387 {
388 gtk_widget_set_visible(g->drago.bias, p->operator == OPERATOR_DRAGO);
389 gtk_widget_set_visible(g->drago.max_light, p->operator == OPERATOR_DRAGO);
390 }
391}
392
393void gui_update(struct dt_iop_module_t *self)
394{
396
397 gui_changed(self, NULL, 0);
398
400 g->lwmax = NAN;
401 g->hash = 0;
403}
404
405void gui_init(struct dt_iop_module_t *self)
406{
408
409 g->lwmax = NAN;
410 g->hash = 0;
411
412 g->operator = dt_bauhaus_combobox_from_params(self, N_("operator"));
413 gtk_widget_set_tooltip_text(g->operator, _("the global tonemap operator"));
414
415 g->drago.bias = dt_bauhaus_slider_from_params(self, "drago.bias");
416 gtk_widget_set_tooltip_text(g->drago.bias, _("the bias for tonemapper controls the linearity, "
417 "the higher the more details in blacks"));
418
419 g->drago.max_light = dt_bauhaus_slider_from_params(self, "drago.max_light");
420 gtk_widget_set_tooltip_text(g->drago.max_light, _("the target light for tonemapper specified as cd/m2"));
421
422 g->detail = dt_bauhaus_slider_from_params(self, N_("detail"));
424}
425
426void gui_cleanup(struct dt_iop_module_t *self)
427{
429}
430
431// clang-format off
432// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
433// vim: shiftwidth=2 expandtab tabstop=2 cindent
434// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
435// clang-format on
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3534
void dt_bilateral_free(dt_bilateral_t *b)
Definition bilateral.c:426
__DT_CLONE_TARGETS__ void dt_bilateral_splat(const dt_bilateral_t *b, const float *const in)
Definition bilateral.c:177
dt_bilateral_t * dt_bilateral_init(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:151
__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:390
void dt_bilateral_blur(const dt_bilateral_t *b)
Definition bilateral.c:335
int width
Definition bilateral.h:1
size_t dt_bilateral_memory_use2(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateralcl.c:64
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:74
int height
Definition bilateral.h:1
float sigma_r
Definition bilateral.h:3
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
@ IOP_CS_LAB
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_control_log(const char *msg,...)
Definition control.c:761
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#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
void dt_iop_params_t
Definition dev_history.h:41
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:367
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:118
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)
Definition format.c:57
@ TYPE_FLOAT
Definition format.h:46
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:131
#define IOP_GUI_FREE
Definition imageop.h:602
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:413
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:166
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:168
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:169
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:419
@ 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 const float x
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define eps
Definition rcd.c:81
#define lw
Definition retouch.c:1055
struct _GtkWidget GtkWidget
Definition splash.h:29
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:162
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
struct dt_iop_global_tonemap_data_t::@57 drago
struct dt_iop_global_tonemap_gui_data_t::@58 drago
struct dt_iop_global_tonemap_params_t::@56 drago
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72
#define MAX(a, b)
Definition thinplate.c:29