1 /*
2 This file is part of darktable,
3 Copyright (C) 2019-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 #include "bauhaus/bauhaus.h"
22 #include "common/colorspaces_inline_conversions.h"
23 #include "common/darktable.h"
24 #include "common/dwt.h"
25 #include "common/image.h"
26 #include "common/iop_profile.h"
27 #include "common/opencl.h"
28 #include "control/control.h"
29 #include "develop/develop.h"
30 #include "develop/imageop_gui.h"
31 #include "develop/imageop_math.h"
32 #include "develop/noise_generator.h"
33 #include "develop/openmp_maths.h"
34 #include "dtgtk/button.h"
35 #include "dtgtk/drawingarea.h"
36 #include "dtgtk/expander.h"
37 #include "dtgtk/paint.h"
38 #include "gui/accelerators.h"
39 #include "gui/color_picker_proxy.h"
40 #include "gui/gtk.h"
41 #include "gui/presets.h"
42 #include "iop/gaussian_elimination.h"
43 #include "iop/iop_api.h"
44
45
46 #include "develop/imageop.h"
47 #include "gui/draw.h"
48
49 #include <assert.h>
50 #include <math.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <time.h>
54
55
56 #define NORM_MIN 1.52587890625e-05f // norm can't be < to 2^(-16)
57 #define INVERSE_SQRT_3 0.5773502691896258f
58
59 #define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(1)
60
61
62 DT_MODULE_INTROSPECTION(4, dt_iop_filmicrgb_params_t)
63
64 /**
65 * DOCUMENTATION
66 *
67 * This code ports :
68 * 1. Troy Sobotka's filmic curves for Blender (and other softs)
69 * https://github.com/sobotka/OpenAgX/blob/master/lib/agx_colour.py
70 * 2. ACES camera logarithmic encoding
71 * https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESutil.Lin_to_Log2_param.ctl
72 *
73 * The ACES log implementation is taken from the profile_gamma.c IOP
74 * where it works in camera RGB space. Here, it works on an arbitrary RGB
75 * space. ProPhotoRGB has been chosen for its wide gamut coverage and
76 * for conveniency because it's already in darktable's libs. Any other
77 * RGB working space could work. This chouice could (should) also be
78 * exposed to the user.
79 *
80 * The filmic curves are tonecurves intended to simulate the luminance
81 * transfer function of film with "S" curves. These could be reproduced in
82 * the tonecurve.c IOP, however what we offer here is a parametric
83 * interface useful to remap accurately and promptly the middle grey
84 * to any arbitrary value chosen accordingly to the destination space.
85 *
86 * The combined use of both define a modern way to deal with large
87 * dynamic range photographs by remapping the values with a comprehensive
88 * interface avoiding many of the back and forth adjustments darktable
89 * is prone to enforce.
90 *
91 * */
92
93
94 /** Note :
95 * we use finite-math-only and fast-math because divisions by zero are manually avoided in the code
96 * fp-contract=fast enables hardware-accelerated Fused Multiply-Add
97 * the rest is loop reorganization and vectorization optimization
98 **/
99 #if defined(__GNUC__)
100 #pragma GCC optimize("unroll-loops", "tree-loop-if-convert", "tree-loop-distribution", "no-strict-aliasing", \
101 "loop-interchange", "tree-loop-im", "unswitch-loops", \
102 "tree-loop-ivcanon", "ira-loop-pressure", "split-ivs-in-unroller", \
103 "variable-expansion-in-unroller", "split-loops", "ivopts", "predictive-commoning", \
104 "finite-math-only", "fp-contract=fast", \
105 "fast-math", "no-math-errno")
106 #endif
107
108
109 typedef enum dt_iop_filmicrgb_methods_type_t
110 {
111 DT_FILMIC_METHOD_NONE = 0, // $DESCRIPTION: "no"
112 DT_FILMIC_METHOD_MAX_RGB = 1, // $DESCRIPTION: "max RGB"
113 DT_FILMIC_METHOD_LUMINANCE = 2, // $DESCRIPTION: "luminance Y"
114 DT_FILMIC_METHOD_POWER_NORM = 3, // $DESCRIPTION: "RGB power norm"
115 DT_FILMIC_METHOD_EUCLIDEAN_NORM_V2 = 5, // $DESCRIPTION: "RGB euclidean norm"
116 DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1 = 4, // $DESCRIPTION: "RGB euclidean norm (legacy)"
117 } dt_iop_filmicrgb_methods_type_t;
118
119
120 typedef enum dt_iop_filmicrgb_curve_type_t
121 {
122 DT_FILMIC_CURVE_POLY_4 = 0, // $DESCRIPTION: "hard"
123 DT_FILMIC_CURVE_POLY_3 = 1, // $DESCRIPTION: "soft"
124 DT_FILMIC_CURVE_RATIONAL = 2, // $DESCRIPTION: "safe"
125 } dt_iop_filmicrgb_curve_type_t;
126
127
128 typedef enum dt_iop_filmicrgb_colorscience_type_t
129 {
130 DT_FILMIC_COLORSCIENCE_V1 = 0, // $DESCRIPTION: "v3 (2019)"
131 DT_FILMIC_COLORSCIENCE_V2 = 1, // $DESCRIPTION: "v4 (2020)"
132 DT_FILMIC_COLORSCIENCE_V3 = 2, // $DESCRIPTION: "v5 (2021)"
133 } dt_iop_filmicrgb_colorscience_type_t;
134
135
136 typedef enum dt_iop_filmicrgb_reconstruction_type_t
137 {
138 DT_FILMIC_RECONSTRUCT_RGB = 0,
139 DT_FILMIC_RECONSTRUCT_RATIOS = 1,
140 } dt_iop_filmicrgb_reconstruction_type_t;
141
142
143 typedef struct dt_iop_filmic_rgb_spline_t
144 {
145 float DT_ALIGNED_PIXEL M1[4], M2[4], M3[4], M4[4], M5[4]; // factors for the interpolation polynom
146 float latitude_min, latitude_max; // bounds of the latitude == linear part by design
147 float y[5]; // controls nodes
148 float x[5]; // controls nodes
149 dt_iop_filmicrgb_curve_type_t type[2];
150 } dt_iop_filmic_rgb_spline_t;
151
152
153 typedef enum dt_iop_filmic_rgb_gui_mode_t
154 {
155 DT_FILMIC_GUI_LOOK = 0, // default GUI, showing only the contrast curve in a log/gamma space
156 DT_FILMIC_GUI_BASECURVE = 1, // basecurve-like GUI, showing the contrast and brightness curves, in lin/lin space
157 DT_FILMIC_GUI_BASECURVE_LOG = 2, // same as previous, but log-scaled
158 DT_FILMIC_GUI_RANGES = 3, // zone-system-like GUI, showing the range to range mapping
159 DT_FILMIC_GUI_LAST
160 } dt_iop_filmic_rgb_gui_mode_t;
161
162 // clang-format off
163 typedef struct dt_iop_filmicrgb_params_t
164 {
165 float grey_point_source; // $MIN: 0 $MAX: 100 $DEFAULT: 18.45 $DESCRIPTION: "middle gray luminance"
166 float black_point_source; // $MIN: -16 $MAX: -0.1 $DEFAULT: -8.0 $DESCRIPTION: "black relative exposure"
167 float white_point_source; // $MIN: 0 $MAX: 16 $DEFAULT: 4.0 $DESCRIPTION: "white relative exposure"
168 float reconstruct_threshold; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: +3.0 $DESCRIPTION: "threshold"
169 float reconstruct_feather; // $MIN: 0.25 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "transition"
170 float reconstruct_bloom_vs_details; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "bloom/reconstruct"
171 float reconstruct_grey_vs_color; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "gray/colorful details"
172 float reconstruct_structure_vs_texture; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION: "structure/texture"
173 float security_factor; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "dynamic range scaling"
174 float grey_point_target; // $MIN: 1 $MAX: 50 $DEFAULT: 18.45 $DESCRIPTION: "target middle gray"
175 float black_point_target; // $MIN: 0.000 $MAX: 20.000 $DEFAULT: 0.01517634 $DESCRIPTION: "target black luminance"
176 float white_point_target; // $MIN: 0 $MAX: 1600 $DEFAULT: 100 $DESCRIPTION: "target white luminance"
177 float output_power; // $MIN: 1 $MAX: 10 $DEFAULT: 4.0 $DESCRIPTION: "hardness"
178 float latitude; // $MIN: 0.01 $MAX: 100 $DEFAULT: 33.0
179 float contrast; // $MIN: 0 $MAX: 5 $DEFAULT: 1.35
180 float saturation; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "extreme luminance saturation"
181 float balance; // $MIN: -50 $MAX: 50 $DEFAULT: 0.0 $DESCRIPTION: "shadows/highlights balance"
182 float noise_level; // $MIN: 0.0 $MAX: 6.0 $DEFAULT: 0.2f $DESCRIPTION: "add noise in highlights"
183 dt_iop_filmicrgb_methods_type_t preserve_color; // $DEFAULT: DT_FILMIC_METHOD_POWER_NORM $DESCRIPTION: "preserve chrominance"
184 dt_iop_filmicrgb_colorscience_type_t version; // $DEFAULT: DT_FILMIC_COLORSCIENCE_V3 $DESCRIPTION: "color science"
185 gboolean auto_hardness; // $DEFAULT: TRUE $DESCRIPTION: "auto adjust hardness"
186 gboolean custom_grey; // $DEFAULT: FALSE $DESCRIPTION: "use custom middle-gray values"
187 int high_quality_reconstruction; // $MIN: 0 $MAX: 10 $DEFAULT: 1 $DESCRIPTION: "iterations of high-quality reconstruction"
188 int noise_distribution; // $DEFAULT: DT_NOISE_GAUSSIAN $DESCRIPTION: "type of noise"
189 dt_iop_filmicrgb_curve_type_t shadows; // $DEFAULT: DT_FILMIC_CURVE_RATIONAL $DESCRIPTION: "contrast in shadows"
190 dt_iop_filmicrgb_curve_type_t highlights; // $DEFAULT: DT_FILMIC_CURVE_RATIONAL $DESCRIPTION: "contrast in highlights"
191 gboolean compensate_icc_black; // $DEFAULT: FALSE $DESCRIPTION: "compensate output ICC profile black point"
192 gint internal_version; // $DEFAULT: 2020 $DESCRIPTION: "version of the spline generator"
193 } dt_iop_filmicrgb_params_t;
194 // clang-format on
195
196
197 // custom buttons in graph views
198 typedef enum dt_iop_filmicrgb_gui_button_t
199 {
200 DT_FILMIC_GUI_BUTTON_TYPE = 0,
201 DT_FILMIC_GUI_BUTTON_LABELS = 1,
202 DT_FILMIC_GUI_BUTTON_LAST
203 } dt_iop_filmicrgb_gui_button_t;
204
205 // custom buttons in graph views - data
206 typedef struct dt_iop_filmicrgb_gui_button_data_t
207 {
208 // coordinates in GUI - compute them only in the drawing function
209 float left;
210 float right;
211 float top;
212 float bottom;
213 float w;
214 float h;
215
216 // properties
217 gint mouse_hover; // whether it should be acted on / mouse is over it
218 GtkStateFlags state;
219
220 // icon drawing, function as set in dtgtk/paint.h
221 DTGTKCairoPaintIconFunc icon;
222
223 } dt_iop_filmicrgb_gui_button_data_t;
224
225
226 typedef struct dt_iop_filmicrgb_gui_data_t
227 {
228 GtkWidget *white_point_source;
229 GtkWidget *grey_point_source;
230 GtkWidget *black_point_source;
231 GtkWidget *reconstruct_threshold, *reconstruct_bloom_vs_details, *reconstruct_grey_vs_color,
232 *reconstruct_structure_vs_texture, *reconstruct_feather;
233 GtkWidget *show_highlight_mask;
234 GtkWidget *security_factor;
235 GtkWidget *auto_button;
236 GtkWidget *grey_point_target;
237 GtkWidget *white_point_target;
238 GtkWidget *black_point_target;
239 GtkWidget *output_power;
240 GtkWidget *latitude;
241 GtkWidget *contrast;
242 GtkWidget *saturation;
243 GtkWidget *balance;
244 GtkWidget *preserve_color;
245 GtkWidget *autoset_display_gamma;
246 GtkWidget *shadows, *highlights;
247 GtkWidget *version;
248 GtkWidget *auto_hardness;
249 GtkWidget *custom_grey;
250 GtkWidget *high_quality_reconstruction;
251 GtkWidget *noise_level, *noise_distribution;
252 GtkWidget *compensate_icc_black;
253 GtkNotebook *notebook;
254 GtkDrawingArea *area;
255 struct dt_iop_filmic_rgb_spline_t spline DT_ALIGNED_ARRAY;
256 gint show_mask;
257 dt_iop_filmic_rgb_gui_mode_t gui_mode; // graph display mode
258 gint gui_show_labels;
259 gint gui_hover;
260 gint gui_sizes_inited;
261 dt_iop_filmicrgb_gui_button_t active_button; // ID of the button under cursor
262 dt_iop_filmicrgb_gui_button_data_t buttons[DT_FILMIC_GUI_BUTTON_LAST];
263
264 // Cache Pango and Cairo stuff for the equalizer drawing
265 float line_height;
266 float sign_width;
267 float zero_width;
268 float graph_width;
269 float graph_height;
270 int inset;
271 int inner_padding;
272
273 GtkAllocation allocation;
274 PangoRectangle ink;
275 GtkStyleContext *context;
276 } dt_iop_filmicrgb_gui_data_t;
277
278 typedef struct dt_iop_filmicrgb_data_t
279 {
280 float max_grad;
281 float white_source;
282 float grey_source;
283 float black_source;
284 float reconstruct_threshold;
285 float reconstruct_feather;
286 float reconstruct_bloom_vs_details;
287 float reconstruct_grey_vs_color;
288 float reconstruct_structure_vs_texture;
289 float normalize;
290 float dynamic_range;
291 float saturation;
292 float output_power;
293 float contrast;
294 float sigma_toe, sigma_shoulder;
295 float noise_level;
296 int preserve_color;
297 int version;
298 int high_quality_reconstruction;
299 struct dt_iop_filmic_rgb_spline_t spline DT_ALIGNED_ARRAY;
300 dt_noise_distribution_t noise_distribution;
301 } dt_iop_filmicrgb_data_t;
302
303
304 typedef struct dt_iop_filmicrgb_global_data_t
305 {
306 int kernel_filmic_rgb_split;
307 int kernel_filmic_rgb_chroma;
308 int kernel_filmic_mask;
309 int kernel_filmic_show_mask;
310 int kernel_filmic_inpaint_noise;
311 int kernel_filmic_bspline_vertical;
312 int kernel_filmic_bspline_horizontal;
313 int kernel_filmic_init_reconstruct;
314 int kernel_filmic_wavelets_detail;
315 int kernel_filmic_wavelets_reconstruct;
316 int kernel_filmic_compute_ratios;
317 int kernel_filmic_restore_ratios;
318 } dt_iop_filmicrgb_global_data_t;
319
320
name()321 const char *name()
322 {
323 return _("filmic rgb");
324 }
325
aliases()326 const char *aliases()
327 {
328 return _("tone mapping|curve|view transform|contrast|saturation|highlights");
329 }
330
description(struct dt_iop_module_t * self)331 const char *description(struct dt_iop_module_t *self)
332 {
333 return dt_iop_set_description(self, _("apply a view transform to prepare the scene-referred pipeline\n"
334 "for display on SDR screens and paper prints\n"
335 "while preventing clipping in non-destructive ways"),
336 _("corrective and creative"),
337 _("linear or non-linear, RGB, scene-referred"),
338 _("non-linear, RGB"),
339 _("non-linear, RGB, display-referred"));
340 }
341
default_group()342 int default_group()
343 {
344 return IOP_GROUP_TONE | IOP_GROUP_TECHNICAL;
345 }
346
flags()347 int flags()
348 {
349 return IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_SUPPORTS_BLENDING;
350 }
351
default_colorspace(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)352 int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
353 {
354 return iop_cs_rgb;
355 }
356
legacy_params(dt_iop_module_t * self,const void * const old_params,const int old_version,void * new_params,const int new_version)357 int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
358 const int new_version)
359 {
360 if(old_version == 1 && new_version == 4)
361 {
362 typedef struct dt_iop_filmicrgb_params_v1_t
363 {
364 float grey_point_source;
365 float black_point_source;
366 float white_point_source;
367 float security_factor;
368 float grey_point_target;
369 float black_point_target;
370 float white_point_target;
371 float output_power;
372 float latitude;
373 float contrast;
374 float saturation;
375 float balance;
376 int preserve_color;
377 } dt_iop_filmicrgb_params_v1_t;
378
379 dt_iop_filmicrgb_params_v1_t *o = (dt_iop_filmicrgb_params_v1_t *)old_params;
380 dt_iop_filmicrgb_params_t *n = (dt_iop_filmicrgb_params_t *)new_params;
381 dt_iop_filmicrgb_params_t *d = (dt_iop_filmicrgb_params_t *)self->default_params;
382
383 *n = *d; // start with a fresh copy of default parameters
384
385 n->grey_point_source = o->grey_point_source;
386 n->white_point_source = o->white_point_source;
387 n->black_point_source = o->black_point_source;
388 n->security_factor = o->security_factor;
389 n->grey_point_target = o->grey_point_target;
390 n->black_point_target = o->black_point_target;
391 n->white_point_target = o->white_point_target;
392 n->output_power = o->output_power;
393 n->latitude = o->latitude;
394 n->contrast = o->contrast;
395 n->saturation = o->saturation;
396 n->balance = o->balance;
397 n->preserve_color = o->preserve_color;
398 n->shadows = DT_FILMIC_CURVE_POLY_4;
399 n->highlights = DT_FILMIC_CURVE_POLY_3;
400 n->reconstruct_threshold
401 = 6.0f; // for old edits, this ensures clipping threshold >> white level, so it's a no-op
402 n->reconstruct_bloom_vs_details = d->reconstruct_bloom_vs_details;
403 n->reconstruct_grey_vs_color = d->reconstruct_grey_vs_color;
404 n->reconstruct_structure_vs_texture = d->reconstruct_structure_vs_texture;
405 n->reconstruct_feather = 3.0f;
406 n->version = DT_FILMIC_COLORSCIENCE_V1;
407 n->auto_hardness = TRUE;
408 n->custom_grey = TRUE;
409 n->high_quality_reconstruction = 0;
410 n->noise_distribution = d->noise_distribution;
411 n->noise_level = 0.f;
412 n->internal_version = 2019;
413 n->compensate_icc_black = FALSE;
414 return 0;
415 }
416 if(old_version == 2 && new_version == 4)
417 {
418 typedef struct dt_iop_filmicrgb_params_v2_t
419 {
420 float grey_point_source;
421 float black_point_source;
422 float white_point_source;
423 float reconstruct_threshold;
424 float reconstruct_feather;
425 float reconstruct_bloom_vs_details;
426 float reconstruct_grey_vs_color;
427 float reconstruct_structure_vs_texture;
428 float security_factor;
429 float grey_point_target;
430 float black_point_target;
431 float white_point_target;
432 float output_power;
433 float latitude;
434 float contrast;
435 float saturation;
436 float balance;
437 int preserve_color;
438 int version;
439 int auto_hardness;
440 int custom_grey;
441 int high_quality_reconstruction;
442 dt_iop_filmicrgb_curve_type_t shadows;
443 dt_iop_filmicrgb_curve_type_t highlights;
444 } dt_iop_filmicrgb_params_v2_t;
445
446 dt_iop_filmicrgb_params_v2_t *o = (dt_iop_filmicrgb_params_v2_t *)old_params;
447 dt_iop_filmicrgb_params_t *n = (dt_iop_filmicrgb_params_t *)new_params;
448 dt_iop_filmicrgb_params_t *d = (dt_iop_filmicrgb_params_t *)self->default_params;
449
450 *n = *d; // start with a fresh copy of default parameters
451
452 n->grey_point_source = o->grey_point_source;
453 n->white_point_source = o->white_point_source;
454 n->black_point_source = o->black_point_source;
455 n->security_factor = o->security_factor;
456 n->grey_point_target = o->grey_point_target;
457 n->black_point_target = o->black_point_target;
458 n->white_point_target = o->white_point_target;
459 n->output_power = o->output_power;
460 n->latitude = o->latitude;
461 n->contrast = o->contrast;
462 n->saturation = o->saturation;
463 n->balance = o->balance;
464 n->preserve_color = o->preserve_color;
465 n->shadows = o->shadows;
466 n->highlights = o->highlights;
467 n->reconstruct_threshold = o->reconstruct_threshold;
468 n->reconstruct_bloom_vs_details = o->reconstruct_bloom_vs_details;
469 n->reconstruct_grey_vs_color = o->reconstruct_grey_vs_color;
470 n->reconstruct_structure_vs_texture = o->reconstruct_structure_vs_texture;
471 n->reconstruct_feather = o->reconstruct_feather;
472 n->version = o->version;
473 n->auto_hardness = o->auto_hardness;
474 n->custom_grey = o->custom_grey;
475 n->high_quality_reconstruction = o->high_quality_reconstruction;
476 n->noise_level = d->noise_level;
477 n->noise_distribution = d->noise_distribution;
478 n->noise_level = 0.f;
479 n->internal_version = 2019;
480 n->compensate_icc_black = FALSE;
481 return 0;
482 }
483 if(old_version == 3 && new_version == 4)
484 {
485 typedef struct dt_iop_filmicrgb_params_v3_t
486 {
487 float grey_point_source; // $MIN: 0 $MAX: 100 $DEFAULT: 18.45 $DESCRIPTION: "middle gray luminance"
488 float black_point_source; // $MIN: -16 $MAX: -0.1 $DEFAULT: -8.0 $DESCRIPTION: "black relative exposure"
489 float white_point_source; // $MIN: 0 $MAX: 16 $DEFAULT: 4.0 $DESCRIPTION: "white relative exposure"
490 float reconstruct_threshold; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: +3.0 $DESCRIPTION: "threshold"
491 float reconstruct_feather; // $MIN: 0.25 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "transition"
492 float reconstruct_bloom_vs_details; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION:
493 // "bloom/reconstruct"
494 float reconstruct_grey_vs_color; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "gray/colorful
495 // details"
496 float reconstruct_structure_vs_texture; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION:
497 // "structure/texture"
498 float security_factor; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "dynamic range scaling"
499 float grey_point_target; // $MIN: 1 $MAX: 50 $DEFAULT: 18.45 $DESCRIPTION: "target middle gray"
500 float black_point_target; // $MIN: 0 $MAX: 20 $DEFAULT: 0 $DESCRIPTION: "target black luminance"
501 float white_point_target; // $MIN: 0 $MAX: 1600 $DEFAULT: 100 $DESCRIPTION: "target white luminance"
502 float output_power; // $MIN: 1 $MAX: 10 $DEFAULT: 4.0 $DESCRIPTION: "hardness"
503 float latitude; // $MIN: 0.01 $MAX: 100 $DEFAULT: 33.0
504 float contrast; // $MIN: 0 $MAX: 5 $DEFAULT: 1.50
505 float saturation; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "extreme luminance saturation"
506 float balance; // $MIN: -50 $MAX: 50 $DEFAULT: 0.0 $DESCRIPTION: "shadows/highlights balance"
507 float noise_level; // $MIN: 0.0 $MAX: 6.0 $DEFAULT: 0.1f $DESCRIPTION: "add noise in highlights"
508 dt_iop_filmicrgb_methods_type_t preserve_color; // $DEFAULT: DT_FILMIC_METHOD_POWER_NORM $DESCRIPTION:
509 // "preserve chrominance"
510 dt_iop_filmicrgb_colorscience_type_t version; // $DEFAULT: DT_FILMIC_COLORSCIENCE_V3 $DESCRIPTION: "color
511 // science"
512 gboolean auto_hardness; // $DEFAULT: TRUE $DESCRIPTION: "auto adjust hardness"
513 gboolean custom_grey; // $DEFAULT: FALSE $DESCRIPTION: "use custom middle-gray values"
514 int high_quality_reconstruction; // $MIN: 0 $MAX: 10 $DEFAULT: 1 $DESCRIPTION: "iterations of high-quality
515 // reconstruction"
516 int noise_distribution; // $DEFAULT: DT_NOISE_POISSONIAN $DESCRIPTION: "type of noise"
517 dt_iop_filmicrgb_curve_type_t shadows; // $DEFAULT: DT_FILMIC_CURVE_POLY_4 $DESCRIPTION: "contrast in shadows"
518 dt_iop_filmicrgb_curve_type_t highlights; // $DEFAULT: DT_FILMIC_CURVE_POLY_4 $DESCRIPTION: "contrast in
519 // highlights"
520 } dt_iop_filmicrgb_params_v3_t;
521
522 dt_iop_filmicrgb_params_v3_t *o = (dt_iop_filmicrgb_params_v3_t *)old_params;
523 dt_iop_filmicrgb_params_t *n = (dt_iop_filmicrgb_params_t *)new_params;
524 dt_iop_filmicrgb_params_t *d = (dt_iop_filmicrgb_params_t *)self->default_params;
525
526 *n = *d; // start with a fresh copy of default parameters
527
528 n->grey_point_source = o->grey_point_source;
529 n->white_point_source = o->white_point_source;
530 n->black_point_source = o->black_point_source;
531 n->security_factor = o->security_factor;
532 n->grey_point_target = o->grey_point_target;
533 n->black_point_target = o->black_point_target;
534 n->white_point_target = o->white_point_target;
535 n->output_power = o->output_power;
536 n->latitude = o->latitude;
537 n->contrast = o->contrast;
538 n->saturation = o->saturation;
539 n->balance = o->balance;
540 n->preserve_color = o->preserve_color;
541 n->shadows = o->shadows;
542 n->highlights = o->highlights;
543 n->reconstruct_threshold = o->reconstruct_threshold;
544 n->reconstruct_bloom_vs_details = o->reconstruct_bloom_vs_details;
545 n->reconstruct_grey_vs_color = o->reconstruct_grey_vs_color;
546 n->reconstruct_structure_vs_texture = o->reconstruct_structure_vs_texture;
547 n->reconstruct_feather = o->reconstruct_feather;
548 n->version = o->version;
549 n->auto_hardness = o->auto_hardness;
550 n->custom_grey = o->custom_grey;
551 n->high_quality_reconstruction = o->high_quality_reconstruction;
552 n->noise_level = d->noise_level;
553 n->noise_distribution = d->noise_distribution;
554 n->noise_level = d->noise_level;
555 n->internal_version = 2019;
556 n->compensate_icc_black = FALSE;
557 return 0;
558 }
559 return 1;
560 }
561
562
563 #ifdef _OPENMP
564 #pragma omp declare simd aligned(pixel:16)
565 #endif
pixel_rgb_norm_power(const float pixel[4])566 static inline float pixel_rgb_norm_power(const float pixel[4])
567 {
568 // weird norm sort of perceptual. This is black magic really, but it looks good.
569 // the full norm is (R^3 + G^3 + B^3) / (R^2 + G^2 + B^2) and it should be in ]0; +infinity[
570
571 float numerator = 0.0f;
572 float denominator = 0.0f;
573
574 for(int c = 0; c < 3; c++)
575 {
576 const float value = fabsf(pixel[c]);
577 const float RGB_square = value * value;
578 const float RGB_cubic = RGB_square * value;
579 numerator += RGB_cubic;
580 denominator += RGB_square;
581 }
582
583 return numerator / fmaxf(denominator, 1e-12f); // prevent from division-by-0 (note: (1e-6)^2 = 1e-12
584 }
585
586
587 #ifdef _OPENMP
588 #pragma omp declare simd aligned(pixel : 16) uniform(variant, work_profile)
589 #endif
get_pixel_norm(const float pixel[4],const dt_iop_filmicrgb_methods_type_t variant,const dt_iop_order_iccprofile_info_t * const work_profile)590 static inline float get_pixel_norm(const float pixel[4], const dt_iop_filmicrgb_methods_type_t variant,
591 const dt_iop_order_iccprofile_info_t *const work_profile)
592 {
593 // a newly added norm should satisfy the condition that it is linear with respect to grey pixels:
594 // norm(R, G, B) = norm(x, x, x) = x
595 // the desaturation code in chroma preservation mode relies on this assumption.
596 // DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1 is an exception to this and is marked as legacy.
597 // DT_FILMIC_METHOD_EUCLIDEAN_NORM_V2 takes the Euclidean norm and scales it such that
598 // norm(1, 1, 1) = 1.
599 switch(variant)
600 {
601 case(DT_FILMIC_METHOD_MAX_RGB):
602 return fmaxf(fmaxf(pixel[0], pixel[1]), pixel[2]);
603
604 case(DT_FILMIC_METHOD_LUMINANCE):
605 return (work_profile)
606 ? dt_ioppr_get_rgb_matrix_luminance(pixel, work_profile->matrix_in, work_profile->lut_in,
607 work_profile->unbounded_coeffs_in, work_profile->lutsize,
608 work_profile->nonlinearlut)
609 : dt_camera_rgb_luminance(pixel);
610
611 case(DT_FILMIC_METHOD_POWER_NORM):
612 return pixel_rgb_norm_power(pixel);
613
614 case(DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1):
615 return sqrtf(sqf(pixel[0]) + sqf(pixel[1]) + sqf(pixel[2]));
616
617 case(DT_FILMIC_METHOD_EUCLIDEAN_NORM_V2):
618 return sqrtf(sqf(pixel[0]) + sqf(pixel[1]) + sqf(pixel[2])) * INVERSE_SQRT_3;
619
620 default:
621 return (work_profile)
622 ? dt_ioppr_get_rgb_matrix_luminance(pixel, work_profile->matrix_in, work_profile->lut_in,
623 work_profile->unbounded_coeffs_in, work_profile->lutsize,
624 work_profile->nonlinearlut)
625 : dt_camera_rgb_luminance(pixel);
626 }
627 }
628
629
630 #ifdef _OPENMP
631 #pragma omp declare simd uniform(grey, black, dynamic_range)
632 #endif
log_tonemapping_v1(const float x,const float grey,const float black,const float dynamic_range)633 static inline float log_tonemapping_v1(const float x, const float grey, const float black,
634 const float dynamic_range)
635 {
636 const float temp = (log2f(x / grey) - black) / dynamic_range;
637 return fmaxf(fminf(temp, 1.0f), NORM_MIN);
638 }
639
640
641 #ifdef _OPENMP
642 #pragma omp declare simd uniform(grey, black, dynamic_range)
643 #endif
log_tonemapping_v2(const float x,const float grey,const float black,const float dynamic_range)644 static inline float log_tonemapping_v2(const float x, const float grey, const float black,
645 const float dynamic_range)
646 {
647 return clamp_simd((log2f(x / grey) - black) / dynamic_range);
648 }
649
650 #ifdef _OPENMP
651 #pragma omp declare simd uniform(grey, black, dynamic_range)
652 #endif
exp_tonemapping_v2(const float x,const float grey,const float black,const float dynamic_range)653 static inline float exp_tonemapping_v2(const float x, const float grey, const float black,
654 const float dynamic_range)
655 {
656 // inverse of log_tonemapping
657 return grey * exp2f(dynamic_range * x + black);
658 }
659
660
661 #ifdef _OPENMP
662 #pragma omp declare simd aligned(M1, M2, M3, M4 : 16) uniform(M1, M2, M3, M4, M5, latitude_min, latitude_max)
663 #endif
filmic_spline(const float x,const float M1[4],const float M2[4],const float M3[4],const float M4[4],const float M5[4],const float latitude_min,const float latitude_max,const dt_iop_filmicrgb_curve_type_t type[2])664 static inline float filmic_spline(const float x, const float M1[4], const float M2[4], const float M3[4],
665 const float M4[4], const float M5[4], const float latitude_min,
666 const float latitude_max, const dt_iop_filmicrgb_curve_type_t type[2])
667 {
668 // if type polynomial :
669 // y = M5 * x⁴ + M4 * x³ + M3 * x² + M2 * x¹ + M1 * x⁰
670 // but we rewrite it using Horner factorisation, to spare ops and enable FMA in available
671 // else if type rational :
672 // y = M1 * (M2 * (x - x_0)² + (x - x_0)) / (M2 * (x - x_0)² + (x - x_0) + M3)
673
674 float result;
675
676 if(x < latitude_min)
677 {
678 // toe
679 if(type[0] == DT_FILMIC_CURVE_POLY_4)
680 {
681 // polynomial toe, 4th order
682 result = M1[0] + x * (M2[0] + x * (M3[0] + x * (M4[0] + x * M5[0])));
683 }
684 else if(type[0] == DT_FILMIC_CURVE_POLY_3)
685 {
686 // polynomial toe, 3rd order
687 result = M1[0] + x * (M2[0] + x * (M3[0] + x * M4[0]));
688 }
689 else
690 {
691 // rational toe
692 const float xi = latitude_min - x;
693 const float rat = xi * (xi * M2[0] + 1.f);
694 result = M4[0] - M1[0] * rat / (rat + M3[0]);
695 }
696 }
697 else if(x > latitude_max)
698 {
699 // shoulder
700 if(type[1] == DT_FILMIC_CURVE_POLY_4)
701 {
702 // polynomial shoulder, 4th order
703 result = M1[1] + x * (M2[1] + x * (M3[1] + x * (M4[1] + x * M5[1])));
704 }
705 else if(type[1] == DT_FILMIC_CURVE_POLY_3)
706 {
707 // polynomial shoulder, 3rd order
708 result = M1[1] + x * (M2[1] + x * (M3[1] + x * M4[1]));
709 }
710 else
711 {
712 // rational toe
713 const float xi = x - latitude_max;
714 const float rat = xi * (xi * M2[1] + 1.f);
715 result = M4[1] + M1[1] * rat / (rat + M3[1]);
716 }
717 }
718 else
719 {
720 // latitude
721 result = M1[2] + x * M2[2];
722 }
723
724 return result;
725 }
726
727
728 #ifdef _OPENMP
729 #pragma omp declare simd uniform(sigma_toe, sigma_shoulder)
730 #endif
filmic_desaturate_v1(const float x,const float sigma_toe,const float sigma_shoulder,const float saturation)731 static inline float filmic_desaturate_v1(const float x, const float sigma_toe, const float sigma_shoulder,
732 const float saturation)
733 {
734 const float radius_toe = x;
735 const float radius_shoulder = 1.0f - x;
736
737 const float key_toe = expf(-0.5f * radius_toe * radius_toe / sigma_toe);
738 const float key_shoulder = expf(-0.5f * radius_shoulder * radius_shoulder / sigma_shoulder);
739
740 return 1.0f - clamp_simd((key_toe + key_shoulder) / saturation);
741 }
742
743
744 #ifdef _OPENMP
745 #pragma omp declare simd uniform(sigma_toe, sigma_shoulder)
746 #endif
filmic_desaturate_v2(const float x,const float sigma_toe,const float sigma_shoulder,const float saturation)747 static inline float filmic_desaturate_v2(const float x, const float sigma_toe, const float sigma_shoulder,
748 const float saturation)
749 {
750 const float radius_toe = x;
751 const float radius_shoulder = 1.0f - x;
752 const float sat2 = 0.5f / sqrtf(saturation);
753 const float key_toe = expf(-radius_toe * radius_toe / sigma_toe * sat2);
754 const float key_shoulder = expf(-radius_shoulder * radius_shoulder / sigma_shoulder * sat2);
755
756 return (saturation - (key_toe + key_shoulder) * (saturation));
757 }
758
759
760 #ifdef _OPENMP
761 #pragma omp declare simd
762 #endif
linear_saturation(const float x,const float luminance,const float saturation)763 static inline float linear_saturation(const float x, const float luminance, const float saturation)
764 {
765 return luminance + saturation * (x - luminance);
766 }
767
768
769 #define MAX_NUM_SCALES 12
770
771
772 #ifdef _OPENMP
773 #pragma omp declare simd aligned(in, mask : 64) uniform(feathering, normalize, width, height, ch)
774 #endif
mask_clipped_pixels(const float * const restrict in,float * const restrict mask,const float normalize,const float feathering,const size_t width,const size_t height,const size_t ch)775 static inline gint mask_clipped_pixels(const float *const restrict in, float *const restrict mask,
776 const float normalize, const float feathering, const size_t width,
777 const size_t height, const size_t ch)
778 {
779 /* 1. Detect if pixels are clipped and count them,
780 * 2. assign them a weight in [0. ; 1.] depending on how close from clipping they are. The weights are defined
781 * by a sigmoid centered in `reconstruct_threshold` so the transition is soft and symmetrical
782 */
783
784 int clipped = 0;
785
786 #ifdef __SSE2__
787 // flush denormals to zero for masking to avoid performance penalty
788 // if there are a lot of zero values in the mask
789 const unsigned int oldMode = _MM_GET_FLUSH_ZERO_MODE();
790 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
791 #endif
792
793 #ifdef _OPENMP
794 #pragma omp parallel for simd default(none) \
795 dt_omp_firstprivate(in, mask, normalize, feathering, width, height, ch) \
796 schedule(simd:static) aligned(mask, in:64) reduction(+:clipped)
797 #endif
798 for(size_t k = 0; k < height * width * ch; k += ch)
799 {
800 const float pix_max = fmaxf(sqrtf(sqf(in[k]) + sqf(in[k + 1]) + sqf(in[k + 2])), 0.f);
801 const float argument = -pix_max * normalize + feathering;
802 const float weight = clamp_simd(1.0f / (1.0f + exp2f(argument)));
803 mask[k / ch] = weight;
804
805 // at x = 4, the sigmoid produces opacity = 5.882 %.
806 // any x > 4 will produce negligible changes over the image,
807 // especially since we have reduced visual sensitivity in highlights.
808 // so we discard pixels for argument > 4. for they are not worth computing.
809 clipped += (4.f > argument);
810 }
811
812 #ifdef __SSE2__
813 _MM_SET_FLUSH_ZERO_MODE(oldMode);
814 #endif
815
816 // If clipped area is < 9 pixels, recovery is not worth the computational cost, so skip it.
817 return (clipped > 9);
818 }
819
820
821 #ifdef _OPENMP
822 #pragma omp declare simd aligned(in, mask, inpainted:64) uniform(width, height, ch, noise_level, noise_distribution, threshold)
823 #endif
inpaint_noise(const float * const in,const float * const mask,float * const inpainted,const float noise_level,const float threshold,const dt_noise_distribution_t noise_distribution,const size_t width,const size_t height,const size_t ch)824 inline static void inpaint_noise(const float *const in, const float *const mask,
825 float *const inpainted, const float noise_level, const float threshold,
826 const dt_noise_distribution_t noise_distribution,
827 const size_t width, const size_t height, const size_t ch)
828 {
829 // add statistical noise in highlights to fill-in texture
830 // this creates "particules" in highlights, that will help the implicit partial derivative equation
831 // solver used in wavelets reconstruction to generate texture
832
833 #ifdef _OPENMP
834 #pragma omp parallel for simd default(none) \
835 dt_omp_firstprivate(in, mask, inpainted, width, height, ch, noise_level, noise_distribution, threshold) \
836 schedule(simd:static) aligned(mask, in, inpainted:64) collapse(2)
837 #endif
838 for(size_t i = 0; i < height; i++)
839 for(size_t j = 0; j < width; j++)
840 {
841 // Init random number generator
842 uint32_t DT_ALIGNED_ARRAY state[4] = { splitmix32(j + 1), splitmix32((j + 1) * (i + 3)), splitmix32(1337), splitmix32(666) };
843 xoshiro128plus(state);
844 xoshiro128plus(state);
845 xoshiro128plus(state);
846 xoshiro128plus(state);
847
848 // get the mask value in [0 ; 1]
849 const size_t idx = i * width + j;
850 const size_t index = idx * ch;
851 const float weight = mask[idx];
852 const float *const restrict pix_in = __builtin_assume_aligned(in + index, 16);
853 float DT_ALIGNED_ARRAY noise[3] = { 0.f };
854 float DT_ALIGNED_ARRAY sigma[3] = { 0.f };
855 const int DT_ALIGNED_ARRAY flip[3] = { TRUE, FALSE, TRUE };
856
857 for(size_t c = 0; c < 3; c++) sigma[c] = pix_in[c] * noise_level / threshold;
858
859 // create statistical noise
860 dt_noise_generator_simd(noise_distribution, pix_in, sigma, flip, state, noise);
861
862 // add noise to input
863 float *const restrict pix_out = __builtin_assume_aligned(inpainted + index, 16);
864 for(size_t c = 0; c < 3; c++) pix_out[c] = fmaxf(pix_in[c] * (1.0f - weight) + weight * noise[c], 0.f);
865 }
866 }
867
868
869 // B spline filter
870 #define FSIZE 5
871
872
873 #ifdef _OPENMP
874 #pragma omp declare simd aligned(buf, indices, result:64)
875 #endif
sparse_scalar_product(const float * const buf,const size_t indices[FSIZE],float result[4])876 inline static void sparse_scalar_product(const float *const buf, const size_t indices[FSIZE], float result[4])
877 {
878 // scalar product of 2 3×5 vectors stored as RGB planes and B-spline filter,
879 // e.g. RRRRR - GGGGG - BBBBB
880
881 const float DT_ALIGNED_ARRAY filter[FSIZE] =
882 { 1.0f / 16.0f, 4.0f / 16.0f, 6.0f / 16.0f, 4.0f / 16.0f, 1.0f / 16.0f };
883
884 #ifdef _OPENMP
885 #pragma omp simd
886 #endif
887 for(size_t c = 0; c < 4; ++c)
888 {
889 float acc = 0.0f;
890 for(size_t k = 0; k < FSIZE; ++k)
891 acc += filter[k] * buf[indices[k] + c];
892 result[c] = fmaxf(acc, 0.f);
893 }
894 }
895
896 #ifdef _OPENMP
897 #pragma omp declare simd aligned(in, out:64) aligned(tempbuf:16)
898 #endif
blur_2D_Bspline(const float * const restrict in,float * const restrict out,float * const restrict tempbuf,const size_t width,const size_t height,const int mult)899 inline static void blur_2D_Bspline(const float *const restrict in, float *const restrict out,
900 float *const restrict tempbuf,
901 const size_t width, const size_t height, const int mult)
902 {
903 // À-trous B-spline interpolation/blur shifted by mult
904 #ifdef _OPENMP
905 #pragma omp parallel for default(none) \
906 dt_omp_firstprivate(width, height, mult) \
907 dt_omp_sharedconst(out, in, tempbuf) \
908 schedule(simd:static)
909 #endif
910 for(size_t row = 0; row < height; row++)
911 {
912 // get a thread-private one-row temporary buffer
913 float *const temp = tempbuf + 4 * width * dt_get_thread_num();
914 // interleave the order in which we process the rows so that we minimize cache misses
915 const size_t i = dwt_interleave_rows(row, height, mult);
916 // Convolve B-spline filter over columns: for each pixel in the current row, compute vertical blur
917 size_t DT_ALIGNED_ARRAY indices[FSIZE] = { 0 };
918 // Start by computing the array indices of the pixels of interest; the offsets from the current pixel stay
919 // unchanged over the entire row, so we can compute once and just offset the base address while iterating
920 // over the row
921 for(size_t ii = 0; ii < FSIZE; ++ii)
922 {
923 const size_t r = CLAMP(mult * (int)(ii - (FSIZE - 1) / 2) + (int)i, (int)0, (int)height - 1);
924 indices[ii] = 4 * r * width;
925 }
926 for(size_t j = 0; j < width; j++)
927 {
928 // Compute the vertical blur of the current pixel and store it in the temp buffer for the row
929 sparse_scalar_product(in + j * 4, indices, temp + j * 4);
930 }
931 // Convolve B-spline filter horizontally over current row
932 for(size_t j = 0; j < width; j++)
933 {
934 // Compute the array indices of the pixels of interest; since the offsets will change near the ends of
935 // the row, we need to recompute for each pixel
936 for(size_t jj = 0; jj < FSIZE; ++jj)
937 {
938 const size_t col = CLAMP(mult * (int)(jj - (FSIZE - 1) / 2) + (int)j, (int)0, (int)width - 1);
939 indices[jj] = 4 * col;
940 }
941 // Compute the horizontal blur of the already vertically-blurred pixel and store the result at the proper
942 // row/column location in the output buffer
943 sparse_scalar_product(temp, indices, out + (i * width + j) * 4);
944 }
945 }
946 }
947
948
wavelets_reconstruct_RGB(const float * const restrict HF,const float * const restrict LF,const float * const restrict texture,const float * const restrict mask,float * const restrict reconstructed,const size_t width,const size_t height,const size_t ch,const float gamma,const float gamma_comp,const float beta,const float beta_comp,const float delta,const size_t s,const size_t scales)949 inline static void wavelets_reconstruct_RGB(const float *const restrict HF, const float *const restrict LF,
950 const float *const restrict texture, const float *const restrict mask,
951 float *const restrict reconstructed, const size_t width,
952 const size_t height, const size_t ch, const float gamma,
953 const float gamma_comp, const float beta, const float beta_comp,
954 const float delta, const size_t s, const size_t scales)
955 {
956 #ifdef _OPENMP
957 #pragma omp parallel for default(none) \
958 dt_omp_firstprivate(width, height, ch, HF, LF, texture, mask, reconstructed, gamma, gamma_comp, beta, \
959 beta_comp, delta, s, scales) schedule(simd : static)
960 #endif
961 for(size_t k = 0; k < height * width * ch; k += 4)
962 {
963 const float alpha = mask[k / ch];
964
965 // cache RGB wavelets scales just to be sure the compiler doesn't reload them
966 const float *const restrict HF_c = __builtin_assume_aligned(HF + k, 16);
967 const float *const restrict LF_c = __builtin_assume_aligned(LF + k, 16);
968 const float *const restrict TT_c = __builtin_assume_aligned(texture + k, 16);
969
970 // synthesize the max of all RGB channels texture as a flat texture term for the whole pixel
971 // this is useful if only 1 or 2 channels are clipped, so we transfer the valid/sharpest texture on the other
972 // channels
973 const float grey_texture = fmaxabsf(fmaxabsf(TT_c[0], TT_c[1]), TT_c[2]);
974
975 // synthesize the max of all interpolated/inpainted RGB channels as a flat details term for the whole pixel
976 // this is smoother than grey_texture and will fill holes smoothly in details layers if grey_texture ~= 0.f
977 const float grey_details = (HF_c[0] + HF_c[1] + HF_c[2]) / 3.f;
978
979 // synthesize both terms with weighting
980 // when beta_comp ~= 1.0, we force the reconstruction to be achromatic, which may help with gamut issues or
981 // magenta highlights.
982 const float grey_HF = beta_comp * (gamma_comp * grey_details + gamma * grey_texture);
983
984 // synthesize the min of all low-frequency RGB channels as a flat structure term for the whole pixel
985 // when beta_comp ~= 1.0, we force the reconstruction to be achromatic, which may help with gamut issues or magenta highlights.
986 const float grey_residual = beta_comp * (LF_c[0] + LF_c[1] + LF_c[2]) / 3.f;
987
988 #ifdef _OPENMP
989 #pragma omp simd aligned(reconstructed:64) aligned(HF_c, LF_c, TT_c:16)
990 #endif
991 for(size_t c = 0; c < 4; c++)
992 {
993 // synthesize interpolated/inpainted RGB channels color details residuals and weigh them
994 // this brings back some color on top of the grey_residual
995
996 // synthesize interpolated/inpainted RGB channels color details and weigh them
997 // this brings back some color on top of the grey_details
998 const float details = (gamma_comp * HF_c[c] + gamma * TT_c[c]) * beta + grey_HF;
999
1000 // reconstruction
1001 const float residual = (s == scales - 1) ? (grey_residual + LF_c[c] * beta) : 0.f;
1002 reconstructed[k + c] += alpha * (delta * details + residual);
1003 }
1004 }
1005 }
1006
wavelets_reconstruct_ratios(const float * const restrict HF,const float * const restrict LF,const float * const restrict texture,const float * const restrict mask,float * const restrict reconstructed,const size_t width,const size_t height,const size_t ch,const float gamma,const float gamma_comp,const float beta,const float beta_comp,const float delta,const size_t s,const size_t scales)1007 inline static void wavelets_reconstruct_ratios(const float *const restrict HF, const float *const restrict LF,
1008 const float *const restrict texture,
1009 const float *const restrict mask,
1010 float *const restrict reconstructed, const size_t width,
1011 const size_t height, const size_t ch, const float gamma,
1012 const float gamma_comp, const float beta, const float beta_comp,
1013 const float delta, const size_t s, const size_t scales)
1014 {
1015 /*
1016 * This is the adapted version of the RGB reconstruction
1017 * RGB contain high frequencies that we try to recover, so we favor them in the reconstruction.
1018 * The ratios represent the chromaticity in image and contain low frequencies in the absence of noise or
1019 * aberrations, so, here, we favor them instead.
1020 *
1021 * Consequences :
1022 * 1. use min of interpolated channels details instead of max, to get smoother details
1023 * 4. use the max of low frequency channels instead of min, to favor achromatic solution.
1024 *
1025 * Note : ratios close to 1 mean higher spectral purity (more white). Ratios close to 0 mean lower spectral purity
1026 * (more colorful)
1027 */
1028 #ifdef _OPENMP
1029 #pragma omp parallel for default(none) \
1030 dt_omp_firstprivate(width, height, ch, HF, LF, texture, mask, reconstructed, gamma, gamma_comp, beta, \
1031 beta_comp, delta, s, scales) schedule(simd \
1032 : static)
1033 #endif
1034 for(size_t k = 0; k < height * width * ch; k += 4)
1035 {
1036 const float alpha = mask[k / ch];
1037
1038 // cache RGB wavelets scales just to be sure the compiler doesn't reload them
1039 const float *const restrict HF_c = __builtin_assume_aligned(HF + k, 16);
1040 const float *const restrict LF_c = __builtin_assume_aligned(LF + k, 16);
1041 const float *const restrict TT_c = __builtin_assume_aligned(texture + k, 16);
1042
1043 // synthesize the max of all RGB channels texture as a flat texture term for the whole pixel
1044 // this is useful if only 1 or 2 channels are clipped, so we transfer the valid/sharpest texture on the other
1045 // channels
1046 const float grey_texture = fmaxabsf(fmaxabsf(TT_c[0], TT_c[1]), TT_c[2]);
1047
1048 // synthesize the max of all interpolated/inpainted RGB channels as a flat details term for the whole pixel
1049 // this is smoother than grey_texture and will fill holes smoothly in details layers if grey_texture ~= 0.f
1050 const float grey_details = (HF_c[0] + HF_c[1] + HF_c[2]) / 3.f;
1051
1052 // synthesize both terms with weighting
1053 // when beta_comp ~= 1.0, we force the reconstruction to be achromatic, which may help with gamut issues or
1054 // magenta highlights.
1055 const float grey_HF = (gamma_comp * grey_details + gamma * grey_texture);
1056
1057 #ifdef _OPENMP
1058 #pragma omp simd aligned(reconstructed:64) aligned(HF_c, TT_c, LF_c:16) linear(k:4)
1059 #endif
1060 for(size_t c = 0; c < 4; c++)
1061 {
1062 // synthesize interpolated/inpainted RGB channels color details residuals and weigh them
1063 // this brings back some color on top of the grey_residual
1064 const float details = 0.5f * ((gamma_comp * HF_c[c] + gamma * TT_c[c]) + grey_HF);
1065
1066 // reconstruction
1067 const float residual = (s == scales - 1) ? LF_c[c] : 0.f;
1068 reconstructed[k + c] += alpha * (delta * details + residual);
1069 }
1070 }
1071 }
1072
1073
init_reconstruct(const float * const restrict in,const float * const restrict mask,float * const restrict reconstructed,const size_t width,const size_t height,const size_t ch)1074 static inline void init_reconstruct(const float *const restrict in, const float *const restrict mask,
1075 float *const restrict reconstructed, const size_t width, const size_t height,
1076 const size_t ch)
1077 {
1078 // init the reconstructed buffer with non-clipped and partially clipped pixels
1079 // Note : it's a simple multiplied alpha blending where mask = alpha weight
1080 #ifdef _OPENMP
1081 #pragma omp parallel for simd default(none) dt_omp_firstprivate(in, mask, reconstructed, width, height, ch) \
1082 schedule(simd \
1083 : static) aligned(in, mask, reconstructed : 64)
1084 #endif
1085 for(size_t k = 0; k < height * width * ch; k++)
1086 {
1087 reconstructed[k] = fmaxf(in[k] * (1.f - mask[k / ch]), 0.f);
1088 }
1089 }
1090
1091
wavelets_detail_level(const float * const restrict detail,const float * const restrict LF,float * const restrict HF,float * const restrict texture,const size_t width,const size_t height,const size_t ch)1092 static inline void wavelets_detail_level(const float *const restrict detail, const float *const restrict LF,
1093 float *const restrict HF, float *const restrict texture,
1094 const size_t width, const size_t height, const size_t ch)
1095 {
1096 #ifdef _OPENMP
1097 #pragma omp parallel for simd default(none) dt_omp_firstprivate(width, height, HF, LF, detail, texture) \
1098 schedule(simd \
1099 : static) aligned(HF, LF, detail, texture : 64)
1100 #endif
1101 for(size_t k = 0; k < height * width; k++)
1102 {
1103 for(size_t c = 0; c < 4; ++c) HF[4*k + c] = texture[4*k + c] = detail[4*k + c] - LF[4*k + c];
1104 }
1105 }
1106
get_scales(const dt_iop_roi_t * roi_in,const dt_dev_pixelpipe_iop_t * const piece)1107 static int get_scales(const dt_iop_roi_t *roi_in, const dt_dev_pixelpipe_iop_t *const piece)
1108 {
1109 /* How many wavelets scales do we need to compute at current zoom level ?
1110 * 0. To get the same preview no matter the zoom scale, the relative image coverage ratio of the filter at
1111 * the coarsest wavelet level should always stay constant.
1112 * 1. The image coverage of each B spline filter of size `FSIZE` is `2^(level) * (FSIZE - 1) / 2 + 1` pixels
1113 * 2. The coarsest level filter at full resolution should cover `1/FSIZE` of the largest image dimension.
1114 * 3. The coarsest level filter at current zoom level should cover `scale/FSIZE` of the largest image dimension.
1115 *
1116 * So we compute the level that solves 1. subject to 3. Of course, integer rounding doesn't make that 1:1
1117 * accurate.
1118 */
1119 const float scale = roi_in->scale / piece->iscale;
1120 const size_t size = MAX(piece->buf_in.height * piece->iscale, piece->buf_in.width * piece->iscale);
1121 const int scales = floorf(log2f((2.0f * size * scale / ((FSIZE - 1) * FSIZE)) - 1.0f));
1122 return CLAMP(scales, 1, MAX_NUM_SCALES);
1123 }
1124
1125
reconstruct_highlights(const float * const restrict in,const float * const restrict mask,float * const restrict reconstructed,const dt_iop_filmicrgb_reconstruction_type_t variant,const size_t ch,const dt_iop_filmicrgb_data_t * const data,dt_dev_pixelpipe_iop_t * piece,const dt_iop_roi_t * const roi_in,const dt_iop_roi_t * const roi_out)1126 static inline gint reconstruct_highlights(const float *const restrict in, const float *const restrict mask,
1127 float *const restrict reconstructed,
1128 const dt_iop_filmicrgb_reconstruction_type_t variant, const size_t ch,
1129 const dt_iop_filmicrgb_data_t *const data, dt_dev_pixelpipe_iop_t *piece,
1130 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
1131 {
1132 gint success = TRUE;
1133
1134 // wavelets scales
1135 const int scales = get_scales(roi_in, piece);
1136
1137 // wavelets scales buffers
1138 float *const restrict LF_even = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height); // low-frequencies RGB
1139 float *const restrict LF_odd = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height); // low-frequencies RGB
1140 float *const restrict HF_RGB = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height); // high-frequencies RGB
1141 float *const restrict HF_grey = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height); // high-frequencies RGB backup
1142
1143 // alloc a permanent reusable buffer for intermediate computations - avoid multiple alloc/free
1144 float *const restrict temp = dt_alloc_sse_ps(dt_get_num_threads() * ch * roi_out->width);
1145
1146 if(!LF_even || !LF_odd || !HF_RGB || !HF_grey || !temp)
1147 {
1148 dt_control_log(_("filmic highlights reconstruction failed to allocate memory, check your RAM settings"));
1149 success = FALSE;
1150 goto error;
1151 }
1152
1153 // Init reconstructed with valid parts of image
1154 init_reconstruct(in, mask, reconstructed, roi_out->width, roi_out->height, ch);
1155
1156 // structure inpainting vs. texture duplicating weight
1157 const float gamma = (data->reconstruct_structure_vs_texture);
1158 const float gamma_comp = 1.0f - data->reconstruct_structure_vs_texture;
1159
1160 // colorful vs. grey weight
1161 const float beta = data->reconstruct_grey_vs_color;
1162 const float beta_comp = 1.f - data->reconstruct_grey_vs_color;
1163
1164 // bloom vs reconstruct weight
1165 const float delta = data->reconstruct_bloom_vs_details;
1166
1167 // À trous wavelet decompose
1168 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
1169 // the wavelets decomposition here is the same as the equalizer/atrous module,
1170 // but simplified because we don't need the edge-aware term, so we can separate the convolution kernel
1171 // with a vertical and horizontal blur, which is 10 multiply-add instead of 25 by pixel.
1172 for(int s = 0; s < scales; ++s)
1173 {
1174 const float *restrict detail; // buffer containing this scale's input
1175 float *restrict LF; // output buffer for the current scale
1176 float *restrict HF_RGB_temp; // temp buffer for HF_RBG terms before blurring
1177
1178 // swap buffers so we only need 2 LF buffers : the LF at scale (s-1) and the one at current scale (s)
1179 if(s == 0)
1180 {
1181 detail = in;
1182 LF = LF_odd;
1183 HF_RGB_temp = LF_even;
1184 }
1185 else if(s % 2 != 0)
1186 {
1187 detail = LF_odd;
1188 LF = LF_even;
1189 HF_RGB_temp = LF_odd;
1190 }
1191 else
1192 {
1193 detail = LF_even;
1194 LF = LF_odd;
1195 HF_RGB_temp = LF_even;
1196 }
1197
1198 const int mult = 1 << s; // fancy-pants C notation for 2^s with integer type, don't be afraid
1199
1200 // Compute wavelets low-frequency scales
1201 blur_2D_Bspline(detail, LF, temp, roi_out->width, roi_out->height, mult);
1202
1203 // Compute wavelets high-frequency scales and save the minimum of texture over the RGB channels
1204 // Note : HF_RGB = detail - LF, HF_grey = max(HF_RGB)
1205 wavelets_detail_level(detail, LF, HF_RGB_temp, HF_grey, roi_out->width, roi_out->height, ch);
1206
1207 // interpolate/blur/inpaint (same thing) the RGB high-frequency to fill holes
1208 blur_2D_Bspline(HF_RGB_temp, HF_RGB, temp, roi_out->width, roi_out->height, 1);
1209
1210 // Reconstruct clipped parts
1211 if(variant == DT_FILMIC_RECONSTRUCT_RGB)
1212 wavelets_reconstruct_RGB(HF_RGB, LF, HF_grey, mask, reconstructed, roi_out->width, roi_out->height, ch,
1213 gamma, gamma_comp, beta, beta_comp, delta, s, scales);
1214 else if(variant == DT_FILMIC_RECONSTRUCT_RATIOS)
1215 wavelets_reconstruct_ratios(HF_RGB, LF, HF_grey, mask, reconstructed, roi_out->width, roi_out->height, ch,
1216 gamma, gamma_comp, beta, beta_comp, delta, s, scales);
1217 }
1218
1219 error:
1220 if(temp) dt_free_align(temp);
1221 if(LF_even) dt_free_align(LF_even);
1222 if(LF_odd) dt_free_align(LF_odd);
1223 if(HF_RGB) dt_free_align(HF_RGB);
1224 if(HF_grey) dt_free_align(HF_grey);
1225 return success;
1226 }
1227
1228
filmic_split_v1(const float * const restrict in,float * const restrict out,const dt_iop_order_iccprofile_info_t * const work_profile,const dt_iop_filmicrgb_data_t * const data,const dt_iop_filmic_rgb_spline_t spline,const size_t width,const size_t height,const size_t ch)1229 static inline void filmic_split_v1(const float *const restrict in, float *const restrict out,
1230 const dt_iop_order_iccprofile_info_t *const work_profile,
1231 const dt_iop_filmicrgb_data_t *const data,
1232 const dt_iop_filmic_rgb_spline_t spline, const size_t width,
1233 const size_t height, const size_t ch)
1234 {
1235 #ifdef _OPENMP
1236 #pragma omp parallel for simd default(none) dt_omp_firstprivate(width, height, ch, data, in, out, work_profile, \
1237 spline) schedule(simd \
1238 : static) aligned(in, out : 64)
1239 #endif
1240 for(size_t k = 0; k < height * width * ch; k += ch)
1241 {
1242 const float *const restrict pix_in = in + k;
1243 float *const restrict pix_out = out + k;
1244 float DT_ALIGNED_ARRAY temp[4];
1245
1246 // Log tone-mapping
1247 for(int c = 0; c < 3; c++)
1248 temp[c] = log_tonemapping_v1(fmaxf(pix_in[c], NORM_MIN), data->grey_source, data->black_source,
1249 data->dynamic_range);
1250
1251 // Get the desaturation coeff based on the log value
1252 const float lum = (work_profile)
1253 ? dt_ioppr_get_rgb_matrix_luminance(temp, work_profile->matrix_in, work_profile->lut_in,
1254 work_profile->unbounded_coeffs_in,
1255 work_profile->lutsize, work_profile->nonlinearlut)
1256 : dt_camera_rgb_luminance(temp);
1257 const float desaturation = filmic_desaturate_v1(lum, data->sigma_toe, data->sigma_shoulder, data->saturation);
1258
1259 // Desaturate on the non-linear parts of the curve
1260 // Filmic S curve on the max RGB
1261 // Apply the transfer function of the display
1262 for(int c = 0; c < 3; c++)
1263 pix_out[c] = powf(
1264 clamp_simd(filmic_spline(linear_saturation(temp[c], lum, desaturation), spline.M1, spline.M2, spline.M3,
1265 spline.M4, spline.M5, spline.latitude_min, spline.latitude_max, spline.type)),
1266 data->output_power);
1267 }
1268 }
1269
1270
filmic_split_v2_v3(const float * const restrict in,float * const restrict out,const dt_iop_order_iccprofile_info_t * const work_profile,const dt_iop_filmicrgb_data_t * const data,const dt_iop_filmic_rgb_spline_t spline,const size_t width,const size_t height,const size_t ch)1271 static inline void filmic_split_v2_v3(const float *const restrict in, float *const restrict out,
1272 const dt_iop_order_iccprofile_info_t *const work_profile,
1273 const dt_iop_filmicrgb_data_t *const data,
1274 const dt_iop_filmic_rgb_spline_t spline, const size_t width,
1275 const size_t height, const size_t ch)
1276 {
1277 #ifdef _OPENMP
1278 #pragma omp parallel for simd default(none) dt_omp_firstprivate(width, height, ch, data, in, out, work_profile, \
1279 spline) schedule(simd \
1280 : static) aligned(in, out : 64)
1281 #endif
1282 for(size_t k = 0; k < height * width * ch; k += ch)
1283 {
1284 const float *const restrict pix_in = in + k;
1285 float *const restrict pix_out = out + k;
1286 float DT_ALIGNED_ARRAY temp[4];
1287
1288 // Log tone-mapping
1289 for(int c = 0; c < 3; c++)
1290 temp[c] = log_tonemapping_v2(fmaxf(pix_in[c], NORM_MIN), data->grey_source, data->black_source,
1291 data->dynamic_range);
1292
1293 // Get the desaturation coeff based on the log value
1294 const float lum = (work_profile)
1295 ? dt_ioppr_get_rgb_matrix_luminance(temp, work_profile->matrix_in, work_profile->lut_in,
1296 work_profile->unbounded_coeffs_in,
1297 work_profile->lutsize, work_profile->nonlinearlut)
1298 : dt_camera_rgb_luminance(temp);
1299 const float desaturation = filmic_desaturate_v2(lum, data->sigma_toe, data->sigma_shoulder, data->saturation);
1300
1301 // Desaturate on the non-linear parts of the curve
1302 // Filmic S curve on the max RGB
1303 // Apply the transfer function of the display
1304 for(int c = 0; c < 3; c++)
1305 pix_out[c] = powf(
1306 clamp_simd(filmic_spline(linear_saturation(temp[c], lum, desaturation), spline.M1, spline.M2, spline.M3,
1307 spline.M4, spline.M5, spline.latitude_min, spline.latitude_max, spline.type)),
1308 data->output_power);
1309 }
1310 }
1311
1312
filmic_chroma_v1(const float * const restrict in,float * const restrict out,const dt_iop_order_iccprofile_info_t * const work_profile,const dt_iop_filmicrgb_data_t * const data,const dt_iop_filmic_rgb_spline_t spline,const int variant,const size_t width,const size_t height,const size_t ch)1313 static inline void filmic_chroma_v1(const float *const restrict in, float *const restrict out,
1314 const dt_iop_order_iccprofile_info_t *const work_profile,
1315 const dt_iop_filmicrgb_data_t *const data,
1316 const dt_iop_filmic_rgb_spline_t spline, const int variant, const size_t width,
1317 const size_t height, const size_t ch)
1318 {
1319 #ifdef _OPENMP
1320 #pragma omp parallel for simd default(none) \
1321 dt_omp_firstprivate(width, height, ch, data, in, out, work_profile, variant, spline) schedule(simd \
1322 : static) \
1323 aligned(in, out : 64)
1324 #endif
1325 for(size_t k = 0; k < height * width * ch; k += ch)
1326 {
1327 const float *const restrict pix_in = in + k;
1328 float *const restrict pix_out = out + k;
1329
1330 float DT_ALIGNED_ARRAY ratios[4] = { 0.0f };
1331 float norm = fmaxf(get_pixel_norm(pix_in, variant, work_profile), NORM_MIN);
1332
1333 // Save the ratios
1334 for(int c = 0; c < 3; c++) ratios[c] = pix_in[c] / norm;
1335
1336 // Sanitize the ratios
1337 const float min_ratios = fminf(fminf(ratios[0], ratios[1]), ratios[2]);
1338 if(min_ratios < 0.0f)
1339 for(int c = 0; c < 3; c++) ratios[c] -= min_ratios;
1340
1341 // Log tone-mapping
1342 norm = log_tonemapping_v1(norm, data->grey_source, data->black_source, data->dynamic_range);
1343
1344 // Get the desaturation value based on the log value
1345 const float desaturation = filmic_desaturate_v1(norm, data->sigma_toe, data->sigma_shoulder, data->saturation);
1346
1347 for(int c = 0; c < 3; c++) ratios[c] *= norm;
1348
1349 const float lum = (work_profile) ? dt_ioppr_get_rgb_matrix_luminance(
1350 ratios, work_profile->matrix_in, work_profile->lut_in, work_profile->unbounded_coeffs_in,
1351 work_profile->lutsize, work_profile->nonlinearlut)
1352 : dt_camera_rgb_luminance(ratios);
1353
1354 // Desaturate on the non-linear parts of the curve and save ratios
1355 for(int c = 0; c < 3; c++) ratios[c] = linear_saturation(ratios[c], lum, desaturation) / norm;
1356
1357 // Filmic S curve on the max RGB
1358 // Apply the transfer function of the display
1359 norm = powf(clamp_simd(filmic_spline(norm, spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1360 spline.latitude_min, spline.latitude_max, spline.type)),
1361 data->output_power);
1362
1363 // Re-apply ratios
1364 for(int c = 0; c < 3; c++) pix_out[c] = ratios[c] * norm;
1365 }
1366 }
1367
1368
filmic_chroma_v2_v3(const float * const restrict in,float * const restrict out,const dt_iop_order_iccprofile_info_t * const work_profile,const dt_iop_filmicrgb_data_t * const data,const dt_iop_filmic_rgb_spline_t spline,const int variant,const size_t width,const size_t height,const size_t ch,const dt_iop_filmicrgb_colorscience_type_t colorscience_version)1369 static inline void filmic_chroma_v2_v3(const float *const restrict in, float *const restrict out,
1370 const dt_iop_order_iccprofile_info_t *const work_profile,
1371 const dt_iop_filmicrgb_data_t *const data,
1372 const dt_iop_filmic_rgb_spline_t spline, const int variant,
1373 const size_t width, const size_t height, const size_t ch,
1374 const dt_iop_filmicrgb_colorscience_type_t colorscience_version)
1375 {
1376
1377 #ifdef _OPENMP
1378 #pragma omp parallel for simd default(none) \
1379 dt_omp_firstprivate(width, height, ch, data, in, out, work_profile, variant, spline, colorscience_version) \
1380 schedule(simd \
1381 : static) aligned(in, out : 64)
1382 #endif
1383 for(size_t k = 0; k < height * width * ch; k += ch)
1384 {
1385 const float *const restrict pix_in = in + k;
1386 float *const restrict pix_out = out + k;
1387
1388 float norm = fmaxf(get_pixel_norm(pix_in, variant, work_profile), NORM_MIN);
1389
1390 // Save the ratios
1391 float DT_ALIGNED_ARRAY ratios[4] = { 0.0f };
1392
1393 for(int c = 0; c < 3; c++) ratios[c] = pix_in[c] / norm;
1394
1395 // Sanitize the ratios
1396 const float min_ratios = fminf(fminf(ratios[0], ratios[1]), ratios[2]);
1397 const int sanitize = (min_ratios < 0.0f);
1398
1399 if(sanitize)
1400 for(int c = 0; c < 3; c++)
1401 ratios[c] -= min_ratios;
1402
1403 // Log tone-mapping
1404 norm = log_tonemapping_v2(norm, data->grey_source, data->black_source, data->dynamic_range);
1405
1406 // Get the desaturation value based on the log value
1407 const float desaturation = filmic_desaturate_v2(norm, data->sigma_toe, data->sigma_shoulder, data->saturation);
1408
1409 // Filmic S curve on the max RGB
1410 // Apply the transfer function of the display
1411 norm = powf(clamp_simd(filmic_spline(norm, spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1412 spline.latitude_min, spline.latitude_max, spline.type)),
1413 data->output_power);
1414
1415 // Re-apply ratios with saturation change
1416 for(int c = 0; c < 3; c++) ratios[c] = fmaxf(ratios[c] + (1.0f - ratios[c]) * (1.0f - desaturation), 0.0f);
1417
1418 // color science v3: normalize again after desaturation - the norm might have changed by the desaturation
1419 // operation.
1420 if(colorscience_version == DT_FILMIC_COLORSCIENCE_V3)
1421 norm /= fmaxf(get_pixel_norm(ratios, variant, work_profile), NORM_MIN);
1422
1423 for(int c = 0; c < 3; c++) pix_out[c] = ratios[c] * norm;
1424
1425 // Gamut mapping
1426 const float max_pix = fmaxf(fmaxf(pix_out[0], pix_out[1]), pix_out[2]);
1427 const int penalize = (max_pix > 1.0f);
1428
1429 // Penalize the ratios by the amount of clipping
1430 if(penalize)
1431 {
1432 for(int c = 0; c < 3; c++)
1433 {
1434 ratios[c] = fmaxf(ratios[c] + (1.0f - max_pix), 0.0f);
1435 pix_out[c] = clamp_simd(ratios[c] * norm);
1436 }
1437 }
1438 }
1439 }
1440
1441
display_mask(const float * const restrict mask,float * const restrict out,const size_t width,const size_t height,const size_t ch)1442 static inline void display_mask(const float *const restrict mask, float *const restrict out, const size_t width,
1443 const size_t height, const size_t ch)
1444 {
1445 #ifdef _OPENMP
1446 #pragma omp parallel for simd default(none) dt_omp_firstprivate(width, height, ch, out, mask) schedule(simd \
1447 : static) \
1448 aligned(mask, out : 64)
1449 #endif
1450 for(size_t k = 0; k < height * width * ch; k++) out[k] = mask[k / ch];
1451 }
1452
1453
compute_ratios(const float * const restrict in,float * const restrict norms,float * const restrict ratios,const dt_iop_order_iccprofile_info_t * const work_profile,const int variant,const size_t width,const size_t height,const size_t ch)1454 static inline void compute_ratios(const float *const restrict in, float *const restrict norms,
1455 float *const restrict ratios,
1456 const dt_iop_order_iccprofile_info_t *const work_profile, const int variant,
1457 const size_t width, const size_t height, const size_t ch)
1458 {
1459 #ifdef _OPENMP
1460 #pragma omp parallel for simd default(none) \
1461 dt_omp_firstprivate(ch, width, height, norms, ratios, in, work_profile, variant) schedule(simd \
1462 : static) \
1463 aligned(norms, ratios, in : 64)
1464 #endif
1465 for(size_t k = 0; k < height * width * ch; k += ch)
1466 {
1467 const float norm = fmaxf(get_pixel_norm(in + k, variant, work_profile), NORM_MIN);
1468 norms[k / ch] = norm;
1469 for(size_t c = 0; c < 3; c++) ratios[k + c] = in[k + c] / norm;
1470 }
1471 }
1472
1473
restore_ratios(float * const restrict ratios,const float * const restrict norms,const size_t width,const size_t height,const size_t ch)1474 static inline void restore_ratios(float *const restrict ratios, const float *const restrict norms,
1475 const size_t width, const size_t height, const size_t ch)
1476 {
1477 #ifdef _OPENMP
1478 #pragma omp parallel for simd default(none) \
1479 dt_omp_firstprivate(width, height, ch, norms, ratios) \
1480 schedule(simd:static) aligned(norms, ratios:64) collapse(2)
1481 #endif
1482 for(size_t k = 0; k < height * width * ch; k += ch)
1483 for(size_t c = 0; c < 3; c++) ratios[k + c] = clamp_simd(ratios[k + c]) * norms[k / ch];
1484 }
1485
1486
process(dt_iop_module_t * self,dt_dev_pixelpipe_iop_t * piece,const void * const restrict ivoid,void * const restrict ovoid,const dt_iop_roi_t * const roi_in,const dt_iop_roi_t * const roi_out)1487 void process(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid,
1488 void *const restrict ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
1489 {
1490 const dt_iop_filmicrgb_data_t *const data = (dt_iop_filmicrgb_data_t *)piece->data;
1491 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(piece->pipe);
1492
1493 if(piece->colors != 4)
1494 {
1495 dt_control_log(_("filmic works only on RGB input"));
1496 return;
1497 }
1498
1499 const size_t ch = 4;
1500
1501 /** The log2(x) -> -INF when x -> 0
1502 * thus very low values (noise) will get even lower, resulting in noise negative amplification,
1503 * which leads to pepper noise in shadows. To avoid that, we need to clip values that are noise for sure.
1504 * Using 16 bits RAW data, the black value (known by rawspeed for every manufacturer) could be used as a
1505 * threshold. However, at this point of the pixelpipe, the RAW levels have already been corrected and everything
1506 * can happen with black levels in the exposure module. So we define the threshold as the first non-null 16 bit
1507 * integer
1508 */
1509
1510 float *restrict in = (float *)ivoid;
1511 float *const restrict out = (float *)ovoid;
1512 float *const restrict mask = dt_alloc_sse_ps((size_t)roi_out->width * roi_out->height);
1513
1514 // used to adjuste noise level depending on size. Don't amplify noise if magnified > 100%
1515 const float scale = fmaxf(piece->iscale / roi_in->scale, 1.f);
1516
1517 // build a mask of clipped pixels
1518 const int recover_highlights = mask_clipped_pixels(in, mask, data->normalize, data->reconstruct_feather, roi_out->width, roi_out->height, 4);
1519
1520 // display mask and exit
1521 if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL && mask)
1522 {
1523 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
1524
1525 if(g->show_mask)
1526 {
1527 display_mask(mask, out, roi_out->width, roi_out->height, ch);
1528 dt_free_align(mask);
1529 return;
1530 }
1531 }
1532
1533 float *const restrict reconstructed = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height);
1534 const gboolean run_fast = (piece->pipe->type & DT_DEV_PIXELPIPE_FAST) == DT_DEV_PIXELPIPE_FAST;
1535
1536 // if fast mode is not in use
1537 if(!run_fast && recover_highlights && mask && reconstructed)
1538 {
1539 // init the blown areas with noise to create particles
1540 float *const restrict inpainted = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height);
1541 inpaint_noise(in, mask, inpainted, data->noise_level / scale, data->reconstruct_threshold, data->noise_distribution,
1542 roi_out->width, roi_out->height, ch);
1543
1544 // diffuse particles with wavelets reconstruction
1545 // PASS 1 on RGB channels
1546 const gint success_1 = reconstruct_highlights(inpainted, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RGB, ch, data, piece, roi_in, roi_out);
1547 gint success_2 = TRUE;
1548
1549 dt_free_align(inpainted);
1550
1551 if(data->high_quality_reconstruction > 0 && success_1)
1552 {
1553 float *const restrict norms = dt_alloc_sse_ps((size_t)roi_out->width * roi_out->height);
1554 float *const restrict ratios = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height);
1555
1556 // reconstruct highlights PASS 2 on ratios
1557 if(norms && ratios)
1558 {
1559 for(int i = 0; i < data->high_quality_reconstruction; i++)
1560 {
1561 compute_ratios(reconstructed, norms, ratios, work_profile, DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1,
1562 roi_out->width, roi_out->height, ch);
1563 success_2 = success_2
1564 && reconstruct_highlights(ratios, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RATIOS, ch,
1565 data, piece, roi_in, roi_out);
1566 restore_ratios(reconstructed, norms, roi_out->width, roi_out->height, ch);
1567 }
1568 }
1569
1570 if(norms) dt_free_align(norms);
1571 if(ratios) dt_free_align(ratios);
1572 }
1573
1574 if(success_1 && success_2) in = reconstructed; // use reconstructed buffer as tonemapping input
1575 }
1576
1577 if(mask) dt_free_align(mask);
1578
1579 if(data->preserve_color == DT_FILMIC_METHOD_NONE)
1580 {
1581 // no chroma preservation
1582 if(data->version == DT_FILMIC_COLORSCIENCE_V1)
1583 filmic_split_v1(in, out, work_profile, data, data->spline, roi_out->width, roi_in->height, ch);
1584 else if(data->version == DT_FILMIC_COLORSCIENCE_V2 || data->version == DT_FILMIC_COLORSCIENCE_V3)
1585 filmic_split_v2_v3(in, out, work_profile, data, data->spline, roi_out->width, roi_in->height, ch);
1586 }
1587 else
1588 {
1589 // chroma preservation
1590 if(data->version == DT_FILMIC_COLORSCIENCE_V1)
1591 filmic_chroma_v1(in, out, work_profile, data, data->spline, data->preserve_color, roi_out->width,
1592 roi_out->height, ch);
1593 else if(data->version == DT_FILMIC_COLORSCIENCE_V2 || data->version == DT_FILMIC_COLORSCIENCE_V3)
1594 filmic_chroma_v2_v3(in, out, work_profile, data, data->spline, data->preserve_color, roi_out->width,
1595 roi_out->height, ch, data->version);
1596 }
1597
1598 if(reconstructed) dt_free_align(reconstructed);
1599
1600 if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK)
1601 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
1602 }
1603
1604 #ifdef HAVE_OPENCL
reconstruct_highlights_cl(cl_mem in,cl_mem mask,cl_mem reconstructed,const dt_iop_filmicrgb_reconstruction_type_t variant,dt_iop_filmicrgb_global_data_t * const gd,const dt_iop_filmicrgb_data_t * const data,dt_dev_pixelpipe_iop_t * piece,const dt_iop_roi_t * const roi_in)1605 static inline cl_int reconstruct_highlights_cl(cl_mem in, cl_mem mask, cl_mem reconstructed,
1606 const dt_iop_filmicrgb_reconstruction_type_t variant, dt_iop_filmicrgb_global_data_t *const gd,
1607 const dt_iop_filmicrgb_data_t *const data, dt_dev_pixelpipe_iop_t *piece,
1608 const dt_iop_roi_t *const roi_in)
1609 {
1610 cl_int err = -999;
1611 const int devid = piece->pipe->devid;
1612 const int width = roi_in->width;
1613 const int height = roi_in->height;
1614 size_t sizes[] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
1615
1616 // wavelets scales
1617 const int scales = get_scales(roi_in, piece);
1618
1619 // wavelets scales buffers
1620 cl_mem LF_even = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // low-frequencies RGB
1621 cl_mem LF_odd = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // low-frequencies RGB
1622 cl_mem HF_RGB = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // high-frequencies RGB
1623 cl_mem HF_grey = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // high-frequencies RGB backup
1624
1625 // alloc a permanent reusable buffer for intermediate computations - avoid multiple alloc/free
1626 cl_mem temp = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);;
1627
1628 if(!LF_even || !LF_odd || !HF_RGB || !HF_grey || !temp)
1629 {
1630 dt_control_log(_("filmic highlights reconstruction failed to allocate memory on GPU"));
1631 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
1632 goto error;
1633 }
1634
1635 // Init reconstructed with valid parts of image
1636 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 0, sizeof(cl_mem), (void *)&in);
1637 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 1, sizeof(cl_mem), (void *)&mask);
1638 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 2, sizeof(cl_mem), (void *)&reconstructed);
1639 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 3, sizeof(int), (void *)&width);
1640 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 4, sizeof(int), (void *)&height);
1641 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_init_reconstruct, sizes);
1642 if(err != CL_SUCCESS) goto error;
1643
1644 // structure inpainting vs. texture duplicating weight
1645 const float gamma = (data->reconstruct_structure_vs_texture);
1646 const float gamma_comp = 1.0f - data->reconstruct_structure_vs_texture;
1647
1648 // colorful vs. grey weight
1649 const float beta = data->reconstruct_grey_vs_color;
1650 const float beta_comp = 1.f - data->reconstruct_grey_vs_color;
1651
1652 // bloom vs reconstruct weight
1653 const float delta = data->reconstruct_bloom_vs_details;
1654
1655 // À trous wavelet decompose
1656 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
1657 // the wavelets decomposition here is the same as the equalizer/atrous module,
1658 // but simplified because we don't need the edge-aware term, so we can separate the convolution kernel
1659 // with a vertical and horizontal blur, which is 10 multiply-add instead of 25 by pixel.
1660 for(int s = 0; s < scales; ++s)
1661 {
1662 cl_mem detail;
1663 cl_mem LF;
1664
1665 // swap buffers so we only need 2 LF buffers : the LF at scale (s-1) and the one at current scale (s)
1666 if(s == 0)
1667 {
1668 detail = in;
1669 LF = LF_odd;
1670 }
1671 else if(s % 2 != 0)
1672 {
1673 detail = LF_odd;
1674 LF = LF_even;
1675 }
1676 else
1677 {
1678 detail = LF_even;
1679 LF = LF_odd;
1680 }
1681
1682 const int mult = 1 << s; // fancy-pants C notation for 2^s with integer type, don't be afraid
1683
1684 // Compute wavelets low-frequency scales
1685 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 0, sizeof(cl_mem), (void *)&detail);
1686 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 1, sizeof(cl_mem), (void *)&temp);
1687 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 2, sizeof(int), (void *)&width);
1688 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 3, sizeof(int), (void *)&height);
1689 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 4, sizeof(int), (void *)&mult);
1690 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_bspline_horizontal, sizes);
1691 if(err != CL_SUCCESS) goto error;
1692
1693 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 0, sizeof(cl_mem), (void *)&temp);
1694 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 1, sizeof(cl_mem), (void *)&LF);
1695 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 2, sizeof(int), (void *)&width);
1696 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 3, sizeof(int), (void *)&height);
1697 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 4, sizeof(int), (void *)&mult);
1698 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_bspline_vertical, sizes);
1699 if(err != CL_SUCCESS) goto error;
1700
1701 // Compute wavelets high-frequency scales and backup the maximum of texture over the RGB channels
1702 // Note : HF_RGB = detail - LF, HF_grey = HF_RGB
1703 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 0, sizeof(cl_mem), (void *)&detail);
1704 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 1, sizeof(cl_mem), (void *)&LF);
1705 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 2, sizeof(cl_mem), (void *)&HF_RGB);
1706 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 3, sizeof(cl_mem), (void *)&HF_grey);
1707 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 4, sizeof(int), (void *)&width);
1708 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 5, sizeof(int), (void *)&height);
1709 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 6, sizeof(int), (void *)&variant);
1710 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_wavelets_detail, sizes);
1711 if(err != CL_SUCCESS) goto error;
1712
1713 // interpolate/blur/inpaint (same thing) the RGB high-frequency to fill holes
1714 const int blur_size = 1;
1715 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 0, sizeof(cl_mem), (void *)&HF_RGB);
1716 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 1, sizeof(cl_mem), (void *)&temp);
1717 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 2, sizeof(int), (void *)&width);
1718 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 3, sizeof(int), (void *)&height);
1719 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 4, sizeof(int), (void *)&blur_size);
1720 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_bspline_vertical, sizes);
1721 if(err != CL_SUCCESS) goto error;
1722
1723 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 0, sizeof(cl_mem), (void *)&temp);
1724 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 1, sizeof(cl_mem), (void *)&HF_RGB);
1725 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 2, sizeof(int), (void *)&width);
1726 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 3, sizeof(int), (void *)&height);
1727 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 4, sizeof(int), (void *)&blur_size);
1728 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_bspline_horizontal, sizes);
1729 if(err != CL_SUCCESS) goto error;
1730
1731 // Reconstruct clipped parts
1732 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 0, sizeof(cl_mem), (void *)&HF_RGB);
1733 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 1, sizeof(cl_mem), (void *)&LF);
1734 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 2, sizeof(cl_mem), (void *)&HF_grey);
1735 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 3, sizeof(cl_mem), (void *)&mask);
1736 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 4, sizeof(cl_mem), (void *)&reconstructed);
1737 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 5, sizeof(cl_mem), (void *)&reconstructed);
1738 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 6, sizeof(int), (void *)&width);
1739 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 7, sizeof(int), (void *)&height);
1740 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 8, sizeof(float), (void *)&gamma);
1741 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 9, sizeof(float), (void *)&gamma_comp);
1742 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 10, sizeof(float), (void *)&beta);
1743 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 11, sizeof(float), (void *)&beta_comp);
1744 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 12, sizeof(float), (void *)&delta);
1745 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 13, sizeof(int), (void *)&s);
1746 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 14, sizeof(int), (void *)&scales);
1747 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 15, sizeof(int), (void *)&variant);
1748 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_wavelets_reconstruct, sizes);
1749 if(err != CL_SUCCESS) goto error;
1750 }
1751
1752 error:
1753 dt_opencl_release_mem_object(temp);
1754 dt_opencl_release_mem_object(LF_even);
1755 dt_opencl_release_mem_object(LF_odd);
1756 dt_opencl_release_mem_object(HF_RGB);
1757 dt_opencl_release_mem_object(HF_grey);
1758 return err;
1759 }
1760
1761
process_cl(struct dt_iop_module_t * self,dt_dev_pixelpipe_iop_t * piece,cl_mem dev_in,cl_mem dev_out,const dt_iop_roi_t * const roi_in,const dt_iop_roi_t * const roi_out)1762 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
1763 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
1764 {
1765 const dt_iop_filmicrgb_data_t *const d = (dt_iop_filmicrgb_data_t *)piece->data;
1766 dt_iop_filmicrgb_global_data_t *const gd = (dt_iop_filmicrgb_global_data_t *)self->global_data;
1767
1768 cl_int err = -999;
1769
1770 if(piece->colors != 4)
1771 {
1772 dt_control_log(_("filmic works only on RGB input"));
1773 return err;
1774 }
1775
1776 const int devid = piece->pipe->devid;
1777 const int width = roi_in->width;
1778 const int height = roi_in->height;
1779
1780 size_t sizes[] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
1781
1782 cl_mem in = dev_in;
1783 cl_mem inpainted = NULL;
1784 cl_mem reconstructed = NULL;
1785 cl_mem mask = NULL;
1786 cl_mem ratios = NULL;
1787 cl_mem norms = NULL;
1788
1789 // fetch working color profile
1790 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(piece->pipe);
1791 const int use_work_profile = (work_profile == NULL) ? 0 : 1;
1792 cl_mem dev_profile_info = NULL;
1793 cl_mem dev_profile_lut = NULL;
1794 dt_colorspaces_iccprofile_info_cl_t *profile_info_cl;
1795 cl_float *profile_lut_cl = NULL;
1796
1797 err = dt_ioppr_build_iccprofile_params_cl(work_profile, devid, &profile_info_cl, &profile_lut_cl,
1798 &dev_profile_info, &dev_profile_lut);
1799 if(err != CL_SUCCESS) goto error;
1800
1801 // used to adjust noise level depending on size. Don't amplify noise if magnified > 100%
1802 const float scale = fmaxf(piece->iscale / roi_in->scale, 1.f);
1803
1804 // get the number of OpenCL threads
1805 uint16_t is_clipped = 0;
1806 cl_mem clipped = dt_opencl_alloc_device(devid, 1, 1, sizeof(uint16_t));
1807
1808 // build a mask of clipped pixels
1809 mask = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float));
1810 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 0, sizeof(cl_mem), (void *)&in);
1811 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 1, sizeof(cl_mem), (void *)&mask);
1812 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 2, sizeof(int), (void *)&width);
1813 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 3, sizeof(int), (void *)&height);
1814 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 4, sizeof(float), (void *)&d->normalize);
1815 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 5, sizeof(float), (void *)&d->reconstruct_feather);
1816 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 6, sizeof(cl_mem), (void *)&clipped);
1817 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_mask, sizes);
1818 if(err != CL_SUCCESS) goto error;
1819
1820 // read the number of clipped pixels
1821 dt_opencl_copy_device_to_host(devid, &is_clipped, clipped, 1, 1, sizeof(uint16_t));
1822 dt_opencl_release_mem_object(clipped);
1823 clipped = NULL;
1824
1825 // display mask and exit
1826 if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL)
1827 {
1828 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
1829
1830 if(g->show_mask)
1831 {
1832 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 0, sizeof(cl_mem), (void *)&mask);
1833 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 1, sizeof(cl_mem), (void *)&dev_out);
1834 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 2, sizeof(int), (void *)&width);
1835 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 3, sizeof(int), (void *)&height);
1836 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_show_mask, sizes);
1837 dt_opencl_release_mem_object(mask);
1838 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
1839 return TRUE;
1840 }
1841 }
1842
1843 const gboolean run_fast = (piece->pipe->type & DT_DEV_PIXELPIPE_FAST) == DT_DEV_PIXELPIPE_FAST;
1844
1845 if(!run_fast && is_clipped > 0)
1846 {
1847 // Inpaint noise
1848 const float noise_level = d->noise_level / scale;
1849 inpainted = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1850 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 0, sizeof(cl_mem), (void *)&in);
1851 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 1, sizeof(cl_mem), (void *)&mask);
1852 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 2, sizeof(cl_mem), (void *)&inpainted);
1853 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 3, sizeof(int), (void *)&width);
1854 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 4, sizeof(int), (void *)&height);
1855 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 5, sizeof(float), (void *)&noise_level);
1856 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 6, sizeof(float), (void *)&d->reconstruct_threshold);
1857 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 7, sizeof(float), (void *)&d->noise_distribution);
1858 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_inpaint_noise, sizes);
1859 if(err != CL_SUCCESS) goto error;
1860
1861 // first step of highlight reconstruction in RGB
1862 reconstructed = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1863 err = reconstruct_highlights_cl(inpainted, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RGB, gd, d, piece, roi_in);
1864 if(err != CL_SUCCESS) goto error;
1865 dt_opencl_release_mem_object(inpainted);
1866 inpainted = NULL;
1867
1868 if(d->high_quality_reconstruction > 0)
1869 {
1870 ratios = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
1871 norms = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float));
1872
1873 if(norms && ratios)
1874 {
1875 for(int i = 0; i < d->high_quality_reconstruction; i++)
1876 {
1877 // break ratios and norms
1878 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 0, sizeof(cl_mem), (void *)&reconstructed);
1879 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 1, sizeof(cl_mem), (void *)&norms);
1880 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 2, sizeof(cl_mem), (void *)&ratios);
1881 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 3, sizeof(int), (void *)&d->preserve_color);
1882 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 4, sizeof(int), (void *)&width);
1883 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 5, sizeof(int), (void *)&height);
1884 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_compute_ratios, sizes);
1885 if(err != CL_SUCCESS) goto error;
1886
1887 // second step of reconstruction over ratios
1888 err = reconstruct_highlights_cl(ratios, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RATIOS, gd, d, piece, roi_in);
1889 if(err != CL_SUCCESS) goto error;
1890
1891 // restore ratios to RGB
1892 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 0, sizeof(cl_mem), (void *)&reconstructed);
1893 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 1, sizeof(cl_mem), (void *)&norms);
1894 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 2, sizeof(cl_mem), (void *)&reconstructed);
1895 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 3, sizeof(int), (void *)&width);
1896 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 4, sizeof(int), (void *)&height);
1897 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_restore_ratios, sizes);
1898 if(err != CL_SUCCESS) goto error;
1899 }
1900 }
1901
1902 dt_opencl_release_mem_object(ratios);
1903 dt_opencl_release_mem_object(norms);
1904 ratios = NULL;
1905 norms = NULL;
1906 }
1907
1908 in = reconstructed;
1909 }
1910
1911 dt_opencl_release_mem_object(mask); // mask is only used for highlights reconstruction.
1912 mask = NULL;
1913
1914 const dt_iop_filmic_rgb_spline_t spline = (dt_iop_filmic_rgb_spline_t)d->spline;
1915
1916 if(d->preserve_color == DT_FILMIC_METHOD_NONE)
1917 {
1918 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 0, sizeof(cl_mem), (void *)&in);
1919 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 1, sizeof(cl_mem), (void *)&dev_out);
1920 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 2, sizeof(int), (void *)&width);
1921 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 3, sizeof(int), (void *)&height);
1922 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 4, sizeof(float), (void *)&d->dynamic_range);
1923 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 5, sizeof(float), (void *)&d->black_source);
1924 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 6, sizeof(float), (void *)&d->grey_source);
1925 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 7, sizeof(cl_mem), (void *)&dev_profile_info);
1926 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 8, sizeof(cl_mem), (void *)&dev_profile_lut);
1927 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 9, sizeof(int), (void *)&use_work_profile);
1928 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 10, sizeof(float), (void *)&d->sigma_toe);
1929 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 11, sizeof(float), (void *)&d->sigma_shoulder);
1930 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 12, sizeof(float), (void *)&d->saturation);
1931 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 13, 4 * sizeof(float), (void *)&spline.M1);
1932 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 14, 4 * sizeof(float), (void *)&spline.M2);
1933 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 15, 4 * sizeof(float), (void *)&spline.M3);
1934 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 16, 4 * sizeof(float), (void *)&spline.M4);
1935 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 17, 4 * sizeof(float), (void *)&spline.M5);
1936 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 18, sizeof(float), (void *)&spline.latitude_min);
1937 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 19, sizeof(float), (void *)&spline.latitude_max);
1938 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 20, sizeof(float), (void *)&d->output_power);
1939 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 21, sizeof(int), (void *)&d->version);
1940 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 22, sizeof(int), (void *)&spline.type[0]);
1941 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 23, sizeof(int), (void *)&spline.type[1]);
1942
1943 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_rgb_split, sizes);
1944 if(err != CL_SUCCESS) goto error;
1945 }
1946 else
1947 {
1948 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 0, sizeof(cl_mem), (void *)&in);
1949 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 1, sizeof(cl_mem), (void *)&dev_out);
1950 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 2, sizeof(int), (void *)&width);
1951 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 3, sizeof(int), (void *)&height);
1952 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 4, sizeof(float), (void *)&d->dynamic_range);
1953 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 5, sizeof(float), (void *)&d->black_source);
1954 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 6, sizeof(float), (void *)&d->grey_source);
1955 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 7, sizeof(cl_mem), (void *)&dev_profile_info);
1956 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 8, sizeof(cl_mem), (void *)&dev_profile_lut);
1957 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 9, sizeof(int), (void *)&use_work_profile);
1958 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 10, sizeof(float), (void *)&d->sigma_toe);
1959 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 11, sizeof(float), (void *)&d->sigma_shoulder);
1960 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 12, sizeof(float), (void *)&d->saturation);
1961 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 13, 4 * sizeof(float), (void *)&spline.M1);
1962 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 14, 4 * sizeof(float), (void *)&spline.M2);
1963 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 15, 4 * sizeof(float), (void *)&spline.M3);
1964 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 16, 4 * sizeof(float), (void *)&spline.M4);
1965 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 17, 4 * sizeof(float), (void *)&spline.M5);
1966 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 18, sizeof(float), (void *)&spline.latitude_min);
1967 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 19, sizeof(float), (void *)&spline.latitude_max);
1968 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 20, sizeof(float), (void *)&d->output_power);
1969 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 21, sizeof(int), (void *)&d->preserve_color);
1970 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 22, sizeof(int), (void *)&d->version);
1971 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 23, sizeof(int), (void *)&spline.type[0]);
1972 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 24, sizeof(int), (void *)&spline.type[1]);
1973
1974 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_rgb_chroma, sizes);
1975 if(err != CL_SUCCESS) goto error;
1976 }
1977
1978 dt_opencl_release_mem_object(reconstructed);
1979 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
1980 return TRUE;
1981
1982 error:
1983 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
1984 dt_opencl_release_mem_object(reconstructed);
1985 dt_opencl_release_mem_object(inpainted);
1986 dt_opencl_release_mem_object(mask);
1987 dt_opencl_release_mem_object(ratios);
1988 dt_opencl_release_mem_object(norms);
1989 dt_print(DT_DEBUG_OPENCL, "[opencl_filmicrgb] couldn't enqueue kernel! %d\n", err);
1990 return FALSE;
1991 }
1992 #endif
1993
1994
apply_auto_grey(dt_iop_module_t * self)1995 static void apply_auto_grey(dt_iop_module_t *self)
1996 {
1997 if(darktable.gui->reset) return;
1998 dt_iop_filmicrgb_params_t *p = (dt_iop_filmicrgb_params_t *)self->params;
1999 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
2000
2001 const dt_iop_order_iccprofile_info_t *const work_profile
2002 = dt_ioppr_get_iop_work_profile_info(self, self->dev->iop);
2003 const float grey = get_pixel_norm(self->picked_color, p->preserve_color, work_profile) / 2.0f;
2004
2005 const float prev_grey = p->grey_point_source;
2006 p->grey_point_source = CLAMP(100.f * grey, 0.001f, 100.0f);
2007 const float grey_var = log2f(prev_grey / p->grey_point_source);
2008 p->black_point_source = p->black_point_source - grey_var;
2009 p->white_point_source = p->white_point_source + grey_var;
2010 p->output_power = logf(p->grey_point_target / 100.0f)
2011 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
2012
2013 ++darktable.gui->reset;
2014 dt_bauhaus_slider_set_soft(g->grey_point_source, p->grey_point_source);
2015 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
2016 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
2017 dt_bauhaus_slider_set_soft(g->output_power, p->output_power);
2018 --darktable.gui->reset;
2019
2020 gtk_widget_queue_draw(self->widget);
2021 dt_dev_add_history_item(darktable.develop, self, TRUE);
2022 }
2023
apply_auto_black(dt_iop_module_t * self)2024 static void apply_auto_black(dt_iop_module_t *self)
2025 {
2026 if(darktable.gui->reset) return;
2027 dt_iop_filmicrgb_params_t *p = (dt_iop_filmicrgb_params_t *)self->params;
2028 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
2029
2030 // Black
2031 const dt_iop_order_iccprofile_info_t *const work_profile
2032 = dt_ioppr_get_iop_work_profile_info(self, self->dev->iop);
2033 const float black = get_pixel_norm(self->picked_color_min, DT_FILMIC_METHOD_MAX_RGB, work_profile);
2034
2035 float EVmin = CLAMP(log2f(black / (p->grey_point_source / 100.0f)), -16.0f, -1.0f);
2036 EVmin *= (1.0f + p->security_factor / 100.0f);
2037
2038 p->black_point_source = fmaxf(EVmin, -16.0f);
2039 p->output_power = logf(p->grey_point_target / 100.0f)
2040 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
2041
2042 ++darktable.gui->reset;
2043 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
2044 dt_bauhaus_slider_set_soft(g->output_power, p->output_power);
2045 --darktable.gui->reset;
2046
2047 gtk_widget_queue_draw(self->widget);
2048 dt_dev_add_history_item(darktable.develop, self, TRUE);
2049 }
2050
2051
apply_auto_white_point_source(dt_iop_module_t * self)2052 static void apply_auto_white_point_source(dt_iop_module_t *self)
2053 {
2054 if(darktable.gui->reset) return;
2055 dt_iop_filmicrgb_params_t *p = (dt_iop_filmicrgb_params_t *)self->params;
2056 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
2057
2058 // White
2059 const dt_iop_order_iccprofile_info_t *const work_profile
2060 = dt_ioppr_get_iop_work_profile_info(self, self->dev->iop);
2061 const float white = get_pixel_norm(self->picked_color_max, DT_FILMIC_METHOD_MAX_RGB, work_profile);
2062
2063 float EVmax = CLAMP(log2f(white / (p->grey_point_source / 100.0f)), 1.0f, 16.0f);
2064 EVmax *= (1.0f + p->security_factor / 100.0f);
2065
2066 p->white_point_source = EVmax;
2067 p->output_power = logf(p->grey_point_target / 100.0f)
2068 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
2069
2070 ++darktable.gui->reset;
2071 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
2072 dt_bauhaus_slider_set_soft(g->output_power, p->output_power);
2073 --darktable.gui->reset;
2074
2075 gtk_widget_queue_draw(self->widget);
2076 dt_dev_add_history_item(darktable.develop, self, TRUE);
2077 }
2078
apply_autotune(dt_iop_module_t * self)2079 static void apply_autotune(dt_iop_module_t *self)
2080 {
2081 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
2082 dt_iop_filmicrgb_params_t *p = (dt_iop_filmicrgb_params_t *)self->params;
2083 const dt_iop_order_iccprofile_info_t *const work_profile
2084 = dt_ioppr_get_iop_work_profile_info(self, self->dev->iop);
2085
2086 // Grey
2087 if(p->custom_grey)
2088 {
2089 const float grey = get_pixel_norm(self->picked_color, p->preserve_color, work_profile) / 2.0f;
2090 p->grey_point_source = CLAMP(100.f * grey, 0.001f, 100.0f);
2091 }
2092
2093 // White
2094 const float white = get_pixel_norm(self->picked_color_max, DT_FILMIC_METHOD_MAX_RGB, work_profile);
2095 float EVmax = CLAMP(log2f(white / (p->grey_point_source / 100.0f)), 1.0f, 16.0f);
2096 EVmax *= (1.0f + p->security_factor / 100.0f);
2097
2098 // Black
2099 const float black = get_pixel_norm(self->picked_color_min, DT_FILMIC_METHOD_MAX_RGB, work_profile);
2100 float EVmin = CLAMP(log2f(black / (p->grey_point_source / 100.0f)), -16.0f, -1.0f);
2101 EVmin *= (1.0f + p->security_factor / 100.0f);
2102
2103 p->black_point_source = fmaxf(EVmin, -16.0f);
2104 p->white_point_source = EVmax;
2105 p->output_power = logf(p->grey_point_target / 100.0f)
2106 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
2107
2108 ++darktable.gui->reset;
2109 dt_bauhaus_slider_set_soft(g->grey_point_source, p->grey_point_source);
2110 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
2111 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
2112 dt_bauhaus_slider_set_soft(g->output_power, p->output_power);
2113 --darktable.gui->reset;
2114
2115 gtk_widget_queue_draw(self->widget);
2116 dt_dev_add_history_item(darktable.develop, self, TRUE);
2117 }
2118
color_picker_apply(dt_iop_module_t * self,GtkWidget * picker,dt_dev_pixelpipe_iop_t * piece)2119 void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_iop_t *piece)
2120 {
2121 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
2122
2123 if(picker == g->grey_point_source)
2124 apply_auto_grey(self);
2125 else if(picker == g->black_point_source)
2126 apply_auto_black(self);
2127 else if(picker == g->white_point_source)
2128 apply_auto_white_point_source(self);
2129 else if(picker == g->auto_button)
2130 apply_autotune(self);
2131 }
2132
show_mask_callback(GtkWidget * slider,gpointer user_data)2133 static void show_mask_callback(GtkWidget *slider, gpointer user_data)
2134 {
2135 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2136 if(darktable.gui->reset) return;
2137 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
2138 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
2139 g->show_mask = !(g->show_mask);
2140 dt_bauhaus_widget_set_quad_active(g->show_highlight_mask, g->show_mask);
2141 dt_bauhaus_widget_set_quad_toggle(g->show_highlight_mask, g->show_mask);
2142 dt_dev_reprocess_center(self->dev);
2143 }
2144
2145
2146 #define ORDER_4 5
2147 #define ORDER_3 4
2148
2149
dt_iop_filmic_rgb_compute_spline(const dt_iop_filmicrgb_params_t * const p,struct dt_iop_filmic_rgb_spline_t * const spline)2150 inline static void dt_iop_filmic_rgb_compute_spline(const dt_iop_filmicrgb_params_t *const p,
2151 struct dt_iop_filmic_rgb_spline_t *const spline)
2152 {
2153 float grey_display = 0.4638f;
2154
2155 if(p->custom_grey)
2156 {
2157 // user set a custom value
2158 grey_display = powf(CLAMP(p->grey_point_target, p->black_point_target, p->white_point_target) / 100.0f,
2159 1.0f / (p->output_power));
2160 }
2161 else
2162 {
2163 // use 18.45% grey and don't bother
2164 grey_display = powf(0.1845f, 1.0f / (p->output_power));
2165 }
2166
2167 const float white_source = p->white_point_source;
2168 const float black_source = p->black_point_source;
2169 const float dynamic_range = white_source - black_source;
2170
2171 // luminance after log encoding
2172 const float black_log = 0.0f; // assumes user set log as in the autotuner
2173 const float grey_log = fabsf(p->black_point_source) / dynamic_range;
2174 const float white_log = 1.0f; // assumes user set log as in the autotuner
2175
2176 // target luminance desired after filmic curve
2177 float black_display, white_display;
2178
2179 if(p->internal_version == 2019)
2180 {
2181 // this is a buggy version that doesn't take the output power function into account
2182 // it was silent because black and white display were set to 0 and 1 and users were advised to not touch them.
2183 // (since 0^x = 0 and 1^x = 1). It's not silent anymore if black display > 0,
2184 // for example if compensating ICC black level for target medium
2185 black_display = CLAMP(p->black_point_target, 0.0f, p->grey_point_target) / 100.0f; // in %
2186 white_display = fmaxf(p->white_point_target, p->grey_point_target) / 100.0f; // in %
2187 }
2188 else //(p->internal_version == 2020)
2189 {
2190 // this is the fixed version
2191 black_display = powf(CLAMP(p->black_point_target, 0.0f, p->grey_point_target) / 100.0f,
2192 1.0f / (p->output_power)); // in %
2193 white_display
2194 = powf(fmaxf(p->white_point_target, p->grey_point_target) / 100.0f, 1.0f / (p->output_power)); // in %
2195 }
2196
2197 float latitude = CLAMP(p->latitude, 0.0f, 100.0f) / 100.0f * dynamic_range; // in % of dynamic range
2198 float balance = CLAMP(p->balance, -50.0f, 50.0f) / 100.0f; // in %
2199 float contrast = CLAMP(p->contrast, 1.00001f, 6.0f);
2200
2201 // nodes for mapping from log encoding to desired target luminance
2202 // X coordinates
2203 float toe_log = grey_log - latitude / dynamic_range * fabsf(black_source / dynamic_range);
2204 float shoulder_log = grey_log + latitude / dynamic_range * fabsf(white_source / dynamic_range);
2205
2206 // interception
2207 float linear_intercept = grey_display - (contrast * grey_log);
2208
2209 // y coordinates
2210 float toe_display = (toe_log * contrast + linear_intercept);
2211 float shoulder_display = (shoulder_log * contrast + linear_intercept);
2212
2213 // Apply the highlights/shadows balance as a shift along the contrast slope
2214 const float norm = sqrtf(contrast * contrast + 1.0f);
2215
2216 // negative values drag to the left and compress the shadows, on the UI negative is the inverse
2217 const float coeff = -((2.0f * latitude) / dynamic_range) * balance;
2218
2219 toe_display += coeff * contrast / norm;
2220 shoulder_display += coeff * contrast / norm;
2221 toe_log += coeff / norm;
2222 shoulder_log += coeff / norm;
2223
2224 /**
2225 * Now we have 3 segments :
2226 * - x = [0.0 ; toe_log], curved part
2227 * - x = [toe_log ; grey_log ; shoulder_log], linear part
2228 * - x = [shoulder_log ; 1.0] curved part
2229 *
2230 * BUT : in case some nodes overlap, we need to remove them to avoid
2231 * degenerating of the curve
2232 **/
2233
2234 // Build the curve from the nodes
2235 spline->x[0] = black_log;
2236 spline->x[1] = toe_log;
2237 spline->x[2] = grey_log;
2238 spline->x[3] = shoulder_log;
2239 spline->x[4] = white_log;
2240
2241 spline->y[0] = black_display;
2242 spline->y[1] = toe_display;
2243 spline->y[2] = grey_display;
2244 spline->y[3] = shoulder_display;
2245 spline->y[4] = white_display;
2246
2247 spline->latitude_min = spline->x[1];
2248 spline->latitude_max = spline->x[3];
2249
2250 spline->type[0] = p->shadows;
2251 spline->type[1] = p->highlights;
2252
2253 /**
2254 * For background and details, see :
2255 * https://eng.aurelienpierre.com/2018/11/30/filmic-darktable-and-the-quest-of-the-hdr-tone-mapping/#filmic_s_curve
2256 *
2257 **/
2258 const double Tl = spline->x[1];
2259 const double Tl2 = Tl * Tl;
2260 const double Tl3 = Tl2 * Tl;
2261 const double Tl4 = Tl3 * Tl;
2262
2263 const double Sl = spline->x[3];
2264 const double Sl2 = Sl * Sl;
2265 const double Sl3 = Sl2 * Sl;
2266 const double Sl4 = Sl3 * Sl;
2267
2268 // if type polynomial :
2269 // y = M5 * x⁴ + M4 * x³ + M3 * x² + M2 * x¹ + M1 * x⁰
2270 // else if type rational :
2271 // y = M1 * (M2 * (x - x_0)² + (x - x_0)) / (M2 * (x - x_0)² + (x - x_0) + M3)
2272 // We then compute M1 to M5 coeffs using the imposed conditions over the curve.
2273 // M1 to M5 are 3×1 vectors, where each element belongs to a part of the curve.
2274
2275 // solve the linear central part - affine function
2276 spline->M2[2] = contrast; // * x¹ (slope)
2277 spline->M1[2] = spline->y[1] - spline->M2[2] * spline->x[1]; // * x⁰ (offset)
2278 spline->M3[2] = 0.f; // * x²
2279 spline->M4[2] = 0.f; // * x³
2280 spline->M5[2] = 0.f; // * x⁴
2281
2282 // solve the toe part
2283 if(p->shadows == DT_FILMIC_CURVE_POLY_4)
2284 {
2285 // fourth order polynom - only mode in darktable 3.0.0
2286 double A0[ORDER_4 * ORDER_4] = { 0., 0., 0., 0., 1., // position in 0
2287 0., 0., 0., 1., 0., // first derivative in 0
2288 Tl4, Tl3, Tl2, Tl, 1., // position at toe node
2289 4. * Tl3, 3. * Tl2, 2. * Tl, 1., 0., // first derivative at toe node
2290 12. * Tl2, 6. * Tl, 2., 0., 0. }; // second derivative at toe node
2291
2292 double b0[ORDER_4] = { spline->y[0], 0., spline->y[1], spline->M2[2], 0. };
2293
2294 gauss_solve(A0, b0, ORDER_4);
2295
2296 spline->M5[0] = b0[0]; // * x⁴
2297 spline->M4[0] = b0[1]; // * x³
2298 spline->M3[0] = b0[2]; // * x²
2299 spline->M2[0] = b0[3]; // * x¹
2300 spline->M1[0] = b0[4]; // * x⁰
2301 }
2302 else if(p->shadows == DT_FILMIC_CURVE_POLY_3)
2303 {
2304 // third order polynom
2305 double A0[ORDER_3 * ORDER_3] = { 0., 0., 0., 1., // position in 0
2306 Tl3, Tl2, Tl, 1., // position at toe node
2307 3. * Tl2, 2. * Tl, 1., 0., // first derivative at toe node
2308 6. * Tl, 2., 0., 0. }; // second derivative at toe node
2309
2310 double b0[ORDER_3] = { spline->y[0], spline->y[1], spline->M2[2], 0. };
2311
2312 gauss_solve(A0, b0, ORDER_3);
2313
2314 spline->M5[0] = 0.0f; // * x⁴
2315 spline->M4[0] = b0[0]; // * x³
2316 spline->M3[0] = b0[1]; // * x²
2317 spline->M2[0] = b0[2]; // * x¹
2318 spline->M1[0] = b0[3]; // * x⁰
2319 }
2320 else
2321 {
2322 const float P1[2] = { black_log, black_display };
2323 const float P0[2] = { toe_log, toe_display };
2324 const float x = P0[0] - P1[0];
2325 const float y = P0[1] - P1[1];
2326 const float g = contrast;
2327 const float b = g / (2.f * y) + (sqrtf(sqf(x * g / y + 1.f) - 4.f) - 1.f) / (2.f * x);
2328 const float c = y / g * (b * sqf(x) + x) / (b * sqf(x) + x - (y / g));
2329 const float a = c * g;
2330 spline->M1[0] = a;
2331 spline->M2[0] = b;
2332 spline->M3[0] = c;
2333 spline->M4[0] = toe_display;
2334 }
2335
2336 // solve the shoulder part
2337 if(p->highlights == DT_FILMIC_CURVE_POLY_3)
2338 {
2339 // 3rd order polynom - only mode in darktable 3.0.0
2340 double A1[ORDER_3 * ORDER_3] = { 1., 1., 1., 1., // position in 1
2341 Sl3, Sl2, Sl, 1., // position at shoulder node
2342 3. * Sl2, 2. * Sl, 1., 0., // first derivative at shoulder node
2343 6. * Sl, 2., 0., 0. }; // second derivative at shoulder node
2344
2345 double b1[ORDER_3] = { spline->y[4], spline->y[3], spline->M2[2], 0. };
2346
2347 gauss_solve(A1, b1, ORDER_3);
2348
2349 spline->M5[1] = 0.0f; // * x⁴
2350 spline->M4[1] = b1[0]; // * x³
2351 spline->M3[1] = b1[1]; // * x²
2352 spline->M2[1] = b1[2]; // * x¹
2353 spline->M1[1] = b1[3]; // * x⁰
2354 }
2355 else if(p->highlights == DT_FILMIC_CURVE_POLY_4)
2356 {
2357 // 4th order polynom
2358 double A1[ORDER_4 * ORDER_4] = { 1., 1., 1., 1., 1., // position in 1
2359 4., 3., 2., 1., 0., // first derivative in 1
2360 Sl4, Sl3, Sl2, Sl, 1., // position at shoulder node
2361 4. * Sl3, 3. * Sl2, 2. * Sl, 1., 0., // first derivative at shoulder node
2362 12. * Sl2, 6. * Sl, 2., 0., 0. }; // second derivative at shoulder node
2363
2364 double b1[ORDER_4] = { spline->y[4], 0., spline->y[3], spline->M2[2], 0. };
2365
2366 gauss_solve(A1, b1, ORDER_4);
2367
2368 spline->M5[1] = b1[0]; // * x⁴
2369 spline->M4[1] = b1[1]; // * x³
2370 spline->M3[1] = b1[2]; // * x²
2371 spline->M2[1] = b1[3]; // * x¹
2372 spline->M1[1] = b1[4]; // * x⁰
2373 }
2374 else
2375 {
2376 const float P1[2] = { white_log, white_display };
2377 const float P0[2] = { shoulder_log, shoulder_display };
2378 const float x = P1[0] - P0[0];
2379 const float y = P1[1] - P0[1];
2380 const float g = contrast;
2381 const float b = g / (2.f * y) + (sqrtf(sqf(x * g / y + 1.f) - 4.f) - 1.f) / (2.f * x);
2382 const float c = y / g * (b * sqf(x) + x) / (b * sqf(x) + x - (y / g));
2383 const float a = c * g;
2384 spline->M1[1] = a;
2385 spline->M2[1] = b;
2386 spline->M3[1] = c;
2387 spline->M4[1] = shoulder_display;
2388 }
2389 }
2390
commit_params(dt_iop_module_t * self,dt_iop_params_t * p1,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)2391 void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
2392 dt_dev_pixelpipe_iop_t *piece)
2393 {
2394 dt_iop_filmicrgb_params_t *p = (dt_iop_filmicrgb_params_t *)p1;
2395 dt_iop_filmicrgb_data_t *d = (dt_iop_filmicrgb_data_t *)piece->data;
2396
2397 // source and display greys
2398 float grey_source = 0.1845f, grey_display = 0.4638f;
2399 if(p->custom_grey)
2400 {
2401 // user set a custom value
2402 grey_source = p->grey_point_source / 100.0f; // in %
2403 grey_display = powf(p->grey_point_target / 100.0f, 1.0f / (p->output_power));
2404 }
2405 else
2406 {
2407 // use 18.45% grey and don't bother
2408 grey_source = 0.1845f; // in %
2409 grey_display = powf(0.1845f, 1.0f / (p->output_power));
2410 }
2411
2412 // source luminance - Used only in the log encoding
2413 const float white_source = p->white_point_source;
2414 const float black_source = p->black_point_source;
2415 const float dynamic_range = white_source - black_source;
2416
2417 // luminance after log encoding
2418 const float grey_log = fabsf(p->black_point_source) / dynamic_range;
2419
2420
2421 float contrast = p->contrast;
2422 if(contrast < grey_display / grey_log)
2423 {
2424 // We need grey_display - (contrast * grey_log) <= 0.0
2425 contrast = 1.0001f * grey_display / grey_log;
2426 }
2427
2428 // commit
2429 d->dynamic_range = dynamic_range;
2430 d->black_source = black_source;
2431 d->grey_source = grey_source;
2432 d->output_power = p->output_power;
2433 d->contrast = contrast;
2434 d->version = p->version;
2435 d->preserve_color = p->preserve_color;
2436 d->high_quality_reconstruction = p->high_quality_reconstruction;
2437 d->noise_level = p->noise_level;
2438 d->noise_distribution = p->noise_distribution;
2439
2440 // compute the curves and their LUT
2441 dt_iop_filmic_rgb_compute_spline(p, &d->spline);
2442
2443 d->saturation = (2.0f * p->saturation / 100.0f + 1.0f);
2444 d->sigma_toe = powf(d->spline.latitude_min / 3.0f, 2.0f);
2445 d->sigma_shoulder = powf((1.0f - d->spline.latitude_max) / 3.0f, 2.0f);
2446
2447 d->reconstruct_threshold = powf(2.0f, white_source + p->reconstruct_threshold) * grey_source;
2448 d->reconstruct_feather = exp2f(12.f / p->reconstruct_feather);
2449
2450 // offset and rescale user param to alpha blending so 0 -> 50% and 1 -> 100%
2451 d->normalize = d->reconstruct_feather / d->reconstruct_threshold;
2452 d->reconstruct_structure_vs_texture = (p->reconstruct_structure_vs_texture / 100.0f + 1.f) / 2.f;
2453 d->reconstruct_bloom_vs_details = (p->reconstruct_bloom_vs_details / 100.0f + 1.f) / 2.f;
2454 d->reconstruct_grey_vs_color = (p->reconstruct_grey_vs_color / 100.0f + 1.f) / 2.f;
2455 }
2456
gui_focus(struct dt_iop_module_t * self,gboolean in)2457 void gui_focus(struct dt_iop_module_t *self, gboolean in)
2458 {
2459 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
2460
2461 if(!in)
2462 {
2463 // lost focus - hide the mask
2464 gint mask_was_shown = g->show_mask;
2465 g->show_mask = FALSE;
2466 dt_bauhaus_widget_set_quad_toggle(g->show_highlight_mask, FALSE);
2467 dt_bauhaus_widget_set_quad_active(g->show_highlight_mask, FALSE);
2468 if(mask_was_shown) dt_dev_reprocess_center(self->dev);
2469 }
2470 }
2471
init_pipe(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)2472 void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
2473 {
2474 piece->data = calloc(1, sizeof(dt_iop_filmicrgb_data_t));
2475 }
2476
cleanup_pipe(dt_iop_module_t * self,dt_dev_pixelpipe_t * pipe,dt_dev_pixelpipe_iop_t * piece)2477 void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
2478 {
2479 free(piece->data);
2480 piece->data = NULL;
2481 }
2482
gui_update(dt_iop_module_t * self)2483 void gui_update(dt_iop_module_t *self)
2484 {
2485 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
2486 dt_iop_filmicrgb_params_t *p = (dt_iop_filmicrgb_params_t *)self->params;
2487
2488 dt_iop_color_picker_reset(self, TRUE);
2489
2490 g->show_mask = FALSE;
2491 g->gui_mode = dt_conf_get_int("plugins/darkroom/filmicrgb/graph_view");
2492 g->gui_show_labels = dt_conf_get_int("plugins/darkroom/filmicrgb/graph_show_labels");
2493 g->gui_hover = FALSE;
2494 g->gui_sizes_inited = FALSE;
2495
2496 // fetch last view in dartablerc
2497
2498
2499 self->color_picker_box[0] = self->color_picker_box[1] = .25f;
2500 self->color_picker_box[2] = self->color_picker_box[3] = .50f;
2501 self->color_picker_point[0] = self->color_picker_point[1] = 0.5f;
2502
2503 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
2504 dt_bauhaus_slider_set_soft(g->grey_point_source, p->grey_point_source);
2505 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
2506 dt_bauhaus_slider_set_soft(g->security_factor, p->security_factor);
2507 dt_bauhaus_slider_set_soft(g->reconstruct_threshold, p->reconstruct_threshold);
2508 dt_bauhaus_slider_set_soft(g->reconstruct_feather, p->reconstruct_feather);
2509 dt_bauhaus_slider_set_soft(g->reconstruct_bloom_vs_details, p->reconstruct_bloom_vs_details);
2510 dt_bauhaus_slider_set_soft(g->reconstruct_grey_vs_color, p->reconstruct_grey_vs_color);
2511 dt_bauhaus_slider_set_soft(g->reconstruct_structure_vs_texture, p->reconstruct_structure_vs_texture);
2512 dt_bauhaus_slider_set_soft(g->white_point_target, p->white_point_target);
2513 dt_bauhaus_slider_set_soft(g->grey_point_target, p->grey_point_target);
2514 dt_bauhaus_slider_set_soft(g->black_point_target, p->black_point_target);
2515 dt_bauhaus_slider_set_soft(g->output_power, p->output_power);
2516 dt_bauhaus_slider_set_soft(g->latitude, p->latitude);
2517 dt_bauhaus_slider_set_soft(g->contrast, p->contrast);
2518 dt_bauhaus_slider_set_soft(g->saturation, p->saturation);
2519 dt_bauhaus_slider_set_soft(g->balance, p->balance);
2520
2521 dt_bauhaus_combobox_set_from_value(g->version, p->version);
2522 dt_bauhaus_combobox_set_from_value(g->preserve_color, p->preserve_color);
2523 dt_bauhaus_combobox_set_from_value(g->shadows, p->shadows);
2524 dt_bauhaus_combobox_set_from_value(g->highlights, p->highlights);
2525 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->auto_hardness), p->auto_hardness);
2526 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->custom_grey), p->custom_grey);
2527
2528 dt_bauhaus_slider_set_soft(g->high_quality_reconstruction, p->high_quality_reconstruction);
2529 dt_bauhaus_slider_set_soft(g->noise_level, p->noise_level);
2530 dt_bauhaus_combobox_set(g->noise_distribution, p->noise_distribution);
2531
2532 gui_changed(self, NULL, NULL);
2533
2534 gtk_widget_queue_draw(self->widget);
2535 }
2536
reload_defaults(dt_iop_module_t * module)2537 void reload_defaults(dt_iop_module_t *module)
2538 {
2539 dt_iop_filmicrgb_params_t *d = module->default_params;
2540
2541 d->black_point_source = module->so->get_f("black_point_source")->Float.Default;
2542 d->white_point_source = module->so->get_f("white_point_source")->Float.Default;
2543 d->output_power = module->so->get_f("output_power")->Float.Default;
2544
2545 module->default_enabled = FALSE;
2546
2547 gchar *workflow = dt_conf_get_string("plugins/darkroom/workflow");
2548 const gboolean is_scene_referred = strcmp(workflow, "scene-referred") == 0;
2549 g_free(workflow);
2550
2551 if(dt_image_is_matrix_correction_supported(&module->dev->image_storage) && is_scene_referred)
2552 {
2553 // For scene-referred workflow, auto-enable and adjust based on exposure
2554 // TODO: fetch actual exposure in module, don't assume 1.
2555 const float exposure = 0.5f - dt_image_get_exposure_bias(&module->dev->image_storage);
2556
2557 // As global exposure increases, white exposure increases faster than black
2558 // this is probably because raw black/white points offsets the lower bound of the dynamic range to 0
2559 // so exposure compensation actually increases the dynamic range too (stretches only white).
2560 d->black_point_source += 0.5f * exposure;
2561 d->white_point_source += 0.8f * exposure;
2562 d->output_power = logf(d->grey_point_target / 100.0f)
2563 / logf(-d->black_point_source / (d->white_point_source - d->black_point_source));
2564 }
2565 }
2566
2567
init_global(dt_iop_module_so_t * module)2568 void init_global(dt_iop_module_so_t *module)
2569 {
2570 const int program = 22; // filmic.cl, from programs.conf
2571 dt_iop_filmicrgb_global_data_t *gd
2572 = (dt_iop_filmicrgb_global_data_t *)malloc(sizeof(dt_iop_filmicrgb_global_data_t));
2573
2574 module->data = gd;
2575 gd->kernel_filmic_rgb_split = dt_opencl_create_kernel(program, "filmicrgb_split");
2576 gd->kernel_filmic_rgb_chroma = dt_opencl_create_kernel(program, "filmicrgb_chroma");
2577 gd->kernel_filmic_mask = dt_opencl_create_kernel(program, "filmic_mask_clipped_pixels");
2578 gd->kernel_filmic_show_mask = dt_opencl_create_kernel(program, "filmic_show_mask");
2579 gd->kernel_filmic_inpaint_noise = dt_opencl_create_kernel(program, "filmic_inpaint_noise");
2580 gd->kernel_filmic_bspline_vertical = dt_opencl_create_kernel(program, "blur_2D_Bspline_vertical");
2581 gd->kernel_filmic_bspline_horizontal = dt_opencl_create_kernel(program, "blur_2D_Bspline_horizontal");
2582 gd->kernel_filmic_init_reconstruct = dt_opencl_create_kernel(program, "init_reconstruct");
2583 gd->kernel_filmic_wavelets_detail = dt_opencl_create_kernel(program, "wavelets_detail_level");
2584 gd->kernel_filmic_wavelets_reconstruct = dt_opencl_create_kernel(program, "wavelets_reconstruct");
2585 gd->kernel_filmic_compute_ratios = dt_opencl_create_kernel(program, "compute_ratios");
2586 gd->kernel_filmic_restore_ratios = dt_opencl_create_kernel(program, "restore_ratios");
2587 }
2588
cleanup_global(dt_iop_module_so_t * module)2589 void cleanup_global(dt_iop_module_so_t *module)
2590 {
2591 dt_iop_filmicrgb_global_data_t *gd = (dt_iop_filmicrgb_global_data_t *)module->data;
2592 dt_opencl_free_kernel(gd->kernel_filmic_rgb_split);
2593 dt_opencl_free_kernel(gd->kernel_filmic_rgb_chroma);
2594 dt_opencl_free_kernel(gd->kernel_filmic_mask);
2595 dt_opencl_free_kernel(gd->kernel_filmic_show_mask);
2596 dt_opencl_free_kernel(gd->kernel_filmic_inpaint_noise);
2597 dt_opencl_free_kernel(gd->kernel_filmic_bspline_vertical);
2598 dt_opencl_free_kernel(gd->kernel_filmic_bspline_horizontal);
2599 dt_opencl_free_kernel(gd->kernel_filmic_init_reconstruct);
2600 dt_opencl_free_kernel(gd->kernel_filmic_wavelets_detail);
2601 dt_opencl_free_kernel(gd->kernel_filmic_wavelets_reconstruct);
2602 dt_opencl_free_kernel(gd->kernel_filmic_compute_ratios);
2603 dt_opencl_free_kernel(gd->kernel_filmic_restore_ratios);
2604 free(module->data);
2605 module->data = NULL;
2606 }
2607
2608
gui_reset(dt_iop_module_t * self)2609 void gui_reset(dt_iop_module_t *self)
2610 {
2611 dt_iop_color_picker_reset(self, TRUE);
2612 }
2613
2614 #define LOGBASE 20.f
2615
dt_cairo_draw_arrow(cairo_t * cr,double origin_x,double origin_y,double destination_x,double destination_y,gboolean show_head)2616 static inline void dt_cairo_draw_arrow(cairo_t *cr, double origin_x, double origin_y, double destination_x,
2617 double destination_y, gboolean show_head)
2618 {
2619 cairo_move_to(cr, origin_x, origin_y);
2620 cairo_line_to(cr, destination_x, destination_y);
2621 cairo_stroke(cr);
2622
2623 if(show_head)
2624 {
2625 // arrow head is hard set to 45° - convert to radians
2626 const float angle_arrow = 45.f / 360.f * M_PI;
2627 const float angle_trunk = atan2f((destination_y - origin_y), (destination_x - origin_x));
2628 const float radius = DT_PIXEL_APPLY_DPI(3);
2629
2630 const float x_1 = destination_x + radius / sinf(angle_arrow + angle_trunk);
2631 const float y_1 = destination_y + radius / cosf(angle_arrow + angle_trunk);
2632
2633 const float x_2 = destination_x - radius / sinf(-angle_arrow + angle_trunk);
2634 const float y_2 = destination_y - radius / cosf(-angle_arrow + angle_trunk);
2635
2636 cairo_move_to(cr, x_1, y_1);
2637 cairo_line_to(cr, destination_x, destination_y);
2638 cairo_line_to(cr, x_2, y_2);
2639 cairo_stroke(cr);
2640 }
2641 }
2642
filmic_gui_draw_icon(cairo_t * cr,struct dt_iop_filmicrgb_gui_button_data_t * button,struct dt_iop_filmicrgb_gui_data_t * g)2643 void filmic_gui_draw_icon(cairo_t *cr, struct dt_iop_filmicrgb_gui_button_data_t *button,
2644 struct dt_iop_filmicrgb_gui_data_t *g)
2645 {
2646 if(!g->gui_sizes_inited) return;
2647
2648 cairo_save(cr);
2649
2650 GdkRGBA color;
2651
2652 // copy color
2653 color.red = darktable.bauhaus->graph_fg.red;
2654 color.green = darktable.bauhaus->graph_fg.green;
2655 color.blue = darktable.bauhaus->graph_fg.blue;
2656 color.alpha = darktable.bauhaus->graph_fg.alpha;
2657
2658 if(button->mouse_hover)
2659 {
2660 // use graph_fg color as-is if mouse hover
2661 cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha);
2662 }
2663 else
2664 {
2665 // use graph_fg color with transparency else
2666 cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha * 0.5);
2667 }
2668
2669 cairo_rectangle(cr, button->left, button->top, button->w - DT_PIXEL_APPLY_DPI(0.5),
2670 button->h - DT_PIXEL_APPLY_DPI(0.5));
2671 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
2672 cairo_stroke(cr);
2673 cairo_translate(cr, button->left + button->w / 2. - DT_PIXEL_APPLY_DPI(0.25),
2674 button->top + button->h / 2. - DT_PIXEL_APPLY_DPI(0.25));
2675
2676 const float scale = 0.85;
2677 cairo_scale(cr, scale, scale);
2678 button->icon(cr, -scale * button->w / 2., -scale * button->h / 2., scale * button->w, scale * button->h,
2679 CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL);
2680 cairo_restore(cr);
2681 }
2682
2683
dt_iop_tonecurve_draw(GtkWidget * widget,cairo_t * crf,gpointer user_data)2684 static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
2685 {
2686 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2687 dt_iop_filmicrgb_params_t *p = (dt_iop_filmicrgb_params_t *)self->params;
2688 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
2689 dt_iop_filmic_rgb_compute_spline(p, &g->spline);
2690
2691 // Cache the graph objects to avoid recomputing all the view at each redraw
2692 gtk_widget_get_allocation(widget, &g->allocation);
2693
2694 cairo_surface_t *cst =
2695 dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, g->allocation.width, g->allocation.height);
2696 PangoFontDescription *desc =
2697 pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
2698 cairo_t *cr = cairo_create(cst);
2699 PangoLayout *layout = pango_cairo_create_layout(cr);
2700
2701 pango_layout_set_font_description(layout, desc);
2702 pango_cairo_context_set_resolution(pango_layout_get_context(layout), darktable.gui->dpi);
2703 g->context = gtk_widget_get_style_context(widget);
2704
2705 char text[256];
2706
2707 // reduce a bit the font size
2708 const gint font_size = pango_font_description_get_size(desc);
2709 pango_font_description_set_size(desc, 0.95 * font_size);
2710 pango_layout_set_font_description(layout, desc);
2711
2712 // Get the text line height for spacing
2713 g_strlcpy(text, "X", sizeof(text));
2714 pango_layout_set_text(layout, text, -1);
2715 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
2716 g->line_height = g->ink.height;
2717
2718 // Get the width of a minus sign for legend labels spacing
2719 g_strlcpy(text, "-", sizeof(text));
2720 pango_layout_set_text(layout, text, -1);
2721 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
2722 g->sign_width = g->ink.width / 2.0;
2723
2724 // Get the width of a zero for legend labels spacing
2725 g_strlcpy(text, "0", sizeof(text));
2726 pango_layout_set_text(layout, text, -1);
2727 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
2728 g->zero_width = g->ink.width;
2729
2730 // Set the sizes, margins and paddings
2731 g->inner_padding = DT_PIXEL_APPLY_DPI(4); // TODO: INNER_PADDING value as defined in bauhaus.c macros, sync them
2732 g->inset = g->inner_padding;
2733
2734 float margin_left;
2735 float margin_bottom;
2736 if(g->gui_show_labels)
2737 {
2738 // leave room for labels
2739 margin_left = 3. * g->zero_width + 2. * g->inset;
2740 margin_bottom = 2. * g->line_height + 4. * g->inset;
2741 }
2742 else
2743 {
2744 margin_left = g->inset;
2745 margin_bottom = g->inset;
2746 }
2747
2748 const float margin_top = 2. * g->line_height + g->inset;
2749 const float margin_right = darktable.bauhaus->quad_width + 2. * g->inset;
2750
2751 g->graph_width = g->allocation.width - margin_right - margin_left; // align the right border on sliders
2752 g->graph_height = g->allocation.height - margin_bottom - margin_top; // give room to nodes
2753
2754 gtk_render_background(g->context, cr, 0, 0, g->allocation.width, g->allocation.height);
2755
2756 // Init icons bounds and cache them for mouse events
2757 for(int i = 0; i < DT_FILMIC_GUI_BUTTON_LAST; i++)
2758 {
2759 // put the buttons in the right margin and increment vertical position
2760 g->buttons[i].right = g->allocation.width;
2761 g->buttons[i].left = g->buttons[i].right - darktable.bauhaus->quad_width;
2762 g->buttons[i].top = margin_top + i * (g->inset + darktable.bauhaus->quad_width);
2763 g->buttons[i].bottom = g->buttons[i].top + darktable.bauhaus->quad_width;
2764 g->buttons[i].w = g->buttons[i].right - g->buttons[i].left;
2765 g->buttons[i].h = g->buttons[i].bottom - g->buttons[i].top;
2766 g->buttons[i].state = GTK_STATE_FLAG_NORMAL;
2767 }
2768
2769 g->gui_sizes_inited = TRUE;
2770
2771 g->buttons[0].icon = dtgtk_cairo_paint_refresh;
2772 g->buttons[1].icon = dtgtk_cairo_paint_text_label;
2773
2774 if(g->gui_hover)
2775 {
2776 for(int i = 0; i < DT_FILMIC_GUI_BUTTON_LAST; i++) filmic_gui_draw_icon(cr, &g->buttons[i], g);
2777 }
2778
2779 const float grey = p->grey_point_source / 100.f;
2780 const float DR = p->white_point_source - p->black_point_source;
2781
2782 // set the graph as the origin of the coordinates
2783 cairo_translate(cr, margin_left, margin_top);
2784
2785 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
2786
2787 // write the graph legend at GUI default size
2788 pango_font_description_set_size(desc, font_size);
2789 pango_layout_set_font_description(layout, desc);
2790 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
2791 g_strlcpy(text, _("look only"), sizeof(text));
2792 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
2793 g_strlcpy(text, _("look + mapping (lin)"), sizeof(text));
2794 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
2795 g_strlcpy(text, _("look + mapping (log)"), sizeof(text));
2796 else if(g->gui_mode == DT_FILMIC_GUI_RANGES)
2797 g_strlcpy(text, _("dynamic range mapping"), sizeof(text));
2798
2799 pango_layout_set_text(layout, text, -1);
2800 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
2801
2802 // legend background
2803 set_color(cr, darktable.bauhaus->graph_bg);
2804 cairo_rectangle(cr, g->allocation.width - margin_left - g->ink.width - g->ink.x - 2. * g->inset,
2805 -g->line_height - g->inset - 0.5 * g->ink.height - g->ink.y - g->inset,
2806 g->ink.width + 3. * g->inset, g->ink.height + 2. * g->inset);
2807 cairo_fill(cr);
2808
2809 // legend text
2810 set_color(cr, darktable.bauhaus->graph_fg);
2811 cairo_move_to(cr, g->allocation.width - margin_left - g->ink.width - g->ink.x - g->inset,
2812 -g->line_height - g->inset - 0.5 * g->ink.height - g->ink.y);
2813 pango_cairo_show_layout(cr, layout);
2814 cairo_stroke(cr);
2815
2816 // reduce font size for the rest of the graph
2817 pango_font_description_set_size(desc, 0.95 * font_size);
2818 pango_layout_set_font_description(layout, desc);
2819
2820 if(g->gui_mode != DT_FILMIC_GUI_RANGES)
2821 {
2822 // Draw graph background then border
2823 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(0.5));
2824 cairo_rectangle(cr, 0, 0, g->graph_width, g->graph_height);
2825 set_color(cr, darktable.bauhaus->graph_bg);
2826 cairo_fill_preserve(cr);
2827 set_color(cr, darktable.bauhaus->graph_border);
2828 cairo_stroke(cr);
2829
2830 // draw grid
2831 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(0.5));
2832 set_color(cr, darktable.bauhaus->graph_border);
2833
2834 // we need to tweak the coordinates system to match dt_draw_grid expectations
2835 cairo_save(cr);
2836 cairo_scale(cr, 1., -1.);
2837 cairo_translate(cr, 0., -g->graph_height);
2838
2839 if(g->gui_mode == DT_FILMIC_GUI_LOOK || g->gui_mode == DT_FILMIC_GUI_BASECURVE)
2840 dt_draw_grid(cr, 4, 0, 0, g->graph_width, g->graph_height);
2841 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
2842 dt_draw_loglog_grid(cr, 4, 0, 0, g->graph_width, g->graph_height, LOGBASE);
2843
2844 // reset coordinates
2845 cairo_restore(cr);
2846
2847 // draw identity line
2848 cairo_move_to(cr, 0, g->graph_height);
2849 cairo_line_to(cr, g->graph_width, 0);
2850 cairo_stroke(cr);
2851
2852 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
2853
2854 // Draw the saturation curve
2855 const float saturation = (2.0f * p->saturation / 100.0f + 1.0f);
2856 const float sigma_toe = powf(g->spline.latitude_min / 3.0f, 2.0f);
2857 const float sigma_shoulder = powf((1.0f - g->spline.latitude_max) / 3.0f, 2.0f);
2858
2859 cairo_set_source_rgb(cr, .5, .5, .5);
2860
2861 // prevent graph overflowing
2862 cairo_save(cr);
2863 cairo_rectangle(cr, -DT_PIXEL_APPLY_DPI(2.), -DT_PIXEL_APPLY_DPI(2.),
2864 g->graph_width + 2. * DT_PIXEL_APPLY_DPI(2.), g->graph_height + 2. * DT_PIXEL_APPLY_DPI(2.));
2865 cairo_clip(cr);
2866
2867 if(p->version == DT_FILMIC_COLORSCIENCE_V1)
2868 {
2869 cairo_move_to(cr, 0,
2870 g->graph_height * (1.0 - filmic_desaturate_v1(0.0f, sigma_toe, sigma_shoulder, saturation)));
2871 for(int k = 1; k < 256; k++)
2872 {
2873 float x = k / 255.0;
2874 const float y = filmic_desaturate_v1(x, sigma_toe, sigma_shoulder, saturation);
2875
2876 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
2877 x = exp_tonemapping_v2(x, grey, p->black_point_source, DR);
2878 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
2879 x = dt_log_scale_axis(exp_tonemapping_v2(x, grey, p->black_point_source, DR), LOGBASE);
2880
2881 cairo_line_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
2882 }
2883 }
2884 else if(p->version == DT_FILMIC_COLORSCIENCE_V2 || p->version == DT_FILMIC_COLORSCIENCE_V3)
2885 {
2886 cairo_move_to(cr, 0,
2887 g->graph_height * (1.0 - filmic_desaturate_v2(0.0f, sigma_toe, sigma_shoulder, saturation)));
2888 for(int k = 1; k < 256; k++)
2889 {
2890 float x = k / 255.0;
2891 const float y = filmic_desaturate_v2(x, sigma_toe, sigma_shoulder, saturation);
2892
2893 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
2894 x = exp_tonemapping_v2(x, grey, p->black_point_source, DR);
2895 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
2896 x = dt_log_scale_axis(exp_tonemapping_v2(x, grey, p->black_point_source, DR), LOGBASE);
2897
2898 cairo_line_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
2899 }
2900 }
2901 cairo_stroke(cr);
2902
2903 // draw the tone curve
2904 float x_start = 0.f;
2905 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
2906 x_start = log_tonemapping_v2(x_start, grey, p->black_point_source, DR);
2907
2908 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG) x_start = dt_log_scale_axis(x_start, LOGBASE);
2909
2910 float y_start = clamp_simd(filmic_spline(x_start, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
2911 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type));
2912
2913 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
2914 y_start = powf(y_start, p->output_power);
2915 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
2916 y_start = dt_log_scale_axis(powf(y_start, p->output_power), LOGBASE);
2917
2918 cairo_move_to(cr, 0, g->graph_height * (1.0 - y_start));
2919
2920 for(int k = 1; k < 256; k++)
2921 {
2922 // k / 255 step defines a linearly scaled space. This might produce large gaps in lowlights when using log
2923 // GUI scaling so we non-linearly rescale that step to get more points in lowlights
2924 float x = powf(k / 255.0f, 2.4f);
2925 float value = x;
2926
2927 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
2928 value = log_tonemapping_v2(x, grey, p->black_point_source, DR);
2929
2930 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG) x = dt_log_scale_axis(x, LOGBASE);
2931
2932 float y = filmic_spline(value, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4, g->spline.M5,
2933 g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
2934
2935 if(y > g->spline.y[4])
2936 {
2937 y = fminf(y, 1.0f);
2938 cairo_set_source_rgb(cr, 0.75, .5, 0.);
2939 }
2940 else if(y < g->spline.y[0])
2941 {
2942 y = fmaxf(y, 0.f);
2943 cairo_set_source_rgb(cr, 0.75, .5, 0.);
2944 }
2945 else
2946 {
2947 set_color(cr, darktable.bauhaus->graph_fg);
2948 }
2949
2950 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
2951 y = powf(y, p->output_power);
2952 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
2953 y = dt_log_scale_axis(powf(y, p->output_power), LOGBASE);
2954
2955 cairo_line_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
2956 cairo_stroke(cr);
2957 cairo_move_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
2958 }
2959
2960 cairo_restore(cr);
2961
2962 // draw nodes
2963
2964 // special case for the grey node
2965 cairo_save(cr);
2966 cairo_rectangle(cr, -DT_PIXEL_APPLY_DPI(4.), -DT_PIXEL_APPLY_DPI(4.),
2967 g->graph_width + 2. * DT_PIXEL_APPLY_DPI(4.), g->graph_height + 2. * DT_PIXEL_APPLY_DPI(4.));
2968 cairo_clip(cr);
2969 float x_grey = g->spline.x[2];
2970 float y_grey = g->spline.y[2];
2971
2972 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
2973 {
2974 x_grey = exp_tonemapping_v2(x_grey, grey, p->black_point_source, DR);
2975 y_grey = powf(y_grey, p->output_power);
2976 }
2977 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
2978 {
2979 x_grey = dt_log_scale_axis(exp_tonemapping_v2(x_grey, grey, p->black_point_source, DR), LOGBASE);
2980 y_grey = dt_log_scale_axis(powf(y_grey, p->output_power), LOGBASE);
2981 }
2982
2983 cairo_set_source_rgb(cr, 0.75, 0.5, 0.0);
2984 cairo_arc(cr, x_grey * g->graph_width, (1.0 - y_grey) * g->graph_height, DT_PIXEL_APPLY_DPI(6), 0,
2985 2. * M_PI);
2986 cairo_fill(cr);
2987 cairo_stroke(cr);
2988
2989 // latitude nodes
2990 float x_black = 0.f;
2991 float y_black = 0.f;
2992
2993 float x_white = 1.f;
2994 float y_white = 1.f;
2995
2996 set_color(cr, darktable.bauhaus->graph_fg);
2997 for(int k = 0; k < 5; k++)
2998 {
2999 if(k != 2) // k == 2 : grey point, already processed above
3000 {
3001 float x = g->spline.x[k];
3002 float y = g->spline.y[k];
3003
3004 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
3005 {
3006 x = exp_tonemapping_v2(x, grey, p->black_point_source, DR);
3007 y = powf(y, p->output_power);
3008 }
3009 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3010 {
3011 x = dt_log_scale_axis(exp_tonemapping_v2(x, grey, p->black_point_source, DR), LOGBASE);
3012 y = dt_log_scale_axis(powf(y, p->output_power), LOGBASE);
3013 }
3014
3015 // save the bounds of the curve to mark the axis graduation
3016 if(k == 0) // black point
3017 {
3018 x_black = x;
3019 y_black = y;
3020 }
3021 else if(k == 4) // white point
3022 {
3023 x_white = x;
3024 y_white = y;
3025 }
3026
3027 // draw bullet
3028 cairo_arc(cr, x * g->graph_width, (1.0 - y) * g->graph_height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
3029 cairo_fill(cr);
3030 cairo_stroke(cr);
3031 }
3032 }
3033 cairo_restore(cr);
3034
3035 if(g->gui_show_labels)
3036 {
3037 // position of the upper bound of x axis labels
3038 const float x_legend_top = g->graph_height + 0.5 * g->line_height;
3039
3040 // mark the y axis graduation at grey spot
3041 set_color(cr, darktable.bauhaus->graph_fg);
3042 snprintf(text, sizeof(text), "%.0f", p->grey_point_target);
3043 pango_layout_set_text(layout, text, -1);
3044 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3045 cairo_move_to(cr, -2. * g->inset - g->ink.width - g->ink.x,
3046 (1.0 - y_grey) * g->graph_height - 0.5 * g->ink.height - g->ink.y);
3047 pango_cairo_show_layout(cr, layout);
3048 cairo_stroke(cr);
3049
3050 // mark the x axis graduation at grey spot
3051 set_color(cr, darktable.bauhaus->graph_fg);
3052 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3053 snprintf(text, sizeof(text), "%+.1f", 0.f);
3054 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3055 snprintf(text, sizeof(text), "%.0f", p->grey_point_source);
3056
3057 pango_layout_set_text(layout, text, -1);
3058 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3059 cairo_move_to(cr, x_grey * g->graph_width - 0.5 * g->ink.width - g->ink.x, x_legend_top);
3060 pango_cairo_show_layout(cr, layout);
3061 cairo_stroke(cr);
3062
3063 // mark the y axis graduation at black spot
3064 set_color(cr, darktable.bauhaus->graph_fg);
3065 snprintf(text, sizeof(text), "%.0f", p->black_point_target);
3066 pango_layout_set_text(layout, text, -1);
3067 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3068 cairo_move_to(cr, -2. * g->inset - g->ink.width - g->ink.x,
3069 (1.0 - y_black) * g->graph_height - 0.5 * g->ink.height - g->ink.y);
3070 pango_cairo_show_layout(cr, layout);
3071 cairo_stroke(cr);
3072
3073 // mark the y axis graduation at black spot
3074 set_color(cr, darktable.bauhaus->graph_fg);
3075 snprintf(text, sizeof(text), "%.0f", p->white_point_target);
3076 pango_layout_set_text(layout, text, -1);
3077 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3078 cairo_move_to(cr, -2. * g->inset - g->ink.width - g->ink.x,
3079 (1.0 - y_white) * g->graph_height - 0.5 * g->ink.height - g->ink.y);
3080 pango_cairo_show_layout(cr, layout);
3081 cairo_stroke(cr);
3082
3083 // mark the x axis graduation at black spot
3084 set_color(cr, darktable.bauhaus->graph_fg);
3085 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3086 snprintf(text, sizeof(text), "%+.1f", p->black_point_source);
3087 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3088 snprintf(text, sizeof(text), "%.0f", exp2f(p->black_point_source) * p->grey_point_source);
3089
3090 pango_layout_set_text(layout, text, -1);
3091 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3092 cairo_move_to(cr, x_black * g->graph_width - 0.5 * g->ink.width - g->ink.x, x_legend_top);
3093 pango_cairo_show_layout(cr, layout);
3094 cairo_stroke(cr);
3095
3096 // mark the x axis graduation at white spot
3097 set_color(cr, darktable.bauhaus->graph_fg);
3098 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3099 snprintf(text, sizeof(text), "%+.1f", p->white_point_source);
3100 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3101 {
3102 if(x_white > 1.f)
3103 snprintf(text, sizeof(text), "%.0f →", 100.f); // this marks the bound of the graph, not the actual white
3104 else
3105 snprintf(text, sizeof(text), "%.0f", exp2f(p->white_point_source) * p->grey_point_source);
3106 }
3107
3108 pango_layout_set_text(layout, text, -1);
3109 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3110 cairo_move_to(cr,
3111 fminf(x_white, 1.f) * g->graph_width - 0.5 * g->ink.width - g->ink.x
3112 + 2. * (x_white > 1.f) * g->sign_width,
3113 x_legend_top);
3114 pango_cairo_show_layout(cr, layout);
3115 cairo_stroke(cr);
3116
3117 // handle the case where white > 100 %, so the node is out of the graph.
3118 // we still want to display the value to get a hint.
3119 set_color(cr, darktable.bauhaus->graph_fg);
3120 if((g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG) && (x_white > 1.f))
3121 {
3122 // set to italic font
3123 PangoStyle backup = pango_font_description_get_style(desc);
3124 pango_font_description_set_style(desc, PANGO_STYLE_ITALIC);
3125 pango_layout_set_font_description(layout, desc);
3126
3127 snprintf(text, sizeof(text), _("(%.0f %%)"), exp2f(p->white_point_source) * p->grey_point_source);
3128 pango_layout_set_text(layout, text, -1);
3129 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3130 cairo_move_to(cr, g->allocation.width - g->ink.width - g->ink.x - margin_left,
3131 g->graph_height + 3. * g->inset + g->line_height - g->ink.y);
3132 pango_cairo_show_layout(cr, layout);
3133 cairo_stroke(cr);
3134
3135 // restore font
3136 pango_font_description_set_style(desc, backup);
3137 pango_layout_set_font_description(layout, desc);
3138 }
3139
3140 // mark the y axis legend
3141 set_color(cr, darktable.bauhaus->graph_fg);
3142 /* xgettext:no-c-format */
3143 g_strlcpy(text, _("% display"), sizeof(text));
3144 pango_layout_set_text(layout, text, -1);
3145 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3146 cairo_move_to(cr, -2. * g->inset - g->zero_width - g->ink.x,
3147 -g->line_height - g->inset - 0.5 * g->ink.height - g->ink.y);
3148 pango_cairo_show_layout(cr, layout);
3149 cairo_stroke(cr);
3150
3151 // mark the x axis legend
3152 set_color(cr, darktable.bauhaus->graph_fg);
3153 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3154 g_strlcpy(text, _("EV scene"), sizeof(text));
3155 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3156 {
3157 /* xgettext:no-c-format */
3158 g_strlcpy(text, _("% camera"), sizeof(text));
3159 }
3160 pango_layout_set_text(layout, text, -1);
3161 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3162 cairo_move_to(cr, 0.5 * g->graph_width - 0.5 * g->ink.width - g->ink.x,
3163 g->graph_height + 3. * g->inset + g->line_height - g->ink.y);
3164 pango_cairo_show_layout(cr, layout);
3165 cairo_stroke(cr);
3166 }
3167 }
3168 else
3169 {
3170 // mode ranges
3171 cairo_identity_matrix(cr); // reset coordinates
3172
3173 // draw the dynamic range of display
3174 // if white = 100%, assume -11.69 EV because of uint8 output + sRGB OETF.
3175 // for uint10 output, white should be set to 400%, so anything above 100% increases DR
3176 // FIXME : if darktable becomes HDR-10bits compatible (for output), this needs to be updated
3177 const float display_DR = 12.f + log2f(p->white_point_target / 100.f);
3178
3179 const float y_display = g->allocation.height / 3.f + g->line_height;
3180 const float y_scene = 2. * g->allocation.height / 3.f + g->line_height;
3181
3182 const float display_top = y_display - g->line_height / 2;
3183 const float display_bottom = display_top + g->line_height;
3184
3185 const float scene_top = y_scene - g->line_height / 2;
3186 const float scene_bottom = scene_top + g->line_height;
3187
3188 float column_left;
3189
3190 if(g->gui_show_labels)
3191 {
3192 // labels
3193 set_color(cr, darktable.bauhaus->graph_fg);
3194 g_strlcpy(text, _("display"), sizeof(text));
3195 pango_layout_set_text(layout, text, -1);
3196 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3197 cairo_move_to(cr, 0., y_display - 0.5 * g->ink.height - g->ink.y);
3198 pango_cairo_show_layout(cr, layout);
3199 cairo_stroke(cr);
3200 const float display_label_width = g->ink.width;
3201
3202 // axis legend
3203 g_strlcpy(text, _("(%)"), sizeof(text));
3204 pango_layout_set_text(layout, text, -1);
3205 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3206 cairo_move_to(cr, 0.5 * display_label_width - 0.5 * g->ink.width - g->ink.x,
3207 display_top - 4. * g->inset - g->ink.height - g->ink.y);
3208 pango_cairo_show_layout(cr, layout);
3209 cairo_stroke(cr);
3210
3211 set_color(cr, darktable.bauhaus->graph_fg);
3212 g_strlcpy(text, _("scene"), sizeof(text));
3213 pango_layout_set_text(layout, text, -1);
3214 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3215 cairo_move_to(cr, 0., y_scene - 0.5 * g->ink.height - g->ink.y);
3216 pango_cairo_show_layout(cr, layout);
3217 cairo_stroke(cr);
3218 const float scene_label_width = g->ink.width;
3219
3220 // axis legend
3221 g_strlcpy(text, _("(EV)"), sizeof(text));
3222 pango_layout_set_text(layout, text, -1);
3223 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3224 cairo_move_to(cr, 0.5 * scene_label_width - 0.5 * g->ink.width - g->ink.x,
3225 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
3226 pango_cairo_show_layout(cr, layout);
3227 cairo_stroke(cr);
3228
3229 // arrow between labels
3230 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
3231 dt_cairo_draw_arrow(cr, fminf(scene_label_width, display_label_width) / 2.f, y_scene - g->line_height,
3232 fminf(scene_label_width, display_label_width) / 2.f,
3233 y_display + g->line_height + g->inset, TRUE);
3234
3235 column_left = fmaxf(display_label_width, scene_label_width) + g->inset;
3236 }
3237 else
3238 column_left = darktable.bauhaus->quad_width;
3239
3240 const float column_right = g->allocation.width - column_left - darktable.bauhaus->quad_width;
3241
3242 // compute dynamic ranges left and right to middle grey
3243 const float display_HL_EV = -log2f(p->grey_point_target / p->white_point_target); // compared to white EV
3244 const float display_LL_EV = display_DR - display_HL_EV; // compared to black EV
3245 const float display_real_black_EV
3246 = -fmaxf(log2f(p->black_point_target / p->grey_point_target),
3247 -11.685887601778058f + display_HL_EV - log2f(p->white_point_target / 100.f));
3248 const float scene_HL_EV = p->white_point_source; // compared to white EV
3249 const float scene_LL_EV = -p->black_point_source; // compared to black EV
3250
3251 // compute the max width needed to fit both dynamic ranges and derivate the unit size of a GUI EV
3252 const float max_DR = ceilf(fmaxf(display_HL_EV, scene_HL_EV)) + ceilf(fmaxf(display_LL_EV, scene_LL_EV));
3253 const float EV = (column_right) / max_DR;
3254
3255 // all greys are aligned vertically in GUI since they are the fulcrum of the transform
3256 // so, get their coordinates
3257 const float grey_EV = fmaxf(ceilf(display_HL_EV), ceilf(scene_HL_EV));
3258 const float grey_x = g->allocation.width - (grey_EV)*EV - darktable.bauhaus->quad_width;
3259
3260 // similarly, get black/white coordinates from grey point
3261 const float display_black_x = grey_x - display_real_black_EV * EV;
3262 const float display_DR_start_x = grey_x - display_LL_EV * EV;
3263 const float display_white_x = grey_x + display_HL_EV * EV;
3264
3265 const float scene_black_x = grey_x - scene_LL_EV * EV;
3266 const float scene_white_x = grey_x + scene_HL_EV * EV;
3267 const float scene_lat_bottom = grey_x + (g->spline.x[1] - g->spline.x[2]) * EV * DR;
3268 const float scene_lat_top = grey_x + (g->spline.x[3] - g->spline.x[2]) * EV * DR;
3269
3270 // show EV zones for display - zones are aligned on 0% and 100%
3271 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
3272
3273 // latitude bounds - show contrast expansion
3274
3275 // Compute usual filmic mapping
3276 float display_lat_bottom = filmic_spline(g->spline.latitude_min, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
3277 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
3278 display_lat_bottom = powf(fmaxf(display_lat_bottom, NORM_MIN), p->output_power); // clamp at -16 EV
3279
3280 // rescale output to log scale
3281 display_lat_bottom = log2f(display_lat_bottom/ (p->grey_point_target / 100.f));
3282
3283 // take clamping into account
3284 if(display_lat_bottom < 0.f) // clamp to - 8 EV (black)
3285 display_lat_bottom = fmaxf(display_lat_bottom, -display_real_black_EV);
3286 else if(display_lat_bottom > 0.f) // clamp to 0 EV (white)
3287 display_lat_bottom = fminf(display_lat_bottom, display_HL_EV);
3288
3289 // get destination coordinate
3290 display_lat_bottom = grey_x + display_lat_bottom * EV;
3291
3292 // Compute usual filmic mapping
3293 float display_lat_top = filmic_spline(g->spline.latitude_max, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
3294 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
3295 display_lat_top = powf(fmaxf(display_lat_top, NORM_MIN), p->output_power); // clamp at -16 EV
3296
3297 // rescale output to log scale
3298 display_lat_top = log2f(display_lat_top / (p->grey_point_target / 100.f));
3299
3300 // take clamping into account
3301 if(display_lat_top < 0.f) // clamp to - 8 EV (black)
3302 display_lat_top = fmaxf(display_lat_top, -display_real_black_EV);
3303 else if(display_lat_top > 0.f) // clamp to 0 EV (white)
3304 display_lat_top = fminf(display_lat_top, display_HL_EV);
3305
3306 // get destination coordinate and draw
3307 display_lat_top = grey_x + display_lat_top * EV;
3308
3309 cairo_move_to(cr, scene_lat_bottom, scene_top);
3310 cairo_line_to(cr, scene_lat_top, scene_top);
3311 cairo_line_to(cr, display_lat_top, display_bottom);
3312 cairo_line_to(cr, display_lat_bottom, display_bottom);
3313 cairo_line_to(cr, scene_lat_bottom, scene_top);
3314 set_color(cr, darktable.bauhaus->graph_bg);
3315 cairo_fill(cr);
3316
3317 for(int i = 0; i < (int)ceilf(display_DR); i++)
3318 {
3319 // content
3320 const float shade = powf(exp2f(-11.f + (float)i), 1.f / 2.4f);
3321 cairo_set_source_rgb(cr, shade, shade, shade);
3322 cairo_rectangle(cr, display_DR_start_x + i * EV, display_top, EV, g->line_height);
3323 cairo_fill_preserve(cr);
3324
3325 // borders
3326 cairo_set_source_rgb(cr, 0.75, .5, 0.);
3327 cairo_stroke(cr);
3328 }
3329
3330 // middle grey display
3331 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
3332 cairo_move_to(cr, grey_x, display_bottom + 2. * g->inset);
3333 cairo_line_to(cr, grey_x, display_top - 2. * g->inset);
3334 cairo_stroke(cr);
3335
3336 // show EV zones for scene - zones are aligned on grey
3337
3338 for(int i = floorf(p->black_point_source); i < ceilf(p->white_point_source); i++)
3339 {
3340 // content
3341 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
3342 const float shade = powf(0.1845f * exp2f((float)i), 1.f / 2.4f);
3343 const float x_temp = grey_x + i * EV;
3344 cairo_set_source_rgb(cr, shade, shade, shade);
3345 cairo_rectangle(cr, x_temp, scene_top, EV, g->line_height);
3346 cairo_fill_preserve(cr);
3347
3348 // borders
3349 cairo_set_source_rgb(cr, 0.75, .5, 0.);
3350 cairo_stroke(cr);
3351
3352 // arrows
3353 if(i == 0)
3354 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
3355 else
3356 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
3357
3358 if((float)i > p->black_point_source && (float)i < p->white_point_source)
3359 {
3360 // Compute usual filmic mapping
3361 const float normal_value = ((float)i - p->black_point_source) / DR;
3362 float y_temp = filmic_spline(normal_value, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
3363 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
3364 y_temp = powf(fmaxf(y_temp, NORM_MIN), p->output_power); // clamp at -16 EV
3365
3366 // rescale output to log scale
3367 y_temp = log2f(y_temp / (p->grey_point_target / 100.f));
3368
3369 // take clamping into account
3370 if(y_temp < 0.f) // clamp to - 8 EV (black)
3371 y_temp = fmaxf(y_temp, -display_real_black_EV);
3372 else if(y_temp > 0.f) // clamp to 0 EV (white)
3373 y_temp = fminf(y_temp, display_HL_EV);
3374
3375 // get destination coordinate and draw
3376 y_temp = grey_x + y_temp * EV;
3377 dt_cairo_draw_arrow(cr, x_temp, scene_top, y_temp, display_bottom, FALSE);
3378 }
3379 }
3380
3381 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
3382
3383 // arrows for black and white
3384 float x_temp = grey_x + p->black_point_source * EV;
3385 float y_temp = grey_x - display_real_black_EV * EV;
3386 dt_cairo_draw_arrow(cr, x_temp, scene_top, y_temp, display_bottom, FALSE);
3387
3388 x_temp = grey_x + p->white_point_source * EV;
3389 y_temp = grey_x + display_HL_EV * EV;
3390 dt_cairo_draw_arrow(cr, x_temp, scene_top, y_temp, display_bottom, FALSE);
3391
3392 // draw white - grey - black ticks
3393
3394 // black display
3395 cairo_move_to(cr, display_black_x, display_bottom);
3396 cairo_line_to(cr, display_black_x, display_top - 2. * g->inset);
3397 cairo_stroke(cr);
3398
3399 // middle grey display
3400 cairo_move_to(cr, grey_x, display_bottom);
3401 cairo_line_to(cr, grey_x, display_top - 2. * g->inset);
3402 cairo_stroke(cr);
3403
3404 // white display
3405 cairo_move_to(cr, display_white_x, display_bottom);
3406 cairo_line_to(cr, display_white_x, display_top - 2. * g->inset);
3407 cairo_stroke(cr);
3408
3409 // black scene
3410 cairo_move_to(cr, scene_black_x, scene_bottom + 2. * g->inset);
3411 cairo_line_to(cr, scene_black_x, scene_top);
3412 cairo_stroke(cr);
3413
3414 // middle grey scene
3415 cairo_move_to(cr, grey_x, scene_bottom + 2. * g->inset);
3416 cairo_line_to(cr, grey_x, scene_top);
3417 cairo_stroke(cr);
3418
3419 // white scene
3420 cairo_move_to(cr, scene_white_x, scene_bottom + 2. * g->inset);
3421 cairo_line_to(cr, scene_white_x, scene_top);
3422 cairo_stroke(cr);
3423
3424 // legends
3425 set_color(cr, darktable.bauhaus->graph_fg);
3426
3427 // black scene legend
3428 snprintf(text, sizeof(text), "%+.1f", p->black_point_source);
3429 pango_layout_set_text(layout, text, -1);
3430 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3431 cairo_move_to(cr, scene_black_x - 0.5 * g->ink.width - g->ink.x,
3432 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
3433 pango_cairo_show_layout(cr, layout);
3434 cairo_stroke(cr);
3435
3436 // grey scene legend
3437 snprintf(text, sizeof(text), "%+.1f", 0.f);
3438 pango_layout_set_text(layout, text, -1);
3439 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3440 cairo_move_to(cr, grey_x - 0.5 * g->ink.width - g->ink.x,
3441 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
3442 pango_cairo_show_layout(cr, layout);
3443 cairo_stroke(cr);
3444
3445 // white scene legend
3446 snprintf(text, sizeof(text), "%+.1f", p->white_point_source);
3447 pango_layout_set_text(layout, text, -1);
3448 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3449 cairo_move_to(cr, scene_white_x - 0.5 * g->ink.width - g->ink.x,
3450 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
3451 pango_cairo_show_layout(cr, layout);
3452 cairo_stroke(cr);
3453
3454 // black scene legend
3455 snprintf(text, sizeof(text), "%.0f", p->black_point_target);
3456 pango_layout_set_text(layout, text, -1);
3457 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3458 cairo_move_to(cr, display_black_x - 0.5 * g->ink.width - g->ink.x,
3459 display_top - 4. * g->inset - g->ink.height - g->ink.y);
3460 pango_cairo_show_layout(cr, layout);
3461 cairo_stroke(cr);
3462
3463 // grey scene legend
3464 snprintf(text, sizeof(text), "%.0f", p->grey_point_target);
3465 pango_layout_set_text(layout, text, -1);
3466 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3467 cairo_move_to(cr, grey_x - 0.5 * g->ink.width - g->ink.x,
3468 display_top - 4. * g->inset - g->ink.height - g->ink.y);
3469 pango_cairo_show_layout(cr, layout);
3470 cairo_stroke(cr);
3471
3472 // white scene legend
3473 snprintf(text, sizeof(text), "%.0f", p->white_point_target);
3474 pango_layout_set_text(layout, text, -1);
3475 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3476 cairo_move_to(cr, display_white_x - 0.5 * g->ink.width - g->ink.x,
3477 display_top - 4. * g->inset - g->ink.height - g->ink.y);
3478 pango_cairo_show_layout(cr, layout);
3479 cairo_stroke(cr);
3480 }
3481
3482 // restore font size
3483 pango_font_description_set_size(desc, font_size);
3484 pango_layout_set_font_description(layout, desc);
3485
3486 cairo_destroy(cr);
3487 cairo_set_source_surface(crf, cst, 0, 0);
3488 cairo_paint(crf);
3489 cairo_surface_destroy(cst);
3490 g_object_unref(layout);
3491 pango_font_description_free(desc);
3492 return TRUE;
3493 }
3494
area_button_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)3495 static gboolean area_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
3496 {
3497 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3498 if(darktable.gui->reset) return TRUE;
3499
3500 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
3501
3502 dt_iop_request_focus(self);
3503
3504 if(g->active_button != DT_FILMIC_GUI_BUTTON_LAST)
3505 {
3506
3507 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
3508 {
3509 // double click resets view
3510 if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
3511 {
3512 g->gui_mode = DT_FILMIC_GUI_LOOK;
3513 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3514 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_view", g->gui_mode);
3515 return TRUE;
3516 }
3517 else
3518 {
3519 return FALSE;
3520 }
3521 }
3522 else if(event->button == 1)
3523 {
3524 // simple left click cycles through modes in positive direction
3525 if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
3526 {
3527 // cycle type of graph
3528 if(g->gui_mode == DT_FILMIC_GUI_RANGES)
3529 g->gui_mode = DT_FILMIC_GUI_LOOK;
3530 else
3531 g->gui_mode++;
3532
3533 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3534 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_view", g->gui_mode);
3535 return TRUE;
3536 }
3537 else if(g->active_button == DT_FILMIC_GUI_BUTTON_LABELS)
3538 {
3539 g->gui_show_labels = !g->gui_show_labels;
3540 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3541 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_show_labels", g->gui_show_labels);
3542 return TRUE;
3543 }
3544 else
3545 {
3546 // we should never get there since (g->active_button != DT_FILMIC_GUI_BUTTON_LAST)
3547 // and any other case has been processed above.
3548 return FALSE;
3549 }
3550 }
3551 else if(event->button == 3)
3552 {
3553 // simple right click cycles through modes in negative direction
3554 if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
3555 {
3556 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3557 g->gui_mode = DT_FILMIC_GUI_RANGES;
3558 else
3559 g->gui_mode--;
3560
3561 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3562 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_view", g->gui_mode);
3563 return TRUE;
3564 }
3565 else if(g->active_button == DT_FILMIC_GUI_BUTTON_LABELS)
3566 {
3567 g->gui_show_labels = !g->gui_show_labels;
3568 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3569 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_show_labels", g->gui_show_labels);
3570 return TRUE;
3571 }
3572 else
3573 {
3574 return FALSE;
3575 }
3576 }
3577 }
3578
3579 return FALSE;
3580 }
3581
area_enter_notify(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)3582 static gboolean area_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
3583 {
3584 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3585 if(darktable.gui->reset) return 1;
3586 if(!self->enabled) return 0;
3587
3588 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
3589 g->gui_hover = TRUE;
3590 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3591 return TRUE;
3592 }
3593
3594
area_leave_notify(GtkWidget * widget,GdkEventCrossing * event,gpointer user_data)3595 static gboolean area_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
3596 {
3597 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3598 if(darktable.gui->reset) return 1;
3599 if(!self->enabled) return 0;
3600
3601 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
3602 g->gui_hover = FALSE;
3603 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3604 return TRUE;
3605 }
3606
area_motion_notify(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)3607 static gboolean area_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
3608 {
3609 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3610 if(darktable.gui->reset) return 1;
3611
3612 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
3613 if(!g->gui_sizes_inited) return FALSE;
3614
3615 // get in-widget coordinates
3616 const float y = event->y;
3617 const float x = event->x;
3618
3619 if(x > 0. && x < g->allocation.width && y > 0. && y < g->allocation.height) g->gui_hover = TRUE;
3620
3621 gint save_active_button = g->active_button;
3622
3623 if(g->gui_hover)
3624 {
3625 // find out which button is under the mouse
3626 gint found_something = FALSE;
3627 for(int i = 0; i < DT_FILMIC_GUI_BUTTON_LAST; i++)
3628 {
3629 // check if mouse in in the button's bounds
3630 if(x > g->buttons[i].left && x < g->buttons[i].right && y > g->buttons[i].top && y < g->buttons[i].bottom)
3631 {
3632 // yeah, mouse is over that button
3633 g->buttons[i].mouse_hover = TRUE;
3634 g->active_button = i;
3635 found_something = TRUE;
3636 }
3637 else
3638 {
3639 // no luck with this button
3640 g->buttons[i].mouse_hover = FALSE;
3641 }
3642 }
3643
3644 if(!found_something) g->active_button = DT_FILMIC_GUI_BUTTON_LAST; // mouse is over no known button
3645
3646 // update the tooltips
3647 if(g->active_button == DT_FILMIC_GUI_BUTTON_LAST && x < g->buttons[0].left)
3648 {
3649 // we are over the graph area
3650 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("use the parameters below to set the nodes.\n"
3651 "the bright curve is the filmic tone mapping curve\n"
3652 "the dark curve is the desaturation curve."));
3653 }
3654 else if(g->active_button == DT_FILMIC_GUI_BUTTON_LABELS)
3655 {
3656 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("toggle axis labels and values display."));
3657 }
3658 else if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
3659 {
3660 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("cycle through graph views.\n"
3661 "left click: cycle forward.\n"
3662 "right click: cycle backward.\n"
3663 "double click: reset to look view."));
3664 }
3665 else
3666 {
3667 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), "");
3668 }
3669
3670 if(save_active_button != g->active_button) gtk_widget_queue_draw(GTK_WIDGET(g->area));
3671 return TRUE;
3672 }
3673 else
3674 {
3675 g->active_button = DT_FILMIC_GUI_BUTTON_LAST;
3676 if(save_active_button != g->active_button) (GTK_WIDGET(g->area));
3677 return FALSE;
3678 }
3679 }
3680
area_scroll_callback(GtkWidget * widget,GdkEventScroll * event,gpointer user_data)3681 static gboolean area_scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
3682 {
3683 if(dt_gui_ignore_scroll(event)) return FALSE;
3684
3685 if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
3686 {
3687 int delta_y;
3688 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
3689 {
3690 //adjust aspect
3691 const int aspect = dt_conf_get_int("plugins/darkroom/filmicrgb/aspect_percent");
3692 dt_conf_set_int("plugins/darkroom/filmicrgb/aspect_percent", aspect + delta_y);
3693 dtgtk_drawing_area_set_aspect_ratio(widget, aspect / 100.0);
3694 }
3695 return TRUE; // Ensure that scrolling cannot move side panel when no delta
3696 }
3697 return FALSE;
3698 }
3699
gui_init(dt_iop_module_t * self)3700 void gui_init(dt_iop_module_t *self)
3701 {
3702 dt_iop_filmicrgb_gui_data_t *g = IOP_GUI_ALLOC(filmicrgb);
3703
3704 g->show_mask = FALSE;
3705 g->gui_mode = DT_FILMIC_GUI_LOOK;
3706 g->gui_show_labels = TRUE;
3707 g->gui_hover = FALSE;
3708 g->gui_sizes_inited = FALSE;
3709
3710 // don't make the area square to safe some vertical space -- it's not interactive anyway
3711 const float aspect = dt_conf_get_int("plugins/darkroom/filmicrgb/aspect_percent") / 100.0;
3712 g->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(aspect));
3713
3714 gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
3715 gtk_widget_add_events(GTK_WIDGET(g->area), GDK_BUTTON_PRESS_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
3716 | GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask);
3717 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(dt_iop_tonecurve_draw), self);
3718 g_signal_connect(G_OBJECT(g->area), "button-press-event", G_CALLBACK(area_button_press), self);
3719 g_signal_connect(G_OBJECT(g->area), "leave-notify-event", G_CALLBACK(area_leave_notify), self);
3720 g_signal_connect(G_OBJECT(g->area), "enter-notify-event", G_CALLBACK(area_enter_notify), self);
3721 g_signal_connect(G_OBJECT(g->area), "motion-notify-event", G_CALLBACK(area_motion_notify), self);
3722 g_signal_connect(G_OBJECT(g->area), "scroll-event", G_CALLBACK(area_scroll_callback), self);
3723
3724 // Init GTK notebook
3725 g->notebook = GTK_NOTEBOOK(gtk_notebook_new());
3726
3727 // Page SCENE
3728 self->widget = dt_ui_notebook_page(g->notebook, _("scene"), NULL);
3729
3730 g->grey_point_source
3731 = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "grey_point_source"));
3732 dt_bauhaus_slider_set_soft_range(g->grey_point_source, .1, 36.0);
3733 dt_bauhaus_slider_set_format(g->grey_point_source, "%.2f %%");
3734 gtk_widget_set_tooltip_text(g->grey_point_source,
3735 _("adjust to match the average luminance of the image's subject.\n"
3736 "the value entered here will then be remapped to 18.45%.\n"
3737 "decrease the value to increase the overall brightness."));
3738
3739 // White slider
3740 g->white_point_source
3741 = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "white_point_source"));
3742 dt_bauhaus_slider_set_soft_range(g->white_point_source, 2.0, 8.0);
3743 dt_bauhaus_slider_set_format(g->white_point_source, _("%+.2f EV"));
3744 gtk_widget_set_tooltip_text(g->white_point_source,
3745 _("number of stops between middle gray and pure white.\n"
3746 "this is a reading a lightmeter would give you on the scene.\n"
3747 "adjust so highlights clipping is avoided"));
3748
3749 // Black slider
3750 g->black_point_source
3751 = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "black_point_source"));
3752 dt_bauhaus_slider_set_soft_range(g->black_point_source, -14.0, -3);
3753 dt_bauhaus_slider_set_format(g->black_point_source, _("%+.2f EV"));
3754 gtk_widget_set_tooltip_text(
3755 g->black_point_source, _("number of stops between middle gray and pure black.\n"
3756 "this is a reading a lightmeter would give you on the scene.\n"
3757 "increase to get more contrast.\ndecrease to recover more details in low-lights."));
3758
3759 // Dynamic range scaling
3760 g->security_factor = dt_bauhaus_slider_from_params(self, "security_factor");
3761 dt_bauhaus_slider_set_soft_max(g->security_factor, 50);
3762 dt_bauhaus_slider_set_format(g->security_factor, "%+.2f %%");
3763 gtk_widget_set_tooltip_text(g->security_factor, _("symmetrically enlarge or shrink the computed dynamic range.\n"
3764 "useful to give a safety margin to extreme luminances."));
3765
3766 // Auto tune slider
3767 g->auto_button = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_combobox_new(self));
3768 dt_bauhaus_widget_set_label(g->auto_button, NULL, N_("auto tune levels"));
3769 gtk_widget_set_tooltip_text(g->auto_button, _("try to optimize the settings with some statistical assumptions.\n"
3770 "this will fit the luminance range inside the histogram bounds.\n"
3771 "works better for landscapes and evenly-lit pictures\n"
3772 "but fails for high-keys, low-keys and high-ISO pictures.\n"
3773 "this is not an artificial intelligence, but a simple guess.\n"
3774 "ensure you understand its assumptions before using it."));
3775 gtk_box_pack_start(GTK_BOX(self->widget), g->auto_button, FALSE, FALSE, 0);
3776
3777 // Page RECONSTRUCT
3778 self->widget = dt_ui_notebook_page(g->notebook, _("reconstruct"), NULL);
3779
3780 GtkWidget *label = dt_ui_section_label_new(_("highlights clipping"));
3781 GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(label));
3782 gtk_style_context_add_class(context, "section_label_top");
3783 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
3784
3785 g->reconstruct_threshold = dt_bauhaus_slider_from_params(self, "reconstruct_threshold");
3786 dt_bauhaus_slider_set_format(g->reconstruct_threshold, _("%+.2f EV"));
3787 gtk_widget_set_tooltip_text(g->reconstruct_threshold,
3788 _("set the exposure threshold upon which\n"
3789 "clipped highlights get reconstructed.\n"
3790 "values are relative to the scene white point.\n"
3791 "0 EV means the threshold is the same as the scene white point.\n"
3792 "decrease to include more areas,\n"
3793 "increase to exclude more areas."));
3794
3795 g->reconstruct_feather = dt_bauhaus_slider_from_params(self, "reconstruct_feather");
3796 dt_bauhaus_slider_set_format(g->reconstruct_feather, _("%+.2f EV"));
3797 gtk_widget_set_tooltip_text(g->reconstruct_feather,
3798 _("soften the transition between clipped highlights and valid pixels.\n"
3799 "decrease to make the transition harder and sharper,\n"
3800 "increase to make the transition softer and blurrier."));
3801
3802 // Highlight Reconstruction Mask
3803 g->show_highlight_mask = dt_bauhaus_combobox_new(self);
3804 dt_bauhaus_widget_set_label(g->show_highlight_mask, NULL, N_("display highlight reconstruction mask"));
3805 dt_bauhaus_widget_set_quad_paint(g->show_highlight_mask, dtgtk_cairo_paint_showmask,
3806 CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL);
3807 dt_bauhaus_widget_set_quad_toggle(g->show_highlight_mask, TRUE);
3808 g_signal_connect(G_OBJECT(g->show_highlight_mask), "quad-pressed", G_CALLBACK(show_mask_callback), self);
3809 gtk_box_pack_start(GTK_BOX(self->widget), g->show_highlight_mask, FALSE, FALSE, 0);
3810
3811 label = dt_ui_section_label_new(_("balance"));
3812 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
3813
3814 g->reconstruct_structure_vs_texture = dt_bauhaus_slider_from_params(self, "reconstruct_structure_vs_texture");
3815 dt_bauhaus_slider_set_step(g->reconstruct_structure_vs_texture, 0.1);
3816 dt_bauhaus_slider_set_format(g->reconstruct_structure_vs_texture, "%.2f %%");
3817 gtk_widget_set_tooltip_text(g->reconstruct_structure_vs_texture,
3818 /* xgettext:no-c-format */
3819 _("decide which reconstruction strategy to favor,\n"
3820 "between inpainting a smooth color gradient,\n"
3821 "or trying to recover the textured details.\n"
3822 "0% is an equal mix of both.\n"
3823 "increase if at least one RGB channel is not clipped.\n"
3824 "decrease if all RGB channels are clipped over large areas."));
3825
3826 g->reconstruct_bloom_vs_details = dt_bauhaus_slider_from_params(self, "reconstruct_bloom_vs_details");
3827 dt_bauhaus_slider_set_step(g->reconstruct_bloom_vs_details, 0.1);
3828 dt_bauhaus_slider_set_format(g->reconstruct_bloom_vs_details, "%.2f %%");
3829 gtk_widget_set_tooltip_text(g->reconstruct_bloom_vs_details,
3830 /* xgettext:no-c-format */
3831 _("decide which reconstruction strategy to favor,\n"
3832 "between blooming highlights like film does,\n"
3833 "or trying to recover sharp details.\n"
3834 "0% is an equal mix of both.\n"
3835 "increase if you want more details.\n"
3836 "decrease if you want more blur."));
3837
3838 // Bloom threshold
3839 g->reconstruct_grey_vs_color = dt_bauhaus_slider_from_params(self, "reconstruct_grey_vs_color");
3840 dt_bauhaus_slider_set_step(g->reconstruct_grey_vs_color, 0.1);
3841 dt_bauhaus_slider_set_format(g->reconstruct_grey_vs_color, "%.2f %%");
3842 gtk_widget_set_tooltip_text(g->reconstruct_grey_vs_color,
3843 /* xgettext:no-c-format */
3844 _("decide which reconstruction strategy to favor,\n"
3845 "between recovering monochromatic highlights,\n"
3846 "or trying to recover colorful highlights.\n"
3847 "0% is an equal mix of both.\n"
3848 "increase if you want more color.\n"
3849 "decrease if you see magenta or out-of-gamut highlights."));
3850
3851 // Page LOOK
3852 self->widget = dt_ui_notebook_page(g->notebook, _("look"), NULL);
3853
3854 g->contrast = dt_bauhaus_slider_from_params(self, N_("contrast"));
3855 dt_bauhaus_slider_set_soft_range(g->contrast, 1.0, 2.0);
3856 dt_bauhaus_slider_set_digits(g->contrast, 3);
3857 dt_bauhaus_slider_set_step(g->contrast, .01);
3858 gtk_widget_set_tooltip_text(g->contrast, _("slope of the linear part of the curve\n"
3859 "affects mostly the mid-tones"));
3860
3861 // brightness slider
3862 g->output_power = dt_bauhaus_slider_from_params(self, "output_power");
3863 gtk_widget_set_tooltip_text(g->output_power, _("equivalent to paper grade in analog.\n"
3864 "increase to make highlights brighter and less compressed.\n"
3865 "decrease to mute highlights."));
3866
3867 g->latitude = dt_bauhaus_slider_from_params(self, N_("latitude"));
3868 dt_bauhaus_slider_set_soft_range(g->latitude, 5.0, 50.0);
3869 dt_bauhaus_slider_set_format(g->latitude, "%.2f %%");
3870 gtk_widget_set_tooltip_text(g->latitude,
3871 _("width of the linear domain in the middle of the curve,\n"
3872 "in percent of the dynamic range (white exposure - black exposure).\n"
3873 "increase to get more contrast and less desaturation at extreme luminances,\n"
3874 "decrease otherwise. no desaturation happens in the latitude range.\n"
3875 "this has no effect on mid-tones."));
3876
3877 g->balance = dt_bauhaus_slider_from_params(self, "balance");
3878 dt_bauhaus_slider_set_format(g->balance, "%.2f %%");
3879 gtk_widget_set_tooltip_text(g->balance, _("slides the latitude along the slope\n"
3880 "to give more room to shadows or highlights.\n"
3881 "use it if you need to protect the details\n"
3882 "at one extremity of the histogram."));
3883
3884 g->saturation = dt_bauhaus_slider_from_params(self, "saturation");
3885 dt_bauhaus_slider_set_soft_max(g->saturation, 50.0);
3886 dt_bauhaus_slider_set_format(g->saturation, "%.2f %%");
3887 gtk_widget_set_tooltip_text(g->saturation, _("desaturates the output of the module\n"
3888 "specifically at extreme luminances.\n"
3889 "increase if shadows and/or highlights are under-saturated."));
3890
3891 // Page DISPLAY
3892 self->widget = dt_ui_notebook_page(g->notebook, _("display"), NULL);
3893
3894 // Black slider
3895 g->black_point_target = dt_bauhaus_slider_from_params(self, "black_point_target");
3896 dt_bauhaus_slider_set_step(g->black_point_target, .001);
3897 dt_bauhaus_slider_set_digits(g->black_point_target, 4);
3898 dt_bauhaus_slider_set_format(g->black_point_target, "%.4f %%");
3899 gtk_widget_set_tooltip_text(g->black_point_target, _("luminance of output pure black, "
3900 "this should be 0%\nexcept if you want a faded look"));
3901
3902 g->grey_point_target = dt_bauhaus_slider_from_params(self, "grey_point_target");
3903 dt_bauhaus_slider_set_step(g->grey_point_target, .01);
3904 dt_bauhaus_slider_set_digits(g->grey_point_target, 4);
3905 dt_bauhaus_slider_set_format(g->grey_point_target, "%.4f %%");
3906 gtk_widget_set_tooltip_text(g->grey_point_target,
3907 _("middle gray value of the target display or color space.\n"
3908 "you should never touch that unless you know what you are doing."));
3909
3910 g->white_point_target = dt_bauhaus_slider_from_params(self, "white_point_target");
3911 dt_bauhaus_slider_set_soft_max(g->white_point_target, 100.0);
3912 dt_bauhaus_slider_set_step(g->white_point_target, .01);
3913 dt_bauhaus_slider_set_digits(g->white_point_target, 4);
3914 dt_bauhaus_slider_set_format(g->white_point_target, "%.4f %%");
3915 gtk_widget_set_tooltip_text(g->white_point_target, _("luminance of output pure white, "
3916 "this should be 100%\nexcept if you want a faded look"));
3917
3918 // Page OPTIONS
3919 self->widget = dt_ui_notebook_page(g->notebook, _("options"), NULL);
3920
3921 // Color science
3922 g->version = dt_bauhaus_combobox_from_params(self, "version");
3923 gtk_widget_set_tooltip_text(g->version,
3924 _("v3 is darktable 3.0 desaturation method, same as color balance.\n"
3925 "v4 is a newer desaturation method, based on spectral purity of light."));
3926
3927 g->preserve_color = dt_bauhaus_combobox_from_params(self, "preserve_color");
3928 gtk_widget_set_tooltip_text(g->preserve_color, _("ensure the original color are preserved.\n"
3929 "may reinforce chromatic aberrations and chroma noise,\n"
3930 "so ensure they are properly corrected elsewhere.\n"));
3931
3932 // Curve type
3933 g->highlights = dt_bauhaus_combobox_from_params(self, "highlights");
3934 gtk_widget_set_tooltip_text(g->highlights, _("choose the desired curvature of the filmic spline in highlights.\n"
3935 "hard uses a high curvature resulting in more tonal compression.\n"
3936 "soft uses a low curvature resulting in less tonal compression."));
3937
3938 g->shadows = dt_bauhaus_combobox_from_params(self, "shadows");
3939 gtk_widget_set_tooltip_text(g->shadows, _("choose the desired curvature of the filmic spline in shadows.\n"
3940 "hard uses a high curvature resulting in more tonal compression.\n"
3941 "soft uses a low curvature resulting in less tonal compression."));
3942
3943 g->custom_grey = dt_bauhaus_toggle_from_params(self, "custom_grey");
3944 gtk_widget_set_tooltip_text(g->custom_grey, _("enable to input custom middle-gray values.\n"
3945 "this is not recommended in general.\n"
3946 "fix the global exposure in the exposure module instead.\n"
3947 "disable to use standard 18.45 %% middle gray."));
3948
3949 g->auto_hardness = dt_bauhaus_toggle_from_params(self, "auto_hardness");
3950 gtk_widget_set_tooltip_text(
3951 g->auto_hardness, _("enable to auto-set the look hardness depending on the scene white and black points.\n"
3952 "this keeps the middle gray on the identity line and improves fast tuning.\n"
3953 "disable if you want a manual control."));
3954
3955 g->high_quality_reconstruction = dt_bauhaus_slider_from_params(self, "high_quality_reconstruction");
3956 gtk_widget_set_tooltip_text(g->high_quality_reconstruction,
3957 _("run extra passes of chromaticity reconstruction.\n"
3958 "more iterations means more color propagation from neighbourhood.\n"
3959 "this will be slower but will yield more neutral highlights.\n"
3960 "it also helps with difficult cases of magenta highlights."));
3961
3962 // Highlight noise
3963 g->noise_level = dt_bauhaus_slider_from_params(self, "noise_level");
3964 gtk_widget_set_tooltip_text(g->noise_level, _("add statistical noise in reconstructed highlights.\n"
3965 "this avoids highlights to look too smooth\n"
3966 "when the picture is noisy overall,\n"
3967 "so they blend with the rest of the picture."));
3968
3969 // Noise distribution
3970 g->noise_distribution = dt_bauhaus_combobox_from_params(self, "noise_distribution");
3971 dt_bauhaus_combobox_add(g->noise_distribution, _("uniform"));
3972 dt_bauhaus_combobox_add(g->noise_distribution, _("gaussian"));
3973 dt_bauhaus_combobox_add(g->noise_distribution, _("poissonian"));
3974 gtk_widget_set_tooltip_text(g->noise_distribution, _("choose the statistical distribution of noise.\n"
3975 "this is useful to match natural sensor noise pattern.\n"));
3976
3977 // start building top level widget
3978 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
3979
3980 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->area), TRUE, TRUE, 0);
3981 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->notebook), FALSE, FALSE, 0);
3982 }
3983
gui_changed(dt_iop_module_t * self,GtkWidget * w,void * previous)3984 void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
3985 {
3986 dt_iop_filmicrgb_params_t *p = (dt_iop_filmicrgb_params_t *)self->params;
3987 dt_iop_filmicrgb_gui_data_t *g = (dt_iop_filmicrgb_gui_data_t *)self->gui_data;
3988
3989 if(!w || w == g->auto_hardness || w == g->security_factor || w == g->grey_point_source
3990 || w == g->black_point_source || w == g->white_point_source)
3991 {
3992 ++darktable.gui->reset;
3993
3994 if(w == g->security_factor || w == g->grey_point_source)
3995 {
3996 float prev = *(float *)previous;
3997 if(w == g->security_factor)
3998 {
3999 float ratio = (p->security_factor - prev) / (prev + 100.0f);
4000
4001 float EVmin = p->black_point_source;
4002 EVmin = EVmin + ratio * EVmin;
4003
4004 float EVmax = p->white_point_source;
4005 EVmax = EVmax + ratio * EVmax;
4006
4007 p->white_point_source = EVmax;
4008 p->black_point_source = EVmin;
4009 }
4010 else
4011 {
4012 float grey_var = log2f(prev / p->grey_point_source);
4013 p->black_point_source = p->black_point_source - grey_var;
4014 p->white_point_source = p->white_point_source + grey_var;
4015 }
4016
4017 dt_bauhaus_slider_set_soft(g->white_point_source, p->white_point_source);
4018 dt_bauhaus_slider_set_soft(g->black_point_source, p->black_point_source);
4019 }
4020
4021 if(p->auto_hardness)
4022 p->output_power = logf(p->grey_point_target / 100.0f)
4023 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
4024
4025 gtk_widget_set_visible(GTK_WIDGET(g->output_power), !p->auto_hardness);
4026 dt_bauhaus_slider_set_soft(g->output_power, p->output_power);
4027
4028 --darktable.gui->reset;
4029 }
4030
4031 if(!w || w == g->version)
4032 {
4033 if(p->version == DT_FILMIC_COLORSCIENCE_V1)
4034 dt_bauhaus_widget_set_label(g->saturation, NULL, N_("extreme luminance saturation"));
4035 else if(p->version == DT_FILMIC_COLORSCIENCE_V2 || p->version == DT_FILMIC_COLORSCIENCE_V3)
4036 dt_bauhaus_widget_set_label(g->saturation, NULL, N_("middle tones saturation"));
4037 }
4038
4039 if(!w || w == g->reconstruct_bloom_vs_details)
4040 {
4041 if(p->reconstruct_bloom_vs_details == -100.f)
4042 {
4043 // user disabled the reconstruction in favor of full blooming
4044 // so the structure vs. texture setting doesn't make any difference
4045 // make it insensitive to not confuse users
4046 gtk_widget_set_sensitive(g->reconstruct_structure_vs_texture, FALSE);
4047 }
4048 else
4049 {
4050 gtk_widget_set_sensitive(g->reconstruct_structure_vs_texture, TRUE);
4051 }
4052 }
4053
4054 if(!w || w == g->custom_grey)
4055 {
4056 gtk_widget_set_visible(g->grey_point_source, p->custom_grey);
4057 gtk_widget_set_visible(g->grey_point_target, p->custom_grey);
4058 }
4059
4060 if(w) gtk_widget_queue_draw(self->widget);
4061 }
4062
4063 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
4064 // vim: shiftwidth=2 expandtab tabstop=2 cindent
4065 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
4066