Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
filmicrgb.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2019-2022 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"
24#include "common/darktable.h"
25#include "common/bspline.h"
26#include "common/dwt.h"
27#include "common/image.h"
28#include "common/iop_profile.h"
29#include "common/opencl.h"
30#include "control/control.h"
31#include "develop/develop.h"
32#include "develop/imageop_gui.h"
36#include "develop/tiling.h"
37#include "dtgtk/button.h"
38#include "dtgtk/drawingarea.h"
39#include "dtgtk/expander.h"
40#include "dtgtk/paint.h"
41
43#include "gui/gtk.h"
44#include "gui/presets.h"
46#include "iop/iop_api.h"
47
48
49#include "develop/imageop.h"
50#include "gui/draw.h"
51
52#include <assert.h>
53#include <math.h>
54#include <stdlib.h>
55#include <string.h>
56#include <time.h>
57
58#define INVERSE_SQRT_3 0.5773502691896258f
59#define SAFETY_MARGIN 0.01f
60
61#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(1)
62
63
65
66
101#if defined(__GNUC__)
102#pragma GCC optimize("unroll-loops", "tree-loop-if-convert", "tree-loop-distribution", "no-strict-aliasing", \
103 "loop-interchange", "loop-nest-optimize", "tree-loop-im", "unswitch-loops", \
104 "tree-loop-ivcanon", "ira-loop-pressure", "split-ivs-in-unroller", \
105 "variable-expansion-in-unroller", "split-loops", "ivopts", "predictive-commoning", \
106 "tree-loop-linear", "loop-block", "loop-strip-mine", "finite-math-only", "fp-contract=fast", \
107 "fast-math", "no-math-errno")
108#endif
109
110
112{
113 DT_FILMIC_METHOD_NONE = 0, // $DESCRIPTION: "no"
114 DT_FILMIC_METHOD_MAX_RGB = 1, // $DESCRIPTION: "max RGB"
115 DT_FILMIC_METHOD_LUMINANCE = 2, // $DESCRIPTION: "luminance Y"
116 DT_FILMIC_METHOD_POWER_NORM = 3, // $DESCRIPTION: "RGB power norm"
117 DT_FILMIC_METHOD_EUCLIDEAN_NORM_V2 = 5, // $DESCRIPTION: "RGB euclidean norm"
118 DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1 = 4, // $DESCRIPTION: "RGB euclidean norm (legacy)"
120
121
123{
124 DT_FILMIC_CURVE_POLY_4 = 0, // $DESCRIPTION: "hard"
125 DT_FILMIC_CURVE_POLY_3 = 1, // $DESCRIPTION: "soft"
126 DT_FILMIC_CURVE_RATIONAL = 2, // $DESCRIPTION: "safe"
128
129
131{
132 DT_FILMIC_COLORSCIENCE_V1 = 0, // $DESCRIPTION: "v3 (2019)"
133 DT_FILMIC_COLORSCIENCE_V2 = 1, // $DESCRIPTION: "v4 (2020)"
134 DT_FILMIC_COLORSCIENCE_V3 = 2, // $DESCRIPTION: "v5 (2021)"
135 DT_FILMIC_COLORSCIENCE_V4 = 3, // $DESCRIPTION: "v6 (2022)"
136 DT_FILMIC_COLORSCIENCE_V5 = 4, // $DESCRIPTION: "v7 (2023)"
138
140{
141 DT_FILMIC_SPLINE_VERSION_V1 = 0, // $DESCRIPTION: "v1 (2019)"
142 DT_FILMIC_SPLINE_VERSION_V2 = 1, // $DESCRIPTION: "v2 (2020)"
143 DT_FILMIC_SPLINE_VERSION_V3 = 2, // $DESCRIPTION: "v3 (2021)"
145
151
152
154{
155 dt_aligned_pixel_t M1, M2, M3, M4, M5; // factors for the interpolation polynom
156 float latitude_min, latitude_max; // bounds of the latitude == linear part by design
157 float y[5]; // controls nodes
158 float x[5]; // controls nodes
161
162
164{
165 DT_FILMIC_GUI_LOOK = 0, // default GUI, showing only the contrast curve in a log/gamma space
166 DT_FILMIC_GUI_BASECURVE = 1, // basecurve-like GUI, showing the contrast and brightness curves, in lin/lin space
167 DT_FILMIC_GUI_BASECURVE_LOG = 2, // same as previous, but log-scaled
168 DT_FILMIC_GUI_RANGES = 3, // zone-system-like GUI, showing the range to range mapping
171
172// copy enum definition for introspection
179
180// clang-format off
182{
183 float grey_point_source; // $MIN: 0 $MAX: 100 $DEFAULT: 18.45 $DESCRIPTION: "middle gray luminance"
184 float black_point_source; // $MIN: -16 $MAX: -0.1 $DEFAULT: -8.0 $DESCRIPTION: "black relative exposure"
185 float white_point_source; // $MIN: 0.1 $MAX: 16 $DEFAULT: 4.0 $DESCRIPTION: "white relative exposure"
186 float reconstruct_threshold; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "threshold"
187 float reconstruct_feather; // $MIN: 0.25 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "transition"
188 float reconstruct_bloom_vs_details; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "bloom \342\206\224 reconstruct"
189 float reconstruct_grey_vs_color; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "gray \342\206\224 colorful details"
190 float reconstruct_structure_vs_texture; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "structure \342\206\224 texture"
191 float security_factor; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "dynamic range scaling"
192 float grey_point_target; // $MIN: 1 $MAX: 50 $DEFAULT: 18.45 $DESCRIPTION: "target middle gray"
193 float black_point_target; // $MIN: 0.000 $MAX: 20.000 $DEFAULT: 0.01517634 $DESCRIPTION: "target black luminance"
194 float white_point_target; // $MIN: 0 $MAX: 1600 $DEFAULT: 100 $DESCRIPTION: "target white luminance"
195 float output_power; // $MIN: 1 $MAX: 10 $DEFAULT: 4.0 $DESCRIPTION: "hardness"
196 float latitude; // $MIN: 0.01 $MAX: 99 $DEFAULT: 33.0
197 float contrast; // $MIN: 0 $MAX: 5 $DEFAULT: 1.18
198 float saturation; // $MIN: -200 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "extreme luminance saturation"
199 float balance; // $MIN: -50 $MAX: 50 $DEFAULT: 0.0 $DESCRIPTION: "shadows \342\206\224 highlights balance"
200 float noise_level; // $MIN: 0.0 $MAX: 6.0 $DEFAULT: 0.05f $DESCRIPTION: "add noise in highlights"
201 dt_iop_filmicrgb_methods_type_t preserve_color; // $DEFAULT: DT_FILMIC_METHOD_MAX_RGB $DESCRIPTION: "preserve chrominance"
202 dt_iop_filmicrgb_colorscience_type_t version; // $DEFAULT: DT_FILMIC_COLORSCIENCE_V5 $DESCRIPTION: "color science"
203 gboolean auto_hardness; // $DEFAULT: TRUE $DESCRIPTION: "auto adjust hardness"
204 gboolean custom_grey; // $DEFAULT: FALSE $DESCRIPTION: "use custom middle-gray values"
205 int high_quality_reconstruction; // $MIN: 0 $MAX: 10 $DEFAULT: 1 $DESCRIPTION: "iterations of color inpainting"
206 dt_iop_filmic_noise_distribution_t noise_distribution; // $DEFAULT: DT_NOISE_POISSONIAN $DESCRIPTION: "type of noise"
207 dt_iop_filmicrgb_curve_type_t shadows; // $DEFAULT: DT_FILMIC_CURVE_RATIONAL $DESCRIPTION: "contrast in shadows"
208 dt_iop_filmicrgb_curve_type_t highlights; // $DEFAULT: DT_FILMIC_CURVE_RATIONAL $DESCRIPTION: "contrast in highlights"
209 gboolean compensate_icc_black; // $DEFAULT: FALSE $DESCRIPTION: "compensate output ICC profile black point"
210 dt_iop_filmicrgb_spline_version_type_t spline_version; // $DEFAULT: DT_FILMIC_SPLINE_VERSION_V3 $DESCRIPTION: "spline handling"
212// clang-format on
213
214
215// custom buttons in graph views
222
223// custom buttons in graph views - data
225{
226 // coordinates in GUI - compute them only in the drawing function
227 float left;
228 float right;
229 float top;
230 float bottom;
231 float w;
232 float h;
233
234 // properties
235 gint mouse_hover; // whether it should be acted on / mouse is over it
236 GtkStateFlags state;
237
238 // icon drawing, function as set in dtgtk/paint.h
240
242
243
245{
252 GtkWidget *security_factor;
253 GtkWidget *auto_button;
257 GtkWidget *output_power;
258 GtkWidget *latitude;
259 GtkWidget *contrast;
260 GtkWidget *saturation;
261 GtkWidget *balance;
262 GtkWidget *preserve_color;
264 GtkWidget *shadows, *highlights;
265 GtkWidget *version;
266 GtkWidget *auto_hardness;
267 GtkWidget *custom_grey;
271 GtkNotebook *notebook;
272 GtkDrawingArea *area;
279 dt_iop_filmicrgb_gui_button_t active_button; // ID of the button under cursor
281
282 // Cache Pango and Cairo stuff for the equalizer drawing
288 int inset;
289
290 GtkAllocation allocation;
291 PangoRectangle ink;
292 GtkStyleContext *context;
294
320
321
337
338
339const char *name()
340{
341 return _("fil_mic rgb");
342}
343
344const char *aliases()
345{
346 return _("tone mapping|curve|view transform|contrast|saturation|highlights");
347}
348
349const char **description(struct dt_iop_module_t *self)
350{
351 return dt_iop_set_description(self, _("apply a view transform to prepare the scene-referred pipeline\n"
352 "for display on SDR screens and paper prints\n"
353 "while preventing clipping in non-destructive ways"),
354 _("corrective and creative"),
355 _("linear or non-linear, RGB, scene-referred"),
356 _("non-linear, RGB"),
357 _("non-linear, RGB, display-referred"));
358}
359
361{
362 return IOP_GROUP_TONES;
363}
364
369
374
375inline static gboolean dt_iop_filmic_rgb_compute_spline(const dt_iop_filmicrgb_params_t *const p,
376 struct dt_iop_filmic_rgb_spline_t *const spline);
377
378// convert parameters from spline v1 or v2 to spline v3
380{
382 return;
383
386
387 // from the spline, compute new values for contrast, balance, and latitude to update spline_version to v3
388 float grey_log = spline.x[2];
389 float toe_log = fminf(spline.x[1], grey_log);
390 float shoulder_log = fmaxf(spline.x[3], grey_log);
391 float black_display = spline.y[0];
392 float grey_display = spline.y[2];
393 float white_display = spline.y[4];
394 const float scaled_safety_margin = SAFETY_MARGIN * (white_display - black_display);
395 float toe_display = fminf(spline.y[1], grey_display);
396 float shoulder_display = fmaxf(spline.y[3], grey_display);
397
398 float hardness = n->output_power;
399 float contrast = (shoulder_display - toe_display) / (shoulder_log - toe_log);
400 // sanitize toe and shoulder, for min and max values, while keeping the same contrast
401 float linear_intercept = grey_display - (contrast * grey_log);
402 if(toe_display < black_display + scaled_safety_margin)
403 {
404 toe_display = black_display + scaled_safety_margin;
405 // compute toe_log to keep same slope
406 toe_log = (toe_display - linear_intercept) / contrast;
407 }
408 if(shoulder_display > white_display - scaled_safety_margin)
409 {
410 shoulder_display = white_display - scaled_safety_margin;
411 // compute shoulder_log to keep same slope
412 shoulder_log = (shoulder_display - linear_intercept) / contrast;
413 }
414 // revert contrast adaptation that will be performed in dt_iop_filmic_rgb_compute_spline
415 contrast *= 8.0f / (n->white_point_source - n->black_point_source);
416 contrast *= hardness * powf(grey_display, hardness-1.0f);
417 // latitude is the % of the segment [b+safety*(w-b),w-safety*(w-b)] which is covered, where b is black_display and w white_display
418 const float latitude = CLAMP((shoulder_display - toe_display) / ((white_display - black_display) - 2.0f * scaled_safety_margin), 0.0f, 0.99f);
419 // find balance
420 float toe_display_ref = latitude * (black_display + scaled_safety_margin) + (1.0f - latitude) * grey_display;
421 float shoulder_display_ref = latitude * (white_display - scaled_safety_margin) + (1.0f - latitude) * grey_display;
422 float balance;
423 if(shoulder_display < shoulder_display_ref)
424 balance = 0.5f * (1.0f - fmaxf(shoulder_display - grey_display, 0.0f) / fmaxf(shoulder_display_ref - grey_display, 1E-5f));
425 else
426 balance = -0.5f * (1.0f - fmaxf(grey_display - toe_display, 0.0f) / fmaxf(grey_display - toe_display_ref, 1E-5f));
427
429 {
430 // black and white point need to be updated as well,
431 // as code path for v3 will raise them to power 1.0f / hardness,
432 // while code path for v1 did not.
433 n->black_point_target = powf(black_display, hardness) * 100.0f;
434 n->white_point_target = powf(white_display, hardness) * 100.0f;
435 }
436 n->latitude = latitude * 100.0f;
437 n->contrast = contrast;
438 n->balance = balance * 100.0f;
440}
441
442int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
443 const int new_version)
444{
445 if(old_version == 1 && new_version == 5)
446 {
447 typedef struct dt_iop_filmicrgb_params_v1_t
448 {
449 float grey_point_source;
450 float black_point_source;
451 float white_point_source;
452 float security_factor;
453 float grey_point_target;
454 float black_point_target;
455 float white_point_target;
456 float output_power;
457 float latitude;
458 float contrast;
459 float saturation;
460 float balance;
461 int preserve_color;
462 } dt_iop_filmicrgb_params_v1_t;
463
464 dt_iop_filmicrgb_params_v1_t *o = (dt_iop_filmicrgb_params_v1_t *)old_params;
467
468 *n = *d; // start with a fresh copy of default parameters
469
470 n->grey_point_source = o->grey_point_source;
471 n->white_point_source = o->white_point_source;
472 n->black_point_source = o->black_point_source;
473 n->security_factor = o->security_factor;
474 n->grey_point_target = o->grey_point_target;
475 n->black_point_target = o->black_point_target;
476 n->white_point_target = o->white_point_target;
477 n->output_power = o->output_power;
478 n->latitude = o->latitude;
479 n->contrast = o->contrast;
480 n->saturation = o->saturation;
481 n->balance = o->balance;
482 n->preserve_color = o->preserve_color;
486 = 6.0f; // for old edits, this ensures clipping threshold >> white level, so it's a no-op
490 n->reconstruct_feather = 3.0f;
492 n->auto_hardness = TRUE;
493 n->custom_grey = TRUE;
496 n->noise_level = 0.f;
500 return 0;
501 }
502 if(old_version == 2 && new_version == 5)
503 {
504 typedef struct dt_iop_filmicrgb_params_v2_t
505 {
506 float grey_point_source;
507 float black_point_source;
508 float white_point_source;
509 float reconstruct_threshold;
510 float reconstruct_feather;
511 float reconstruct_bloom_vs_details;
512 float reconstruct_grey_vs_color;
513 float reconstruct_structure_vs_texture;
514 float security_factor;
515 float grey_point_target;
516 float black_point_target;
517 float white_point_target;
518 float output_power;
519 float latitude;
520 float contrast;
521 float saturation;
522 float balance;
523 int preserve_color;
524 int version;
525 int auto_hardness;
526 int custom_grey;
527 int high_quality_reconstruction;
530 } dt_iop_filmicrgb_params_v2_t;
531
532 dt_iop_filmicrgb_params_v2_t *o = (dt_iop_filmicrgb_params_v2_t *)old_params;
535
536 *n = *d; // start with a fresh copy of default parameters
537
538 n->grey_point_source = o->grey_point_source;
539 n->white_point_source = o->white_point_source;
540 n->black_point_source = o->black_point_source;
541 n->security_factor = o->security_factor;
542 n->grey_point_target = o->grey_point_target;
543 n->black_point_target = o->black_point_target;
544 n->white_point_target = o->white_point_target;
545 n->output_power = o->output_power;
546 n->latitude = o->latitude;
547 n->contrast = o->contrast;
548 n->saturation = o->saturation;
549 n->balance = o->balance;
550 n->preserve_color = o->preserve_color;
551 n->shadows = o->shadows;
552 n->highlights = o->highlights;
553 n->reconstruct_threshold = o->reconstruct_threshold;
554 n->reconstruct_bloom_vs_details = o->reconstruct_bloom_vs_details;
555 n->reconstruct_grey_vs_color = o->reconstruct_grey_vs_color;
556 n->reconstruct_structure_vs_texture = o->reconstruct_structure_vs_texture;
557 n->reconstruct_feather = o->reconstruct_feather;
558 n->version = o->version;
559 n->auto_hardness = o->auto_hardness;
560 n->custom_grey = o->custom_grey;
561 n->high_quality_reconstruction = o->high_quality_reconstruction;
562 n->noise_level = d->noise_level;
564 n->noise_level = 0.f;
568 return 0;
569 }
570 if(old_version == 3 && new_version == 5)
571 {
572 typedef struct dt_iop_filmicrgb_params_v3_t
573 {
574 float grey_point_source; // $MIN: 0 $MAX: 100 $DEFAULT: 18.45 $DESCRIPTION: "middle gray luminance"
575 float black_point_source; // $MIN: -16 $MAX: -0.1 $DEFAULT: -8.0 $DESCRIPTION: "black relative exposure"
576 float white_point_source; // $MIN: 0 $MAX: 16 $DEFAULT: 4.0 $DESCRIPTION: "white relative exposure"
577 float reconstruct_threshold; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: +3.0 $DESCRIPTION: "threshold"
578 float reconstruct_feather; // $MIN: 0.25 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "transition"
579 float reconstruct_bloom_vs_details; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION:
580 // "bloom/reconstruct"
581 float reconstruct_grey_vs_color; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "gray/colorful
582 // details"
583 float reconstruct_structure_vs_texture; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION:
584 // "structure/texture"
585 float security_factor; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "dynamic range scaling"
586 float grey_point_target; // $MIN: 1 $MAX: 50 $DEFAULT: 18.45 $DESCRIPTION: "target middle gray"
587 float black_point_target; // $MIN: 0 $MAX: 20 $DEFAULT: 0 $DESCRIPTION: "target black luminance"
588 float white_point_target; // $MIN: 0 $MAX: 1600 $DEFAULT: 100 $DESCRIPTION: "target white luminance"
589 float output_power; // $MIN: 1 $MAX: 10 $DEFAULT: 4.0 $DESCRIPTION: "hardness"
590 float latitude; // $MIN: 0.01 $MAX: 100 $DEFAULT: 33.0
591 float contrast; // $MIN: 0 $MAX: 5 $DEFAULT: 1.50
592 float saturation; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "extreme luminance saturation"
593 float balance; // $MIN: -50 $MAX: 50 $DEFAULT: 0.0 $DESCRIPTION: "shadows/highlights balance"
594 float noise_level; // $MIN: 0.0 $MAX: 6.0 $DEFAULT: 0.1f $DESCRIPTION: "add noise in highlights"
595 dt_iop_filmicrgb_methods_type_t preserve_color; // $DEFAULT: DT_FILMIC_METHOD_POWER_NORM $DESCRIPTION:
596 // "preserve chrominance"
597 dt_iop_filmicrgb_colorscience_type_t version; // $DEFAULT: DT_FILMIC_COLORSCIENCE_V3 $DESCRIPTION: "color
598 // science"
599 gboolean auto_hardness; // $DEFAULT: TRUE $DESCRIPTION: "auto adjust hardness"
600 gboolean custom_grey; // $DEFAULT: FALSE $DESCRIPTION: "use custom middle-gray values"
601 int high_quality_reconstruction; // $MIN: 0 $MAX: 10 $DEFAULT: 1 $DESCRIPTION: "iterations of high-quality
602 // reconstruction"
603 int noise_distribution; // $DEFAULT: DT_NOISE_POISSONIAN $DESCRIPTION: "type of noise"
604 dt_iop_filmicrgb_curve_type_t shadows; // $DEFAULT: DT_FILMIC_CURVE_POLY_4 $DESCRIPTION: "contrast in shadows"
605 dt_iop_filmicrgb_curve_type_t highlights; // $DEFAULT: DT_FILMIC_CURVE_POLY_4 $DESCRIPTION: "contrast in
606 // highlights"
607 } dt_iop_filmicrgb_params_v3_t;
608
609 dt_iop_filmicrgb_params_v3_t *o = (dt_iop_filmicrgb_params_v3_t *)old_params;
612
613 *n = *d; // start with a fresh copy of default parameters
614
615 n->grey_point_source = o->grey_point_source;
616 n->white_point_source = o->white_point_source;
617 n->black_point_source = o->black_point_source;
618 n->security_factor = o->security_factor;
619 n->grey_point_target = o->grey_point_target;
620 n->black_point_target = o->black_point_target;
621 n->white_point_target = o->white_point_target;
622 n->output_power = o->output_power;
623 n->latitude = o->latitude;
624 n->contrast = o->contrast;
625 n->saturation = o->saturation;
626 n->balance = o->balance;
627 n->preserve_color = o->preserve_color;
628 n->shadows = o->shadows;
629 n->highlights = o->highlights;
630 n->reconstruct_threshold = o->reconstruct_threshold;
631 n->reconstruct_bloom_vs_details = o->reconstruct_bloom_vs_details;
632 n->reconstruct_grey_vs_color = o->reconstruct_grey_vs_color;
633 n->reconstruct_structure_vs_texture = o->reconstruct_structure_vs_texture;
634 n->reconstruct_feather = o->reconstruct_feather;
635 n->version = o->version;
636 n->auto_hardness = o->auto_hardness;
637 n->custom_grey = o->custom_grey;
638 n->high_quality_reconstruction = o->high_quality_reconstruction;
639 n->noise_level = d->noise_level;
641 n->noise_level = d->noise_level;
645 return 0;
646 }
647 if(old_version == 4 && new_version == 5)
648 {
649 typedef struct dt_iop_filmicrgb_params_v4_t
650 {
651 float grey_point_source; // $MIN: 0 $MAX: 100 $DEFAULT: 18.45 $DESCRIPTION: "middle gray luminance"
652 float black_point_source; // $MIN: -16 $MAX: -0.1 $DEFAULT: -8.0 $DESCRIPTION: "black relative exposure"
653 float white_point_source; // $MIN: 0 $MAX: 16 $DEFAULT: 4.0 $DESCRIPTION: "white relative exposure"
654 float reconstruct_threshold; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: +3.0 $DESCRIPTION: "threshold"
655 float reconstruct_feather; // $MIN: 0.25 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "transition"
656 float reconstruct_bloom_vs_details; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "bloom \342\206\224 reconstruct"
657 float reconstruct_grey_vs_color; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "gray \342\206\224 colorful details"
658 float reconstruct_structure_vs_texture; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION: "structure \342\206\224 texture"
659 float security_factor; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "dynamic range scaling"
660 float grey_point_target; // $MIN: 1 $MAX: 50 $DEFAULT: 18.45 $DESCRIPTION: "target middle gray"
661 float black_point_target; // $MIN: 0.000 $MAX: 20.000 $DEFAULT: 0.01517634 $DESCRIPTION: "target black luminance"
662 float white_point_target; // $MIN: 0 $MAX: 1600 $DEFAULT: 100 $DESCRIPTION: "target white luminance"
663 float output_power; // $MIN: 1 $MAX: 10 $DEFAULT: 4.0 $DESCRIPTION: "hardness"
664 float latitude; // $MIN: 0.01 $MAX: 99 $DEFAULT: 50.0
665 float contrast; // $MIN: 0 $MAX: 5 $DEFAULT: 1.1
666 float saturation; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "extreme luminance saturation"
667 float balance; // $MIN: -50 $MAX: 50 $DEFAULT: 0.0 $DESCRIPTION: "shadows \342\206\224 highlights balance"
668 float noise_level; // $MIN: 0.0 $MAX: 6.0 $DEFAULT: 0.2f $DESCRIPTION: "add noise in highlights"
669 dt_iop_filmicrgb_methods_type_t preserve_color; // $DEFAULT: DT_FILMIC_METHOD_POWER_NORM $DESCRIPTION: "preserve chrominance"
670 dt_iop_filmicrgb_colorscience_type_t version; // $DEFAULT: DT_FILMIC_COLORSCIENCE_V3 $DESCRIPTION: "color science"
671 gboolean auto_hardness; // $DEFAULT: TRUE $DESCRIPTION: "auto adjust hardness"
672 gboolean custom_grey; // $DEFAULT: FALSE $DESCRIPTION: "use custom middle-gray values"
673 int high_quality_reconstruction; // $MIN: 0 $MAX: 10 $DEFAULT: 1 $DESCRIPTION: "iterations of high-quality reconstruction"
674 dt_iop_filmic_noise_distribution_t noise_distribution; // $DEFAULT: DT_NOISE_GAUSSIAN $DESCRIPTION: "type of noise"
675 dt_iop_filmicrgb_curve_type_t shadows; // $DEFAULT: DT_FILMIC_CURVE_RATIONAL $DESCRIPTION: "contrast in shadows"
676 dt_iop_filmicrgb_curve_type_t highlights; // $DEFAULT: DT_FILMIC_CURVE_RATIONAL $DESCRIPTION: "contrast in highlights"
677 gboolean compensate_icc_black; // $DEFAULT: FALSE $DESCRIPTION: "compensate output ICC profile black point"
678 gint internal_version; // $DEFAULT: 2020 $DESCRIPTION: "version of the spline generator"
679 } dt_iop_filmicrgb_params_v4_t;
680
681 dt_iop_filmicrgb_params_v4_t *o = (dt_iop_filmicrgb_params_v4_t *)old_params;
683 *n = *(dt_iop_filmicrgb_params_t*)o; // structure didn't change except the enum instead of gint for internal_version
684 // we still need to convert the internal_version (in year) to the enum
685 switch(o->internal_version)
686 {
687 case(2019):
689 break;
690 case(2020):
692 break;
693 case(2021):
695 break;
696 default:
697 return 1;
698 }
700 return 0;
701 }
702 return 1;
703}
704
705#ifdef _OPENMP
706#pragma omp declare simd aligned(pixel:16)
707#endif
708static inline float pixel_rgb_norm_power(const dt_aligned_pixel_t pixel)
709{
710 // weird norm sort of perceptual. This is black magic really, but it looks good.
711 // the full norm is (R^3 + G^3 + B^3) / (R^2 + G^2 + B^2) and it should be in ]0; +infinity[
712
713 float numerator = 0.0f;
714 float denominator = 0.0f;
715
716 for(int c = 0; c < 3; c++)
717 {
718 const float value = fabsf(pixel[c]);
719 const float RGB_square = value * value;
720 const float RGB_cubic = RGB_square * value;
721 numerator += RGB_cubic;
722 denominator += RGB_square;
723 }
724
725 return numerator / fmaxf(denominator, 1e-12f); // prevent from division-by-0 (note: (1e-6)^2 = 1e-12
726}
727
728
729#ifdef _OPENMP
730#pragma omp declare simd aligned(pixel : 16) uniform(variant, work_profile)
731#endif
732static inline float get_pixel_norm(const dt_aligned_pixel_t pixel, const dt_iop_filmicrgb_methods_type_t variant,
733 const dt_iop_order_iccprofile_info_t *const work_profile)
734{
735 // a newly added norm should satisfy the condition that it is linear with respect to grey pixels:
736 // norm(R, G, B) = norm(x, x, x) = x
737 // the desaturation code in chroma preservation mode relies on this assumption.
738 // DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1 is an exception to this and is marked as legacy.
739 // DT_FILMIC_METHOD_EUCLIDEAN_NORM_V2 takes the Euclidean norm and scales it such that
740 // norm(1, 1, 1) = 1.
741 switch(variant)
742 {
744 return fmaxf(fmaxf(pixel[0], pixel[1]), pixel[2]);
745
747 return (work_profile)
748 ? dt_ioppr_get_rgb_matrix_luminance(pixel, work_profile->matrix_in, work_profile->lut_in,
749 work_profile->unbounded_coeffs_in, work_profile->lutsize,
750 work_profile->nonlinearlut)
752
754 return pixel_rgb_norm_power(pixel);
755
757 return sqrtf(sqf(pixel[0]) + sqf(pixel[1]) + sqf(pixel[2]));
758
760 return sqrtf(sqf(pixel[0]) + sqf(pixel[1]) + sqf(pixel[2])) * INVERSE_SQRT_3;
761
762 default:
763 return (work_profile)
764 ? dt_ioppr_get_rgb_matrix_luminance(pixel, work_profile->matrix_in, work_profile->lut_in,
765 work_profile->unbounded_coeffs_in, work_profile->lutsize,
766 work_profile->nonlinearlut)
768 }
769}
770
771#ifdef _OPENMP
772#pragma omp declare simd uniform(grey, black, dynamic_range)
773#endif
774static inline float log_tonemapping(const float x, const float grey, const float black,
775 const float dynamic_range)
776{
777 return clamp_simd((log2f(x / grey) - black) / dynamic_range);
778}
779
780#ifdef _OPENMP
781#pragma omp declare simd uniform(grey, black, dynamic_range)
782#endif
783static inline float exp_tonemapping_v2(const float x, const float grey, const float black,
784 const float dynamic_range)
785{
786 // inverse of log_tonemapping
787 return grey * exp2f(dynamic_range * x + black);
788}
789
790
791#ifdef _OPENMP
792#pragma omp declare simd aligned(M1, M2, M3, M4 : 16) uniform(M1, M2, M3, M4, M5, latitude_min, latitude_max)
793#endif
794static inline float filmic_spline(const float x, const dt_aligned_pixel_t M1, const dt_aligned_pixel_t M2,
795 const dt_aligned_pixel_t M3, const dt_aligned_pixel_t M4,
796 const dt_aligned_pixel_t M5, const float latitude_min,
797 const float latitude_max, const dt_iop_filmicrgb_curve_type_t type[2])
798{
799 // if type polynomial :
800 // y = M5 * x⁴ + M4 * x³ + M3 * x² + M2 * x¹ + M1 * x⁰
801 // but we rewrite it using Horner factorisation, to spare ops and enable FMA in available
802 // else if type rational :
803 // y = M1 * (M2 * (x - x_0)² + (x - x_0)) / (M2 * (x - x_0)² + (x - x_0) + M3)
804
805 float result;
806
807 if(x < latitude_min)
808 {
809 // toe
811 {
812 // polynomial toe, 4th order
813 result = M1[0] + x * (M2[0] + x * (M3[0] + x * (M4[0] + x * M5[0])));
814 }
815 else if(type[0] == DT_FILMIC_CURVE_POLY_3)
816 {
817 // polynomial toe, 3rd order
818 result = M1[0] + x * (M2[0] + x * (M3[0] + x * M4[0]));
819 }
820 else
821 {
822 // rational toe
823 const float xi = latitude_min - x;
824 const float rat = xi * (xi * M2[0] + 1.f);
825 result = M4[0] - M1[0] * rat / (rat + M3[0]);
826 }
827 }
828 else if(x > latitude_max)
829 {
830 // shoulder
832 {
833 // polynomial shoulder, 4th order
834 result = M1[1] + x * (M2[1] + x * (M3[1] + x * (M4[1] + x * M5[1])));
835 }
836 else if(type[1] == DT_FILMIC_CURVE_POLY_3)
837 {
838 // polynomial shoulder, 3rd order
839 result = M1[1] + x * (M2[1] + x * (M3[1] + x * M4[1]));
840 }
841 else
842 {
843 // rational toe
844 const float xi = x - latitude_max;
845 const float rat = xi * (xi * M2[1] + 1.f);
846 result = M4[1] + M1[1] * rat / (rat + M3[1]);
847 }
848 }
849 else
850 {
851 // latitude
852 result = M1[2] + x * M2[2];
853 }
854
855 return result;
856}
857
858
859#ifdef _OPENMP
860#pragma omp declare simd uniform(sigma_toe, sigma_shoulder)
861#endif
862static inline float filmic_desaturate_v1(const float x, const float sigma_toe, const float sigma_shoulder,
863 const float saturation)
864{
865 const float radius_toe = x;
866 const float radius_shoulder = 1.0f - x;
867
868 const float key_toe = expf(-0.5f * radius_toe * radius_toe / sigma_toe);
869 const float key_shoulder = expf(-0.5f * radius_shoulder * radius_shoulder / sigma_shoulder);
870
871 return 1.0f - clamp_simd((key_toe + key_shoulder) / saturation);
872}
873
874
875#ifdef _OPENMP
876#pragma omp declare simd uniform(sigma_toe, sigma_shoulder)
877#endif
878static inline float filmic_desaturate_v2(const float x, const float sigma_toe, const float sigma_shoulder,
879 const float saturation)
880{
881 const float radius_toe = x;
882 const float radius_shoulder = 1.0f - x;
883 const float sat2 = 0.5f / sqrtf(saturation);
884 const float key_toe = expf(-radius_toe * radius_toe / sigma_toe * sat2);
885 const float key_shoulder = expf(-radius_shoulder * radius_shoulder / sigma_shoulder * sat2);
886
887 return (saturation - (key_toe + key_shoulder) * (saturation));
888}
889
890
891#ifdef _OPENMP
892#pragma omp declare simd
893#endif
894static inline float linear_saturation(const float x, const float luminance, const float saturation)
895{
896 return luminance + saturation * (x - luminance);
897}
898
899
900#define MAX_NUM_SCALES 10
901
902
903#ifdef _OPENMP
904#pragma omp declare simd aligned(in, mask : 64) uniform(feathering, normalize, width, height, ch)
905#endif
906static inline gint mask_clipped_pixels(const float *const restrict in, float *const restrict mask,
907 const float normalize, const float feathering, const size_t width,
908 const size_t height, const size_t ch)
909{
910 /* 1. Detect if pixels are clipped and count them,
911 * 2. assign them a weight in [0. ; 1.] depending on how close from clipping they are. The weights are defined
912 * by a sigmoid centered in `reconstruct_threshold` so the transition is soft and symmetrical
913 */
914
915 int clipped = 0;
916
917 #ifdef __SSE2__
918 // flush denormals to zero for masking to avoid performance penalty
919 // if there are a lot of zero values in the mask
920 const unsigned int oldMode = _MM_GET_FLUSH_ZERO_MODE();
921 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
922 #endif
923
924#ifdef _OPENMP
925#pragma omp parallel for simd default(none) \
926 dt_omp_firstprivate(in, mask, normalize, feathering, width, height, ch) \
927 schedule(simd:static) aligned(mask, in:64) reduction(+:clipped)
928#endif
929 for(size_t k = 0; k < height * width * ch; k += ch)
930 {
931 const float pix_max = fmaxf(sqrtf(sqf(in[k]) + sqf(in[k + 1]) + sqf(in[k + 2])), 0.f);
932 const float argument = -pix_max * normalize + feathering;
933 const float weight = clamp_simd(1.0f / (1.0f + exp2f(argument)));
934 mask[k / ch] = weight;
935
936 // at x = 4, the sigmoid produces opacity = 5.882 %.
937 // any x > 4 will produce negligible changes over the image,
938 // especially since we have reduced visual sensitivity in highlights.
939 // so we discard pixels for argument > 4. for they are not worth computing.
940 clipped += (4.f > argument);
941 }
942
943 #ifdef __SSE2__
944 _MM_SET_FLUSH_ZERO_MODE(oldMode);
945 #endif
946
947 // If clipped area is < 9 pixels, recovery is not worth the computational cost, so skip it.
948 return (clipped > 9);
949}
950
951
952#ifdef _OPENMP
953#pragma omp declare simd aligned(in, mask, inpainted:64) uniform(width, height, noise_level, noise_distribution, threshold)
954#endif
955inline static void inpaint_noise(const float *const in, const float *const mask,
956 float *const inpainted, const float noise_level, const float threshold,
957 const dt_noise_distribution_t noise_distribution,
958 const size_t width, const size_t height)
959{
960 // add statistical noise in highlights to fill-in texture
961 // this creates "particules" in highlights, that will help the implicit partial derivative equation
962 // solver used in wavelets reconstruction to generate texture
963
964#ifdef _OPENMP
965#pragma omp parallel for default(none) \
966 dt_omp_firstprivate(in, mask, inpainted, width, height, noise_level, noise_distribution, threshold) \
967 schedule(simd:static) collapse(2)
968#endif
969 for(size_t i = 0; i < height; i++)
970 for(size_t j = 0; j < width; j++)
971 {
972 // Init random number generator
973 uint32_t DT_ALIGNED_ARRAY state[4] = { splitmix32(j + 1), splitmix32((j + 1) * (i + 3)), splitmix32(1337), splitmix32(666) };
974 xoshiro128plus(state);
975 xoshiro128plus(state);
976 xoshiro128plus(state);
977 xoshiro128plus(state);
978
979 // get the mask value in [0 ; 1]
980 const size_t idx = i * width + j;
981 const size_t index = idx * 4;
982 const float weight = mask[idx];
983 const float *const restrict pix_in = __builtin_assume_aligned(in + index, 16);
984 dt_aligned_pixel_t noise = { 0.f };
985 dt_aligned_pixel_t sigma = { 0.f };
986 const int DT_ALIGNED_ARRAY flip[4] = { TRUE, FALSE, TRUE, FALSE };
987
988 for_each_channel(c,aligned(pix_in))
989 sigma[c] = pix_in[c] * noise_level / threshold;
990
991 // create statistical noise
992 dt_noise_generator_simd(noise_distribution, pix_in, sigma, flip, state, noise);
993
994 // add noise to input
995 float *const restrict pix_out = __builtin_assume_aligned(inpainted + index, 16);
996 for_each_channel(c,aligned(pix_in,pix_out))
997 pix_out[c] = fmaxf(pix_in[c] * (1.0f - weight) + weight * noise[c], 0.f);
998 }
999}
1000
1001inline static void wavelets_reconstruct_RGB(const float *const restrict HF, const float *const restrict LF,
1002 const float *const restrict texture, const float *const restrict mask,
1003 float *const restrict reconstructed, const size_t width,
1004 const size_t height, const size_t ch, const float gamma,
1005 const float gamma_comp, const float beta, const float beta_comp,
1006 const float delta, const size_t s, const size_t scales)
1007{
1008#ifdef _OPENMP
1009#pragma omp parallel for default(none) \
1010 dt_omp_firstprivate(width, height, ch, HF, LF, texture, mask, reconstructed, gamma, gamma_comp, beta, \
1011 beta_comp, delta, s, scales) schedule(simd : static)
1012#endif
1013 for(size_t k = 0; k < height * width * ch; k += 4)
1014 {
1015 const float alpha = mask[k / ch];
1016
1017 // cache RGB wavelets scales just to be sure the compiler doesn't reload them
1018 const float *const restrict HF_c = __builtin_assume_aligned(HF + k, 16);
1019 const float *const restrict LF_c = __builtin_assume_aligned(LF + k, 16);
1020 const float *const restrict TT_c = __builtin_assume_aligned(texture + k, 16);
1021
1022 // synthesize the max of all RGB channels texture as a flat texture term for the whole pixel
1023 // this is useful if only 1 or 2 channels are clipped, so we transfer the valid/sharpest texture on the other
1024 // channels
1025 const float grey_texture = fmaxabsf(fmaxabsf(TT_c[0], TT_c[1]), TT_c[2]);
1026
1027 // synthesize the max of all interpolated/inpainted RGB channels as a flat details term for the whole pixel
1028 // this is smoother than grey_texture and will fill holes smoothly in details layers if grey_texture ~= 0.f
1029 const float grey_details = (HF_c[0] + HF_c[1] + HF_c[2]) / 3.f;
1030
1031 // synthesize both terms with weighting
1032 // when beta_comp ~= 1.0, we force the reconstruction to be achromatic, which may help with gamut issues or
1033 // magenta highlights.
1034 const float grey_HF = beta_comp * (gamma_comp * grey_details + gamma * grey_texture);
1035
1036 // synthesize the min of all low-frequency RGB channels as a flat structure term for the whole pixel
1037 // when beta_comp ~= 1.0, we force the reconstruction to be achromatic, which may help with gamut issues or magenta highlights.
1038 const float grey_residual = beta_comp * (LF_c[0] + LF_c[1] + LF_c[2]) / 3.f;
1039
1040 #ifdef _OPENMP
1041 #pragma omp simd aligned(reconstructed:64) aligned(HF_c, LF_c, TT_c:16)
1042 #endif
1043 for(size_t c = 0; c < 4; c++)
1044 {
1045 // synthesize interpolated/inpainted RGB channels color details residuals and weigh them
1046 // this brings back some color on top of the grey_residual
1047
1048 // synthesize interpolated/inpainted RGB channels color details and weigh them
1049 // this brings back some color on top of the grey_details
1050 const float details = (gamma_comp * HF_c[c] + gamma * TT_c[c]) * beta + grey_HF;
1051
1052 // reconstruction
1053 const float residual = (s == scales - 1) ? (grey_residual + LF_c[c] * beta) : 0.f;
1054 reconstructed[k + c] += alpha * (delta * details + residual);
1055 }
1056 }
1057}
1058
1059inline static void wavelets_reconstruct_ratios(const float *const restrict HF, const float *const restrict LF,
1060 const float *const restrict texture,
1061 const float *const restrict mask,
1062 float *const restrict reconstructed, const size_t width,
1063 const size_t height, const size_t ch, const float gamma,
1064 const float gamma_comp, const float beta, const float beta_comp,
1065 const float delta, const size_t s, const size_t scales)
1066{
1067/*
1068 * This is the adapted version of the RGB reconstruction
1069 * RGB contain high frequencies that we try to recover, so we favor them in the reconstruction.
1070 * The ratios represent the chromaticity in image and contain low frequencies in the absence of noise or
1071 * aberrations, so, here, we favor them instead.
1072 *
1073 * Consequences :
1074 * 1. use min of interpolated channels details instead of max, to get smoother details
1075 * 4. use the max of low frequency channels instead of min, to favor achromatic solution.
1076 *
1077 * Note : ratios close to 1 mean higher spectral purity (more white). Ratios close to 0 mean lower spectral purity
1078 * (more colorful)
1079 */
1080#ifdef _OPENMP
1081#pragma omp parallel for default(none) \
1082 dt_omp_firstprivate(width, height, ch, HF, LF, texture, mask, reconstructed, gamma, gamma_comp, beta, \
1083 beta_comp, delta, s, scales) schedule(simd \
1084 : static)
1085#endif
1086 for(size_t k = 0; k < height * width * ch; k += 4)
1087 {
1088 const float alpha = mask[k / ch];
1089
1090 // cache RGB wavelets scales just to be sure the compiler doesn't reload them
1091 const float *const restrict HF_c = __builtin_assume_aligned(HF + k, 16);
1092 const float *const restrict LF_c = __builtin_assume_aligned(LF + k, 16);
1093 const float *const restrict TT_c = __builtin_assume_aligned(texture + k, 16);
1094
1095 // synthesize the max of all RGB channels texture as a flat texture term for the whole pixel
1096 // this is useful if only 1 or 2 channels are clipped, so we transfer the valid/sharpest texture on the other
1097 // channels
1098 const float grey_texture = fmaxabsf(fmaxabsf(TT_c[0], TT_c[1]), TT_c[2]);
1099
1100 // synthesize the max of all interpolated/inpainted RGB channels as a flat details term for the whole pixel
1101 // this is smoother than grey_texture and will fill holes smoothly in details layers if grey_texture ~= 0.f
1102 const float grey_details = (HF_c[0] + HF_c[1] + HF_c[2]) / 3.f;
1103
1104 // synthesize both terms with weighting
1105 // when beta_comp ~= 1.0, we force the reconstruction to be achromatic, which may help with gamut issues or
1106 // magenta highlights.
1107 const float grey_HF = (gamma_comp * grey_details + gamma * grey_texture);
1108
1109 #ifdef _OPENMP
1110 #pragma omp simd aligned(reconstructed:64) aligned(HF_c, TT_c, LF_c:16) linear(k:4)
1111 #endif
1112 for(size_t c = 0; c < 4; c++)
1113 {
1114 // synthesize interpolated/inpainted RGB channels color details residuals and weigh them
1115 // this brings back some color on top of the grey_residual
1116 const float details = 0.5f * ((gamma_comp * HF_c[c] + gamma * TT_c[c]) + grey_HF);
1117
1118 // reconstruction
1119 const float residual = (s == scales - 1) ? LF_c[c] : 0.f;
1120 reconstructed[k + c] += alpha * (delta * details + residual);
1121 }
1122 }
1123}
1124
1125
1126static inline void init_reconstruct(const float *const restrict in, const float *const restrict mask,
1127 float *const restrict reconstructed, const size_t width, const size_t height)
1128{
1129// init the reconstructed buffer with non-clipped and partially clipped pixels
1130// Note : it's a simple multiplied alpha blending where mask = alpha weight
1131#ifdef _OPENMP
1132#pragma omp parallel for default(none) dt_omp_firstprivate(in, mask, reconstructed, width, height) \
1133 schedule(static)
1134#endif
1135 for(size_t k = 0; k < height * width; k++)
1136 {
1137 for_each_channel(c,aligned(in,mask,reconstructed))
1138 reconstructed[4*k + c] = fmaxf(in[4*k + c] * (1.f - mask[k]), 0.f);
1139 }
1140}
1141
1142
1143static inline void wavelets_detail_level(const float *const restrict detail, const float *const restrict LF,
1144 float *const restrict HF, float *const restrict texture,
1145 const size_t width, const size_t height, const size_t ch)
1146{
1147#ifdef _OPENMP
1148#pragma omp parallel for simd default(none) dt_omp_firstprivate(width, height, HF, LF, detail, texture) \
1149 schedule(simd \
1150 : static) aligned(HF, LF, detail, texture : 64)
1151#endif
1152 for(size_t k = 0; k < height * width; k++)
1153 {
1154 for(size_t c = 0; c < 4; ++c) HF[4*k + c] = texture[4*k + c] = detail[4*k + c] - LF[4*k + c];
1155 }
1156}
1157
1158static int get_scales(const dt_iop_roi_t *roi_in, const dt_dev_pixelpipe_iop_t *const piece)
1159{
1160 /* How many wavelets scales do we need to compute at current zoom level ?
1161 * 0. To get the same preview no matter the zoom scale, the relative image coverage ratio of the filter at
1162 * the coarsest wavelet level should always stay constant.
1163 * 1. The image coverage of each B spline filter of size `BSPLINE_FSIZE` is `2^(level) * (BSPLINE_FSIZE - 1) / 2 + 1` pixels
1164 * 2. The coarsest level filter at full resolution should cover `1/BSPLINE_FSIZE` of the largest image dimension.
1165 * 3. The coarsest level filter at current zoom level should cover `scale/BSPLINE_FSIZE` of the largest image dimension.
1166 *
1167 * So we compute the level that solves 1. subject to 3. Of course, integer rounding doesn't make that 1:1
1168 * accurate.
1169 */
1170 const float scale = roi_in->scale / piece->iscale;
1171 const size_t size = MAX(piece->buf_in.height * piece->iscale, piece->buf_in.width * piece->iscale);
1172 const int scales = floorf(log2f((2.0f * size * scale / ((BSPLINE_FSIZE - 1) * BSPLINE_FSIZE)) - 1.0f));
1173 return CLAMP(scales, 1, MAX_NUM_SCALES);
1174}
1175
1176
1177static inline gint reconstruct_highlights(const float *const restrict in, const float *const restrict mask,
1178 float *const restrict reconstructed,
1179 const dt_iop_filmicrgb_reconstruction_type_t variant, const size_t ch,
1180 const dt_iop_filmicrgb_data_t *const data, dt_dev_pixelpipe_iop_t *piece,
1181 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
1182{
1183 gint success = TRUE;
1184
1185 // wavelets scales
1186 const int scales = get_scales(roi_in, piece);
1187
1188 // wavelets scales buffers
1189 float *const restrict LF_even = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height); // low-frequencies RGB
1190 float *const restrict LF_odd = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height); // low-frequencies RGB
1191 float *const restrict HF_RGB = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height); // high-frequencies RGB
1192 float *const restrict HF_grey = dt_alloc_sse_ps(ch * roi_out->width * roi_out->height); // high-frequencies RGB backup
1193
1194 // alloc a permanent reusable buffer for intermediate computations - avoid multiple alloc/free
1195 float *const restrict temp = dt_alloc_sse_ps(darktable.num_openmp_threads * ch * roi_out->width);
1196
1197 if(!LF_even || !LF_odd || !HF_RGB || !HF_grey || !temp)
1198 {
1199 dt_control_log(_("filmic highlights reconstruction failed to allocate memory, check your RAM settings"));
1200 success = FALSE;
1201 goto error;
1202 }
1203
1204 // Init reconstructed with valid parts of image
1205 init_reconstruct(in, mask, reconstructed, roi_out->width, roi_out->height);
1206
1207 // structure inpainting vs. texture duplicating weight
1208 const float gamma = (data->reconstruct_structure_vs_texture);
1209 const float gamma_comp = 1.0f - data->reconstruct_structure_vs_texture;
1210
1211 // colorful vs. grey weight
1212 const float beta = data->reconstruct_grey_vs_color;
1213 const float beta_comp = 1.f - data->reconstruct_grey_vs_color;
1214
1215 // bloom vs reconstruct weight
1216 const float delta = data->reconstruct_bloom_vs_details;
1217
1218 // À trous wavelet decompose
1219 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
1220 // the wavelets decomposition here is the same as the equalizer/atrous module,
1221 // but simplified because we don't need the edge-aware term, so we can separate the convolution kernel
1222 // with a vertical and horizontal blur, which is 10 multiply-add instead of 25 by pixel.
1223 for(int s = 0; s < scales; ++s)
1224 {
1225 const float *restrict detail; // buffer containing this scale's input
1226 float *restrict LF; // output buffer for the current scale
1227 float *restrict HF_RGB_temp; // temp buffer for HF_RBG terms before blurring
1228
1229 // swap buffers so we only need 2 LF buffers : the LF at scale (s-1) and the one at current scale (s)
1230 if(s == 0)
1231 {
1232 detail = in;
1233 LF = LF_odd;
1234 HF_RGB_temp = LF_even;
1235 }
1236 else if(s % 2 != 0)
1237 {
1238 detail = LF_odd;
1239 LF = LF_even;
1240 HF_RGB_temp = LF_odd;
1241 }
1242 else
1243 {
1244 detail = LF_even;
1245 LF = LF_odd;
1246 HF_RGB_temp = LF_even;
1247 }
1248
1249 const int mult = 1 << s; // fancy-pants C notation for 2^s with integer type, don't be afraid
1250
1251 // Compute wavelets low-frequency scales
1252 blur_2D_Bspline(detail, LF, temp, roi_out->width, roi_out->height, mult, TRUE); // clip negatives
1253
1254 // Compute wavelets high-frequency scales and save the minimum of texture over the RGB channels
1255 // Note : HF_RGB = detail - LF, HF_grey = max(HF_RGB)
1256 wavelets_detail_level(detail, LF, HF_RGB_temp, HF_grey, roi_out->width, roi_out->height, ch);
1257
1258 // interpolate/blur/inpaint (same thing) the RGB high-frequency to fill holes
1259 blur_2D_Bspline(HF_RGB_temp, HF_RGB, temp, roi_out->width, roi_out->height, 1, FALSE);
1260
1261 // Reconstruct clipped parts
1262 if(variant == DT_FILMIC_RECONSTRUCT_RGB)
1263 wavelets_reconstruct_RGB(HF_RGB, LF, HF_grey, mask, reconstructed, roi_out->width, roi_out->height, ch,
1264 gamma, gamma_comp, beta, beta_comp, delta, s, scales);
1265 else if(variant == DT_FILMIC_RECONSTRUCT_RATIOS)
1266 wavelets_reconstruct_ratios(HF_RGB, LF, HF_grey, mask, reconstructed, roi_out->width, roi_out->height, ch,
1267 gamma, gamma_comp, beta, beta_comp, delta, s, scales);
1268 }
1269
1270error:
1271 if(temp) dt_free_align(temp);
1272 if(LF_even) dt_free_align(LF_even);
1273 if(LF_odd) dt_free_align(LF_odd);
1274 if(HF_RGB) dt_free_align(HF_RGB);
1275 if(HF_grey) dt_free_align(HF_grey);
1276 return success;
1277}
1278
1279
1280static inline void filmic_split_v1(const float *const restrict in, float *const restrict out,
1281 const dt_iop_order_iccprofile_info_t *const work_profile,
1282 const dt_iop_filmicrgb_data_t *const data,
1283 const dt_iop_filmic_rgb_spline_t spline, const size_t width,
1284 const size_t height)
1285{
1286#ifdef _OPENMP
1287#pragma omp parallel for default(none) \
1288 dt_omp_firstprivate(width, height, data, in, out, work_profile, spline) \
1289 schedule(simd : static)
1290#endif
1291 for(size_t k = 0; k < height * width * 4; k += 4)
1292 {
1293 const float *const restrict pix_in = in + k;
1294 float *const restrict pix_out = out + k;
1295 dt_aligned_pixel_t temp;
1296
1297 // Log tone-mapping
1298 for(int c = 0; c < 3; c++)
1299 temp[c] = log_tonemapping(fmaxf(pix_in[c], NORM_MIN), data->grey_source, data->black_source,
1300 data->dynamic_range);
1301
1302 // Get the desaturation coeff based on the log value
1303 const float lum = (work_profile)
1304 ? dt_ioppr_get_rgb_matrix_luminance(temp, work_profile->matrix_in, work_profile->lut_in,
1305 work_profile->unbounded_coeffs_in,
1306 work_profile->lutsize, work_profile->nonlinearlut)
1308 const float desaturation = filmic_desaturate_v1(lum, data->sigma_toe, data->sigma_shoulder, data->saturation);
1309
1310 // Desaturate on the non-linear parts of the curve
1311 // Filmic S curve on the max RGB
1312 // Apply the transfer function of the display
1313 for(int c = 0; c < 3; c++)
1314 pix_out[c] = powf(
1315 CLAMPF(filmic_spline(linear_saturation(temp[c], lum, desaturation),
1316 spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1317 spline.latitude_min, spline.latitude_max, spline.type),
1318 spline.y[0], spline.y[4]),
1319 data->output_power);
1320 }
1321}
1322
1323
1324static inline void filmic_split_v2_v3(const float *const restrict in, float *const restrict out,
1325 const dt_iop_order_iccprofile_info_t *const work_profile,
1326 const dt_iop_filmicrgb_data_t *const data,
1327 const dt_iop_filmic_rgb_spline_t spline, const size_t width,
1328 const size_t height)
1329{
1330#ifdef _OPENMP
1331#pragma omp parallel for default(none) \
1332 dt_omp_firstprivate(width, height, data, in, out, work_profile, spline) \
1333 schedule(simd : static)
1334#endif
1335 for(size_t k = 0; k < height * width * 4; k += 4)
1336 {
1337 const float *const restrict pix_in = in + k;
1338 float *const restrict pix_out = out + k;
1339 dt_aligned_pixel_t temp;
1340
1341 // Log tone-mapping
1342 for(int c = 0; c < 3; c++)
1343 temp[c] = log_tonemapping(fmaxf(pix_in[c], NORM_MIN), data->grey_source, data->black_source,
1344 data->dynamic_range);
1345
1346 // Get the desaturation coeff based on the log value
1347 const float lum = (work_profile)
1348 ? dt_ioppr_get_rgb_matrix_luminance(temp, work_profile->matrix_in, work_profile->lut_in,
1349 work_profile->unbounded_coeffs_in,
1350 work_profile->lutsize, work_profile->nonlinearlut)
1352 const float desaturation = filmic_desaturate_v2(lum, data->sigma_toe, data->sigma_shoulder, data->saturation);
1353
1354 // Desaturate on the non-linear parts of the curve
1355 // Filmic S curve on the max RGB
1356 // Apply the transfer function of the display
1357 for(int c = 0; c < 3; c++)
1358 pix_out[c] = powf(
1359 CLAMPF(filmic_spline(linear_saturation(temp[c], lum, desaturation),
1360 spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1361 spline.latitude_min, spline.latitude_max, spline.type),
1362 spline.y[0], spline.y[4]),
1363 data->output_power);
1364 }
1365}
1366
1367
1368static inline void filmic_chroma_v1(const float *const restrict in, float *const restrict out,
1369 const dt_iop_order_iccprofile_info_t *const work_profile,
1370 const dt_iop_filmicrgb_data_t *const data,
1371 const dt_iop_filmic_rgb_spline_t spline, const int variant, const size_t width,
1372 const size_t height)
1373{
1374#ifdef _OPENMP
1375#pragma omp parallel for default(none) \
1376 dt_omp_firstprivate(width, height, data, in, out, work_profile, variant, spline) schedule(simd : static)
1377#endif
1378 for(size_t k = 0; k < height * width * 4; k += 4)
1379 {
1380 const float *const restrict pix_in = in + k;
1381 float *const restrict pix_out = out + k;
1382
1383 dt_aligned_pixel_t ratios = { 0.0f };
1384 float norm = fmaxf(get_pixel_norm(pix_in, variant, work_profile), NORM_MIN);
1385
1386 // Save the ratios
1387 for_each_channel(c,aligned(pix_in))
1388 ratios[c] = pix_in[c] / norm;
1389
1390 // Sanitize the ratios
1391 const float min_ratios = fminf(fminf(ratios[0], ratios[1]), ratios[2]);
1392 if(min_ratios < 0.0f)
1393 for_each_channel(c) ratios[c] -= min_ratios;
1394
1395 // Log tone-mapping
1396 norm = log_tonemapping(norm, data->grey_source, data->black_source, data->dynamic_range);
1397
1398 // Get the desaturation value based on the log value
1399 const float desaturation = filmic_desaturate_v1(norm, data->sigma_toe, data->sigma_shoulder, data->saturation);
1400
1401 for_each_channel(c) ratios[c] *= norm;
1402
1403 const float lum = (work_profile) ? dt_ioppr_get_rgb_matrix_luminance(
1404 ratios, work_profile->matrix_in, work_profile->lut_in, work_profile->unbounded_coeffs_in,
1405 work_profile->lutsize, work_profile->nonlinearlut)
1406 : dt_camera_rgb_luminance(ratios);
1407
1408 // Desaturate on the non-linear parts of the curve and save ratios
1409 for(int c = 0; c < 3; c++) ratios[c] = linear_saturation(ratios[c], lum, desaturation) / norm;
1410
1411 // Filmic S curve on the max RGB
1412 // Apply the transfer function of the display
1413 norm = powf(CLAMPF(filmic_spline(norm, spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1414 spline.latitude_min, spline.latitude_max, spline.type),
1415 spline.y[0], spline.y[4]),
1416 data->output_power);
1417
1418 // Re-apply ratios
1419 for_each_channel(c,aligned(pix_out)) pix_out[c] = ratios[c] * norm;
1420 }
1421}
1422
1423
1424static inline void filmic_chroma_v2_v3(const float *const restrict in, float *const restrict out,
1425 const dt_iop_order_iccprofile_info_t *const work_profile,
1426 const dt_iop_filmicrgb_data_t *const data,
1427 const dt_iop_filmic_rgb_spline_t spline, const int variant,
1428 const size_t width, const size_t height, const size_t ch,
1429 const dt_iop_filmicrgb_colorscience_type_t colorscience_version)
1430{
1431
1432#ifdef _OPENMP
1433#pragma omp parallel for default(none) \
1434 dt_omp_firstprivate(width, height, ch, data, in, out, work_profile, variant, spline, colorscience_version) \
1435 schedule(simd :static)
1436#endif
1437 for(size_t k = 0; k < height * width * ch; k += ch)
1438 {
1439 const float *const restrict pix_in = in + k;
1440 float *const restrict pix_out = out + k;
1441
1442 float norm = fmaxf(get_pixel_norm(pix_in, variant, work_profile), NORM_MIN);
1443
1444 // Save the ratios
1445 dt_aligned_pixel_t ratios = { 0.0f };
1446
1447 for_each_channel(c,aligned(pix_in))
1448 ratios[c] = pix_in[c] / norm;
1449
1450 // Sanitize the ratios
1451 const float min_ratios = fminf(fminf(ratios[0], ratios[1]), ratios[2]);
1452 const int sanitize = (min_ratios < 0.0f);
1453
1454 if(sanitize)
1456 ratios[c] -= min_ratios;
1457
1458 // Log tone-mapping
1459 norm = log_tonemapping(norm, data->grey_source, data->black_source, data->dynamic_range);
1460
1461 // Get the desaturation value based on the log value
1462 const float desaturation = filmic_desaturate_v2(norm, data->sigma_toe, data->sigma_shoulder, data->saturation);
1463
1464 // Filmic S curve on the max RGB
1465 // Apply the transfer function of the display
1466 norm = powf(CLAMPF(filmic_spline(norm, spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1467 spline.latitude_min, spline.latitude_max, spline.type),
1468 spline.y[0], spline.y[4]),
1469 data->output_power);
1470
1471 // Re-apply ratios with saturation change
1472 for(int c = 0; c < 3; c++) ratios[c] = fmaxf(ratios[c] + (1.0f - ratios[c]) * (1.0f - desaturation), 0.0f);
1473
1474 // color science v3: normalize again after desaturation - the norm might have changed by the desaturation
1475 // operation.
1476 if(colorscience_version == DT_FILMIC_COLORSCIENCE_V3)
1477 norm /= fmaxf(get_pixel_norm(ratios, variant, work_profile), NORM_MIN);
1478
1479 for_each_channel(c,aligned(pix_out))
1480 pix_out[c] = ratios[c] * norm;
1481
1482 // Gamut mapping
1483 const float max_pix = fmaxf(fmaxf(pix_out[0], pix_out[1]), pix_out[2]);
1484 const int penalize = (max_pix > 1.0f);
1485
1486 // Penalize the ratios by the amount of clipping
1487 if(penalize)
1488 {
1489 for_each_channel(c,aligned(pix_out))
1490 {
1491 ratios[c] = fmaxf(ratios[c] + (1.0f - max_pix), 0.0f);
1492 pix_out[c] = ratios[c] * norm;
1493 }
1494 }
1495 }
1496}
1497
1498
1499#ifdef _OPENMP
1500#pragma omp declare simd uniform(matrix) aligned(in, out:16) aligned(matrix:64)
1501#endif
1502static inline void pipe_RGB_to_Ych(const dt_aligned_pixel_t in, const dt_colormatrix_t matrix, dt_aligned_pixel_t out)
1503{
1504 dt_aligned_pixel_t LMS = { 0.f };
1505 dt_aligned_pixel_t Yrg = { 0.f };
1506
1507 // go from pipeline RGB to CIE 2006 LMS D65
1508 dot_product(in, matrix, LMS);
1509
1510 // go from CIE LMS 2006 to Kirk/Filmlight Yrg
1511 LMS_to_Yrg(LMS, Yrg);
1512
1513 // rewrite in polar coordinates
1514 Yrg_to_Ych(Yrg, out);
1515}
1516
1517
1518#ifdef _OPENMP
1519#pragma omp declare simd uniform(matrix) aligned(in, out:16) aligned(matrix:64)
1520#endif
1521static inline void Ych_to_pipe_RGB(const dt_aligned_pixel_t in, const dt_colormatrix_t matrix, dt_aligned_pixel_t out)
1522{
1523 dt_aligned_pixel_t LMS = { 0.f };
1524 dt_aligned_pixel_t Yrg = { 0.f };
1525
1526 // rewrite in cartesian coordinates
1527 Ych_to_Yrg(in, Yrg);
1528
1529 // go from Kirk/Filmlight Yrg to CIE LMS 2006
1530 Yrg_to_LMS(Yrg, LMS);
1531
1532 // go from CIE LMS 2006 to pipeline RGB
1533 dot_product(LMS, matrix, out);
1534}
1535
1536static inline void filmic_desaturate_v4(const dt_aligned_pixel_t Ych_original, dt_aligned_pixel_t Ych_final, const float saturation)
1537{
1538 // Note : Ych is normalized trough the LMS conversion,
1539 // meaning c is actually a saturation (saturation ~= chroma / brightness).
1540 // So copy-pasting c and h from a different Y is equivalent to
1541 // tonemapping with a norm, which is equivalent to doing exposure compensation :
1542 // it's saturation-invariant, aka chroma will get increased
1543 // if Y is increased, and the other way around.
1544 const float chroma_original = Ych_original[1] * Ych_original[0]; // c2
1545 float chroma_final = Ych_final[1] * Ych_final[0]; // c1
1546
1547 // fit a linear model `chroma = f(y)`:
1548 // `chroma = c1 + (yc - y1) * (c2 - c1) / (y2 - y1)`
1549 // where `(yc - y1)` is user-defined as `saturation * (y2 - y1)`
1550 // so `chroma = c1 + saturation * (c2 - c1)`
1551 // when saturation = 0, we stay at the saturation-invariant final chroma
1552 // when saturation > 0, we go back towards the initial chroma before tone-mapping
1553 // when saturation < 0, we amplify the initial -> final chroma change
1554 const float delta_chroma = saturation * (chroma_original - chroma_final);
1555
1556 const int filmic_brightens = (Ych_final[0] > Ych_original[0]);
1557 const int filmic_resat = (chroma_original < chroma_final);
1558 const int filmic_desat = (chroma_original > chroma_final);
1559 const int user_resat = (saturation > 0.f);
1560 const int user_desat = (saturation < 0.f);
1561
1562 chroma_final = (filmic_brightens && filmic_resat)
1563 ? (chroma_original + chroma_final) / 2.f // force original lower sat if brightening
1564 : ((user_resat && filmic_desat) || user_desat)
1565 ? chroma_final + delta_chroma // allow resaturation only if filmic desaturated, allow desat anytime
1566 : chroma_final;
1567
1568 Ych_final[1] = fmaxf(chroma_final / Ych_final[0], 0.f);
1569}
1570
1571// Pipeline and ICC luminance is CIE Y 1931
1572// Kirk Ych/Yrg uses CIE Y 2006
1573// 1 CIE Y 1931 = 1.05785528 CIE Y 2006, so we need to adjust that.
1574// This also accounts for the CAT16 D50->D65 adaptation that has to be done
1575// to go from RGB to CIE LMS 2006.
1576// Warning: only applies to achromatic pixels.
1577#define CIE_Y_1931_to_CIE_Y_2006(x) (1.05785528f * (x))
1578
1579
1580static inline float clip_chroma_white_raw(const float coeffs[3], const float target_white, const float Y,
1581 const float cos_h, const float sin_h)
1582{
1583 const float denominator_Y_coeff = coeffs[0] * (0.979381443298969f * cos_h + 0.391752577319588f * sin_h)
1584 + coeffs[1] * (0.0206185567010309f * cos_h + 0.608247422680412f * sin_h)
1585 - coeffs[2] * (cos_h + sin_h);
1586 const float denominator_target_term = target_white * (0.68285981628866f * cos_h + 0.482137060515464f * sin_h);
1587
1588 // this channel won't limit the chroma
1589 if(denominator_Y_coeff == 0.f) return FLT_MAX;
1590
1591 // The equation for max chroma has an asymptote at this point (zero of denominator).
1592 // Any Y below that value won't give us sensible results for the upper bound
1593 // and we should consider the lower bound instead.
1594 const float Y_asymptote = denominator_target_term / denominator_Y_coeff;
1595 if(Y <= Y_asymptote) return FLT_MAX;
1596
1597 // Get chroma that brings one component of target RGB to the given target_rgb value.
1598 // coeffs are the transformation coeffs to get one components (R, G or B) from input LMS.
1599 // i.e. it is a row of the LMS -> RGB transformation matrix.
1600 // See tools/derive_filmic_v6_gamut_mapping.py for derivation of these equations.
1601 const float denominator = Y * denominator_Y_coeff - denominator_target_term;
1602 const float numerator = -0.427506877216495f
1603 * (Y * (coeffs[0] + 0.856492345150334f * coeffs[1] + 0.554995960637719f * coeffs[2])
1604 - 0.988237752433297f * target_white);
1605
1606 return numerator / denominator;
1607}
1608
1609
1610static inline float clip_chroma_white(const float coeffs[3], const float target_white, const float Y,
1611 const float cos_h, const float sin_h)
1612{
1613 // Due to slight numerical inaccuracies in color matrices,
1614 // the chroma clipping curves for each RGB channel may be
1615 // slightly at the max luminance. Thus we linearly interpolate
1616 // each clipping line to zero chroma near max luminance.
1617 const float eps = 1e-3f;
1618 const float max_Y = CIE_Y_1931_to_CIE_Y_2006(target_white);
1619 const float delta_Y = MAX(max_Y - Y, 0.f);
1620 float max_chroma;
1621 if(delta_Y < eps)
1622 {
1623 max_chroma = delta_Y / (eps * max_Y) * clip_chroma_white_raw(coeffs, target_white, (1.f - eps) * max_Y, cos_h, sin_h);
1624 }
1625 else
1626 {
1627 max_chroma = clip_chroma_white_raw(coeffs, target_white, Y, cos_h, sin_h);
1628 }
1629 return max_chroma >= 0.f ? max_chroma : FLT_MAX;
1630}
1631
1632
1633static inline float clip_chroma_black(const float coeffs[3], const float cos_h, const float sin_h)
1634{
1635 // N.B. this is the same as clip_chroma_white_raw() but with target value = 0.
1636 // This allows eliminating some computation.
1637
1638 // Get chroma that brings one component of target RGB to zero.
1639 // coeffs are the transformation coeffs to get one components (R, G or B) from input LMS.
1640 // i.e. it is a row of the LMS -> RGB transformation matrix.
1641 // See tools/derive_filmic_v6_gamut_mapping.py for derivation of these equations.
1642 const float denominator = coeffs[0] * (0.979381443298969f * cos_h + 0.391752577319588f * sin_h)
1643 + coeffs[1] * (0.0206185567010309f * cos_h + 0.608247422680412f * sin_h)
1644 - coeffs[2] * (cos_h + sin_h);
1645
1646 // this channel won't limit the chroma
1647 if(denominator == 0.f) return FLT_MAX;
1648
1649 const float numerator = -0.427506877216495f * (coeffs[0] + 0.856492345150334f * coeffs[1] + 0.554995960637719f * coeffs[2]);
1650 const float max_chroma = numerator / denominator;
1651 return max_chroma >= 0.f ? max_chroma : FLT_MAX;
1652}
1653
1654
1655static inline float clip_chroma(const dt_colormatrix_t matrix_out, const float target_white, const float Y,
1656 const float cos_h, const float sin_h, const float chroma)
1657{
1658 // Note: ideally we should figure out in advance which channel is going to clip first
1659 // (either go negative or over maximum allowed value) and calculate chroma clipping
1660 // curves only for those channels. That would avoid some ambiguities
1661 // (what do negative chroma values mean etc.) and reduce computation. However this
1662 // "brute-force" approach seems to work fine for now.
1663
1664 const float chroma_R_white = clip_chroma_white(matrix_out[0], target_white, Y, cos_h, sin_h);
1665 const float chroma_G_white = clip_chroma_white(matrix_out[1], target_white, Y, cos_h, sin_h);
1666 const float chroma_B_white = clip_chroma_white(matrix_out[2], target_white, Y, cos_h, sin_h);
1667 const float max_chroma_white = MIN(MIN(chroma_R_white, chroma_G_white), chroma_B_white);
1668
1669 const float chroma_R_black = clip_chroma_black(matrix_out[0], cos_h, sin_h);
1670 const float chroma_G_black = clip_chroma_black(matrix_out[1], cos_h, sin_h);
1671 const float chroma_B_black = clip_chroma_black(matrix_out[2], cos_h, sin_h);
1672 const float max_chroma_black = MIN(MIN(chroma_R_black, chroma_G_black), chroma_B_black);
1673
1674 return MIN(MIN(chroma, max_chroma_black), max_chroma_white);
1675}
1676
1677
1678static inline void gamut_check_RGB(const dt_colormatrix_t matrix_in, const dt_colormatrix_t matrix_out,
1679 const float display_black, const float display_white,
1680 const dt_aligned_pixel_t Ych_in, dt_aligned_pixel_t RGB_out)
1681{
1682 // Heuristic: if there are negatives, calculate the amount (luminance) of white light that
1683 // would need to be mixed in to bring the pixel back in gamut.
1684 dt_aligned_pixel_t RGB_brightened = { 0.f };
1685 Ych_to_pipe_RGB(Ych_in, matrix_out, RGB_brightened);
1686 const float min_pix = MIN(MIN(RGB_brightened[0], RGB_brightened[1]), RGB_brightened[2]);
1687 const float black_offset = MAX(-min_pix, 0.f);
1688 for_each_channel(c) RGB_brightened[c] += black_offset;
1689 dt_aligned_pixel_t Ych_brightened = { 0.f };
1690 pipe_RGB_to_Ych(RGB_brightened, matrix_in, Ych_brightened);
1691
1692 // Increase the input luminance a little by the value we calculated above.
1693 // Note, however, that this doesn't actually desaturate the color like mixing
1694 // white would do. We will next find the chroma change needed to bring the pixel
1695 // into gamut.
1696 const float Y = CLAMP((Ych_in[0] + Ych_brightened[0]) / 2.f, CIE_Y_1931_to_CIE_Y_2006(display_black), CIE_Y_1931_to_CIE_Y_2006(display_white));
1697 // Precompute sin and cos of hue for reuse
1698 const float cos_h = cosf(Ych_in[2]);
1699 const float sin_h = sinf(Ych_in[2]);
1700 const float new_chroma = clip_chroma(matrix_out, display_white, Y, cos_h, sin_h, Ych_in[1]);
1701
1702 // Go to RGB, using existing luminance and hue and the new chroma
1703 const dt_aligned_pixel_t Ych = { Y, new_chroma, Ych_in[2], 0.f };
1704 Ych_to_pipe_RGB(Ych, matrix_out, RGB_out);
1705
1706 // Clamp in target RGB as a final catch-all
1707 for_each_channel(c, aligned(RGB_out)) RGB_out[c] = CLAMP(RGB_out[c], 0.f, display_white);
1708}
1709
1710
1711#ifdef _OPENMP
1712#pragma omp declare simd uniform(input_matrix, output_matrix, export_input_matrix, export_output_matrix, use_output_profile) \
1713 aligned(Ych_final, Ych_original, pix_out:16) aligned(input_matrix, output_matrix, export_input_matrix, export_output_matrix:64)
1714#endif
1715static inline void gamut_mapping(dt_aligned_pixel_t Ych_final, dt_aligned_pixel_t Ych_original, dt_aligned_pixel_t pix_out,
1716 const dt_colormatrix_t input_matrix, const dt_colormatrix_t output_matrix,
1717 const dt_colormatrix_t export_input_matrix, const dt_colormatrix_t export_output_matrix,
1718 const float display_black, const float display_white, const float saturation,
1719 const int use_output_profile)
1720{
1721 // Force final hue to original
1722 Ych_final[2] = Ych_original[2];
1723
1724 // Clip luminance
1725 Ych_final[0] = CLAMP(Ych_final[0],
1726 CIE_Y_1931_to_CIE_Y_2006(display_black),
1727 CIE_Y_1931_to_CIE_Y_2006(display_white));
1728
1729 // Massage chroma
1730 filmic_desaturate_v4(Ych_original, Ych_final, saturation);
1731 gamut_check_Yrg(Ych_final);
1732
1733 if(!use_output_profile)
1734 {
1735 // Now, it is still possible that one channel > display white because of saturation.
1736 // We have already clipped Y, so we know that any problem now is caused by c
1737 gamut_check_RGB(input_matrix, output_matrix, display_black, display_white, Ych_final, pix_out);
1738 }
1739 else
1740 {
1741 // Now, it is still possible that one channel > display white because of saturation.
1742 // We have already clipped Y, so we know that any problem now is caused by c
1743 gamut_check_RGB(export_input_matrix, export_output_matrix, display_black, display_white, Ych_final, pix_out);
1744
1745 // Go from export RGB to CIE LMS 2006 D65
1746 dt_aligned_pixel_t LMS = { 0.f };
1747 dot_product(pix_out, export_input_matrix, LMS);
1748
1749 // Go from CIE LMS 2006 D65 to pipeline RGB D50
1750 dot_product(LMS, output_matrix, pix_out);
1751 }
1752}
1753
1754
1755static int filmic_v4_prepare_matrices(dt_colormatrix_t input_matrix, dt_colormatrix_t output_matrix,
1756 dt_colormatrix_t export_input_matrix, dt_colormatrix_t export_output_matrix,
1757 const dt_iop_order_iccprofile_info_t *const work_profile,
1758 const dt_iop_order_iccprofile_info_t *const export_profile)
1759
1760{
1761 dt_colormatrix_t temp_matrix;
1762
1763 // Prepare the pipeline RGB (D50) -> XYZ D50 -> XYZ D65 -> LMS 2006 matrix
1764 dt_colormatrix_mul(temp_matrix, XYZ_D50_to_D65_CAT16, work_profile->matrix_in);
1765 dt_colormatrix_mul(input_matrix, XYZ_D65_to_LMS_2006_D65, temp_matrix);
1766
1767 // Prepare the LMS 2006 -> XYZ D65 -> XYZ D50 -> pipeline RGB matrix (D50)
1769 dt_colormatrix_mul(output_matrix, work_profile->matrix_out, temp_matrix);
1770
1771 // If the pipeline output profile is supported (matrix profile), we gamut map against it
1772 const int use_output_profile = (export_profile != NULL);
1773 if(use_output_profile)
1774 {
1775 // Prepare the LMS 2006 -> XYZ D65 -> XYZ D50 -> output RGB (D50) matrix
1777 dt_colormatrix_mul(export_output_matrix, export_profile->matrix_out, temp_matrix);
1778
1779 // Prepare the output RGB (D50) -> XYZ D50 -> XYZ D65 -> LMS 2006 matrix
1780 dt_colormatrix_mul(temp_matrix, XYZ_D50_to_D65_CAT16, export_profile->matrix_in);
1781 dt_colormatrix_mul(export_input_matrix, XYZ_D65_to_LMS_2006_D65, temp_matrix);
1782 }
1783
1784 return use_output_profile;
1785}
1786
1787#ifdef _OPENMP
1788#pragma omp declare simd uniform(work_profile, data, spline, norm_min, norm_max, display_black, display_white, type) \
1789 aligned(pix_in, pix_out:16)
1790#endif
1791static inline void norm_tone_mapping_v4(const dt_aligned_pixel_t pix_in, dt_aligned_pixel_t pix_out,
1793 const dt_iop_order_iccprofile_info_t *const work_profile,
1794 const dt_iop_filmicrgb_data_t *const data,
1795 const dt_iop_filmic_rgb_spline_t spline,
1796 const float norm_min, const float norm_max,
1797 const float display_black, const float display_white)
1798 {
1799 // Norm must be clamped early to the valid input range, otherwise it will be clamped
1800 // later in log_tonemapping and the ratios will be then incorrect.
1801 // This would result in colorful patches darker than their surrounding in places
1802 // where the raw data is clipped.
1803 float norm = CLAMPF(get_pixel_norm(pix_in, type, work_profile), norm_min, norm_max);
1804
1805 // Save the ratios
1806 dt_aligned_pixel_t ratios = { 0.0f };
1807 for_each_channel(c,aligned(pix_in)) ratios[c] = pix_in[c] / norm;
1808
1809 // Log tone-mapping
1810 norm = log_tonemapping(norm, data->grey_source, data->black_source, data->dynamic_range);
1811
1812 // Filmic S curve on the max RGB
1813 // Apply the transfer function of the display
1814 norm = powf(CLAMP(filmic_spline(norm, spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1815 spline.latitude_min, spline.latitude_max, spline.type),
1816 spline.y[0],
1817 spline.y[4]),
1818 data->output_power);
1819
1820 // Restore RGB
1821 for_each_channel(c,aligned(pix_out)) pix_out[c] = ratios[c] * norm;
1822}
1823
1824#ifdef _OPENMP
1825#pragma omp declare simd uniform(data, spline, display_black, display_white) \
1826 aligned(pix_in, pix_out:16)
1827#endif
1828static inline void RGB_tone_mapping_v4(const dt_aligned_pixel_t pix_in, dt_aligned_pixel_t pix_out,
1829 const dt_iop_filmicrgb_data_t *const data,
1830 const dt_iop_filmic_rgb_spline_t spline,
1831 const float display_black, const float display_white)
1832{
1833 for_each_channel(c,aligned(pix_in, pix_out))
1834 {
1835 // Log tone-mapping
1836 pix_out[c] = log_tonemapping(pix_in[c], data->grey_source, data->black_source, data->dynamic_range);
1837
1838 // Filmic S curve on RGB
1839 // Apply the transfer function of the display
1840 pix_out[c] = powf(CLAMP(filmic_spline(pix_out[c], spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1841 spline.latitude_min, spline.latitude_max, spline.type),
1842 0.f, // individual components can always go to zero, luminance is clamped later
1843 spline.y[4]),
1844 data->output_power);
1845 }
1846}
1847
1848static inline void filmic_chroma_v4(const float *const restrict in, float *const restrict out,
1849 const dt_iop_order_iccprofile_info_t *const work_profile,
1850 const dt_iop_order_iccprofile_info_t *const export_profile,
1851 const dt_iop_filmicrgb_data_t *const data,
1852 const dt_iop_filmic_rgb_spline_t spline, const int variant,
1853 const size_t width, const size_t height, const size_t ch,
1854 const dt_iop_filmicrgb_colorscience_type_t colorscience_version,
1855 const float display_black, const float display_white)
1856{
1857 // See colorbalancergb.c for details
1858 dt_colormatrix_t input_matrix; // pipeline RGB -> LMS 2006
1859 dt_colormatrix_t output_matrix; // LMS 2006 -> pipeline RGB
1860 dt_colormatrix_t export_input_matrix; // output RGB -> LMS 2006
1861 dt_colormatrix_t export_output_matrix; // LMS 2006 -> output RGB
1862
1863 const int use_output_profile = filmic_v4_prepare_matrices(input_matrix, output_matrix, export_input_matrix,
1864 export_output_matrix, work_profile, export_profile);
1865
1866 const float norm_min = exp_tonemapping_v2(0.f, data->grey_source, data->black_source, data->dynamic_range);
1867 const float norm_max = exp_tonemapping_v2(1.f, data->grey_source, data->black_source, data->dynamic_range);
1868
1869#ifdef _OPENMP
1870#pragma omp parallel for default(none) \
1871 dt_omp_firstprivate(width, height, ch, data, in, out, work_profile, input_matrix, output_matrix, \
1872 variant, spline, display_white, display_black, export_input_matrix, export_output_matrix, \
1873 use_output_profile, norm_min, norm_max) \
1874 schedule(simd :static)
1875#endif
1876 for(size_t k = 0; k < height * width * ch; k += ch)
1877 {
1878 const float *const restrict pix_in = in + k;
1879 float *const restrict pix_out = out + k;
1880
1881 norm_tone_mapping_v4(pix_in, pix_out, variant, work_profile, data, spline, norm_min, norm_max, display_black, display_white);
1882
1883 // Save Ych in Kirk/Filmlight Yrg
1884 dt_aligned_pixel_t Ych_original = { 0.f };
1885 pipe_RGB_to_Ych(pix_in, input_matrix, Ych_original);
1886
1887 // Get final Ych in Kirk/Filmlight Yrg
1888 dt_aligned_pixel_t Ych_final = { 0.f };
1889 pipe_RGB_to_Ych(pix_out, input_matrix, Ych_final);
1890
1891 gamut_mapping(Ych_final, Ych_original, pix_out, input_matrix, output_matrix, export_input_matrix,
1892 export_output_matrix, display_black, display_white, data->saturation, use_output_profile);
1893 }
1894}
1895
1896static inline void filmic_split_v4(const float *const restrict in, float *const restrict out,
1897 const dt_iop_order_iccprofile_info_t *const work_profile,
1898 const dt_iop_order_iccprofile_info_t *const export_profile,
1899 const dt_iop_filmicrgb_data_t *const data,
1900 const dt_iop_filmic_rgb_spline_t spline, const int variant,
1901 const size_t width, const size_t height, const size_t ch,
1902 const dt_iop_filmicrgb_colorscience_type_t colorscience_version,
1903 const float display_black, const float display_white)
1904
1905{
1906 // See colorbalancergb.c for details
1907 dt_colormatrix_t input_matrix; // pipeline RGB -> LMS 2006
1908 dt_colormatrix_t output_matrix; // LMS 2006 -> pipeline RGB
1909 dt_colormatrix_t export_input_matrix; // output RGB -> LMS 2006
1910 dt_colormatrix_t export_output_matrix; // LMS 2006 -> output RGB
1911
1912 const int use_output_profile = filmic_v4_prepare_matrices(input_matrix, output_matrix, export_input_matrix,
1913 export_output_matrix, work_profile, export_profile);
1914#ifdef _OPENMP
1915#pragma omp parallel for default(none) \
1916 dt_omp_firstprivate(width, height, ch, data, in, out, work_profile, input_matrix, output_matrix, \
1917 variant, spline, display_white, display_black, export_input_matrix, export_output_matrix, \
1918 use_output_profile) \
1919 schedule(simd :static)
1920#endif
1921 for(size_t k = 0; k < height * width * ch; k += ch)
1922 {
1923 const float *const restrict pix_in = in + k;
1924 float *const restrict pix_out = out + k;
1925
1926 RGB_tone_mapping_v4(pix_in, pix_out, data, spline, display_black, display_white);
1927
1928 // Save Ych in Kirk/Filmlight Yrg
1929 dt_aligned_pixel_t Ych_original = { 0.f };
1930 pipe_RGB_to_Ych(pix_in, input_matrix, Ych_original);
1931
1932 // Get final Ych in Kirk/Filmlight Yrg
1933 dt_aligned_pixel_t Ych_final = { 0.f };
1934 pipe_RGB_to_Ych(pix_out, input_matrix, Ych_final);
1935
1936 Ych_final[1] = fminf(Ych_original[1], Ych_final[1]);
1937
1938 gamut_mapping(Ych_final, Ych_original, pix_out, input_matrix, output_matrix, export_input_matrix,
1939 export_output_matrix, display_black, display_white, data->saturation, use_output_profile);
1940 }
1941}
1942
1943
1944static inline void filmic_v5(const float *const restrict in, float *const restrict out,
1945 const dt_iop_order_iccprofile_info_t *const work_profile,
1946 const dt_iop_order_iccprofile_info_t *const export_profile,
1947 const dt_iop_filmicrgb_data_t *const data,
1948 const dt_iop_filmic_rgb_spline_t spline,
1949 const size_t width, const size_t height, const size_t ch,
1950 const float display_black, const float display_white)
1951
1952{
1953 // See colorbalancergb.c for details
1954 dt_colormatrix_t input_matrix; // pipeline RGB -> LMS 2006
1955 dt_colormatrix_t output_matrix; // LMS 2006 -> pipeline RGB
1956 dt_colormatrix_t export_input_matrix; // output RGB -> LMS 2006
1957 dt_colormatrix_t export_output_matrix; // LMS 2006 -> output RGB
1958
1959 const int use_output_profile = filmic_v4_prepare_matrices(input_matrix, output_matrix, export_input_matrix,
1960 export_output_matrix, work_profile, export_profile);
1961
1962 const float norm_min = exp_tonemapping_v2(0.f, data->grey_source, data->black_source, data->dynamic_range);
1963 const float norm_max = exp_tonemapping_v2(1.f, data->grey_source, data->black_source, data->dynamic_range);
1964
1965#ifdef _OPENMP
1966#pragma omp parallel for default(none) \
1967 dt_omp_firstprivate(width, height, ch, data, in, out, work_profile, input_matrix, output_matrix, \
1968 spline, display_white, display_black, norm_min, norm_max, export_input_matrix, export_output_matrix, \
1969 use_output_profile) \
1970 schedule(simd :static)
1971#endif
1972 for(size_t k = 0; k < height * width * ch; k += ch)
1973 {
1974 const float *const restrict pix_in = in + k;
1975 float *const restrict pix_out = out + k;
1976
1977 dt_aligned_pixel_t max_rgb = { 0.f };
1978 dt_aligned_pixel_t naive_rgb = { 0.f };
1979
1980 RGB_tone_mapping_v4(pix_in, naive_rgb, data, spline, display_black, display_white);
1981 norm_tone_mapping_v4(pix_in, max_rgb, DT_FILMIC_METHOD_MAX_RGB, work_profile, data, spline, norm_min, norm_max, display_black, display_white);
1982
1983 // Mix max RGB with naive RGB
1984 for_each_channel(c, aligned(pix_out, max_rgb, naive_rgb))
1985 pix_out[c] = (0.5f - data->saturation) * naive_rgb[c] + (0.5f + data->saturation) * max_rgb[c];
1986
1987 // Save Ych in Kirk/Filmlight Yrg
1988 dt_aligned_pixel_t Ych_original = { 0.f };
1989 pipe_RGB_to_Ych(pix_in, input_matrix, Ych_original);
1990
1991 // Get final Ych in Kirk/Filmlight Yrg
1992 dt_aligned_pixel_t Ych_final = { 0.f };
1993 pipe_RGB_to_Ych(pix_out, input_matrix, Ych_final);
1994
1995 Ych_final[1] = fminf(Ych_original[1], Ych_final[1]);
1996
1997 gamut_mapping(Ych_final, Ych_original, pix_out, input_matrix, output_matrix, export_input_matrix,
1998 export_output_matrix, display_black, display_white, 0.f, use_output_profile);
1999 }
2000}
2001
2002
2003static inline void display_mask(const float *const restrict mask, float *const restrict out, const size_t width,
2004 const size_t height)
2005{
2006#ifdef _OPENMP
2007#pragma omp parallel for default(none) dt_omp_firstprivate(width, height, out, mask) schedule(static)
2008#endif
2009 for(size_t k = 0; k < height * width; k++)
2010 {
2011 for_each_channel(c,aligned(out))
2012 out[4*k+c] = mask[k];
2013 }
2014}
2015
2016
2017static inline void compute_ratios(const float *const restrict in, float *const restrict norms,
2018 float *const restrict ratios,
2019 const dt_iop_order_iccprofile_info_t *const work_profile, const int variant,
2020 const size_t width, const size_t height)
2021{
2022#ifdef _OPENMP
2023#pragma omp parallel for default(none) \
2024 dt_omp_firstprivate(width, height, norms, ratios, in, work_profile, variant) schedule(static)
2025#endif
2026 for(size_t k = 0; k < height * width * 4; k += 4)
2027 {
2028 const float norm = fmaxf(get_pixel_norm(in + k, variant, work_profile), NORM_MIN);
2029 norms[k / 4] = norm;
2030 for_each_channel(c,aligned(ratios,in))
2031 ratios[k + c] = in[k + c] / norm;
2032 }
2033}
2034
2035
2036static inline void restore_ratios(float *const restrict ratios, const float *const restrict norms,
2037 const size_t width, const size_t height)
2038{
2039 #ifdef _OPENMP
2040 #pragma omp parallel for default(none) \
2041 dt_omp_firstprivate(width, height, norms, ratios) \
2042 schedule(simd:static)
2043 #endif
2044 for(size_t k = 0; k < height * width; k++)
2045 for_each_channel(c,aligned(norms,ratios))
2046 ratios[4*k + c] = clamp_simd(ratios[4*k + c]) * norms[k];
2047}
2048
2050 const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out,
2052{
2053 const int scales = get_scales(roi_in, piece);
2054 const int max_filter_radius = (1 << scales);
2055
2056 // in + out + 2 * tmp + 2 * LF + 2 * temp + ratios
2057 tiling->factor = 9.0f;
2058 tiling->factor_cl = 9.0f;
2059
2060 tiling->maxbuf = 1.0f;
2061 tiling->maxbuf_cl = 1.0f;
2062 tiling->overhead = 0;
2063 tiling->overlap = max_filter_radius;
2064 tiling->xalign = 1;
2065 tiling->yalign = 1;
2066 return;
2067}
2068
2069void process(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid,
2070 void *const restrict ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
2071{
2072 const dt_iop_filmicrgb_data_t *const data = (dt_iop_filmicrgb_data_t *)piece->data;
2075
2076 if(piece->colors != 4)
2077 {
2078 dt_control_log(_("filmic works only on RGB input"));
2079 return;
2080 }
2081
2082 const size_t ch = 4;
2083
2093 float *restrict in = (float *)ivoid;
2094 float *const restrict out = (float *)ovoid;
2095 float *const restrict mask = dt_alloc_sse_ps((size_t)roi_out->width * roi_out->height);
2096
2097 // used to adjuste noise level depending on size. Don't amplify noise if magnified > 100%
2098 const float scale = fmaxf(piece->iscale / roi_in->scale, 1.f);
2099
2100 // build a mask of clipped pixels
2101 const int recover_highlights = mask_clipped_pixels(in, mask, data->normalize, data->reconstruct_feather, roi_out->width, roi_out->height, 4);
2102
2103 // display mask and exit
2104 if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL && mask)
2105 {
2107
2108 if(g->show_mask)
2109 {
2110 display_mask(mask, out, roi_out->width, roi_out->height);
2111 dt_free_align(mask);
2112 return;
2113 }
2114 }
2115
2116 float *const restrict reconstructed = dt_alloc_align_float((size_t)roi_out->width * roi_out->height * 4);
2117
2118 // if fast mode is not in use
2119 if(recover_highlights && mask && reconstructed)
2120 {
2121 // init the blown areas with noise to create particles
2122 float *const restrict inpainted = dt_alloc_align_float((size_t)roi_out->width * roi_out->height * 4);
2123 inpaint_noise(in, mask, inpainted, data->noise_level / scale, data->reconstruct_threshold, data->noise_distribution,
2124 roi_out->width, roi_out->height);
2125
2126 // diffuse particles with wavelets reconstruction
2127 // PASS 1 on RGB channels
2128 const gint success_1 = reconstruct_highlights(inpainted, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RGB, ch, data, piece, roi_in, roi_out);
2129 gint success_2 = TRUE;
2130
2131 dt_free_align(inpainted);
2132
2133 if(data->high_quality_reconstruction > 0 && success_1)
2134 {
2135 float *const restrict norms = dt_alloc_align_float((size_t)roi_out->width * roi_out->height);
2136 float *const restrict ratios = dt_alloc_align_float((size_t)roi_out->width * roi_out->height * 4);
2137
2138 // reconstruct highlights PASS 2 on ratios
2139 if(norms && ratios)
2140 {
2141 for(int i = 0; i < data->high_quality_reconstruction; i++)
2142 {
2143 compute_ratios(reconstructed, norms, ratios, work_profile, DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1,
2144 roi_out->width, roi_out->height);
2145 success_2 = success_2
2146 && reconstruct_highlights(ratios, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RATIOS, ch,
2147 data, piece, roi_in, roi_out);
2148 restore_ratios(reconstructed, norms, roi_out->width, roi_out->height);
2149 }
2150 }
2151
2152 if(norms) dt_free_align(norms);
2153 if(ratios) dt_free_align(ratios);
2154 }
2155
2156 if(success_1 && success_2) in = reconstructed; // use reconstructed buffer as tonemapping input
2157 }
2158
2159 if(mask) dt_free_align(mask);
2160
2161 const float white_display = powf(data->spline.y[4], data->output_power);
2162 const float black_display = powf(data->spline.y[0], data->output_power);
2163
2165 {
2166 filmic_v5(in, out, work_profile, export_profile, data, data->spline, roi_out->width,
2167 roi_out->height, ch, black_display, white_display);
2168 }
2169 else
2170 {
2172 {
2173 // no chroma preservation
2175 filmic_split_v1(in, out, work_profile, data, data->spline, roi_out->width, roi_in->height);
2177 filmic_split_v2_v3(in, out, work_profile, data, data->spline, roi_out->width, roi_in->height);
2178 else if(data->version == DT_FILMIC_COLORSCIENCE_V4)
2179 filmic_split_v4(in, out, work_profile, export_profile, data, data->spline, data->preserve_color, roi_out->width,
2180 roi_out->height, ch, data->version, black_display, white_display);
2181 }
2182 else
2183 {
2184 // chroma preservation
2186 filmic_chroma_v1(in, out, work_profile, data, data->spline, data->preserve_color, roi_out->width,
2187 roi_out->height);
2189 filmic_chroma_v2_v3(in, out, work_profile, data, data->spline, data->preserve_color, roi_out->width,
2190 roi_out->height, ch, data->version);
2191 else if(data->version == DT_FILMIC_COLORSCIENCE_V4)
2192 filmic_chroma_v4(in, out, work_profile, export_profile, data, data->spline, data->preserve_color, roi_out->width,
2193 roi_out->height, ch, data->version, black_display, white_display);
2194 }
2195 }
2196
2197 if(reconstructed) dt_free_align(reconstructed);
2198
2199 if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK)
2200 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
2201}
2202
2203#ifdef HAVE_OPENCL
2204static inline cl_int reconstruct_highlights_cl(cl_mem in, cl_mem mask, cl_mem reconstructed,
2206 const dt_iop_filmicrgb_data_t *const data, dt_dev_pixelpipe_iop_t *piece,
2207 const dt_iop_roi_t *const roi_in)
2208{
2209 cl_int err = -999;
2210 const int devid = piece->pipe->devid;
2211 const int width = roi_in->width;
2212 const int height = roi_in->height;
2213 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
2214
2215 // wavelets scales
2216 const int scales = get_scales(roi_in, piece);
2217
2218 // wavelets scales buffers
2219 cl_mem LF_even = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // low-frequencies RGB
2220 cl_mem LF_odd = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // low-frequencies RGB
2221 cl_mem HF_RGB = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // high-frequencies RGB
2222 cl_mem HF_grey = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // high-frequencies RGB backup
2223
2224 // alloc a permanent reusable buffer for intermediate computations - avoid multiple alloc/free
2225 cl_mem temp = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);;
2226
2227 if(!LF_even || !LF_odd || !HF_RGB || !HF_grey || !temp)
2228 {
2229 dt_control_log(_("filmic highlights reconstruction failed to allocate memory on GPU"));
2230 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
2231 goto error;
2232 }
2233
2234 // Init reconstructed with valid parts of image
2235 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 0, sizeof(cl_mem), (void *)&in);
2236 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 1, sizeof(cl_mem), (void *)&mask);
2237 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 2, sizeof(cl_mem), (void *)&reconstructed);
2238 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 3, sizeof(int), (void *)&width);
2239 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 4, sizeof(int), (void *)&height);
2241 if(err != CL_SUCCESS) goto error;
2242
2243 // structure inpainting vs. texture duplicating weight
2244 const float gamma = (data->reconstruct_structure_vs_texture);
2245 const float gamma_comp = 1.0f - data->reconstruct_structure_vs_texture;
2246
2247 // colorful vs. grey weight
2248 const float beta = data->reconstruct_grey_vs_color;
2249 const float beta_comp = 1.f - data->reconstruct_grey_vs_color;
2250
2251 // bloom vs reconstruct weight
2252 const float delta = data->reconstruct_bloom_vs_details;
2253
2254 // À trous wavelet decompose
2255 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
2256 // the wavelets decomposition here is the same as the equalizer/atrous module,
2257 // but simplified because we don't need the edge-aware term, so we can separate the convolution kernel
2258 // with a vertical and horizontal blur, which is 10 multiply-add instead of 25 by pixel.
2259 for(int s = 0; s < scales; ++s)
2260 {
2261 cl_mem detail;
2262 cl_mem LF;
2263
2264 // swap buffers so we only need 2 LF buffers : the LF at scale (s-1) and the one at current scale (s)
2265 if(s == 0)
2266 {
2267 detail = in;
2268 LF = LF_odd;
2269 }
2270 else if(s % 2 != 0)
2271 {
2272 detail = LF_odd;
2273 LF = LF_even;
2274 }
2275 else
2276 {
2277 detail = LF_even;
2278 LF = LF_odd;
2279 }
2280
2281 const int mult = 1 << s; // fancy-pants C notation for 2^s with integer type, don't be afraid
2282
2283 // Compute wavelets low-frequency scales
2284 const int clamp_lf = 1;
2285 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 0, sizeof(cl_mem), (void *)&detail);
2286 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 1, sizeof(cl_mem), (void *)&temp);
2287 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 2, sizeof(int), (void *)&width);
2288 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 3, sizeof(int), (void *)&height);
2289 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 4, sizeof(int), (void *)&mult);
2290 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 5, sizeof(int), (void *)&clamp_lf);
2292 if(err != CL_SUCCESS) goto error;
2293
2294 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 0, sizeof(cl_mem), (void *)&temp);
2295 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 1, sizeof(cl_mem), (void *)&LF);
2296 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 2, sizeof(int), (void *)&width);
2297 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 3, sizeof(int), (void *)&height);
2298 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 4, sizeof(int), (void *)&mult);
2299 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 5, sizeof(int), (void *)&clamp_lf);
2301 if(err != CL_SUCCESS) goto error;
2302
2303 // Compute wavelets high-frequency scales and backup the maximum of texture over the RGB channels
2304 // Note : HF_RGB = detail - LF
2305 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 0, sizeof(cl_mem), (void *)&detail);
2306 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 1, sizeof(cl_mem), (void *)&LF);
2307 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 2, sizeof(cl_mem), (void *)&HF_RGB);
2308 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 3, sizeof(int), (void *)&width);
2309 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 4, sizeof(int), (void *)&height);
2311 if(err != CL_SUCCESS) goto error;
2312
2313 // Take a backup copy of HF_RGB in HF_grey - only HF_RGB will be blurred
2314 size_t origin[] = { 0, 0, 0 };
2315 err = dt_opencl_enqueue_copy_image(devid, HF_RGB, HF_grey, origin, origin, sizes);
2316 if(err != CL_SUCCESS) goto error;
2317
2318 // interpolate/blur/inpaint (same thing) the RGB high-frequency to fill holes
2319 const int blur_size = 1;
2320 const int clamp_hf = 0;
2321 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 0, sizeof(cl_mem), (void *)&HF_RGB);
2322 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 1, sizeof(cl_mem), (void *)&temp);
2323 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 2, sizeof(int), (void *)&width);
2324 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 3, sizeof(int), (void *)&height);
2325 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 4, sizeof(int), (void *)&blur_size);
2326 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 5, sizeof(int), (void *)&clamp_hf);
2328 if(err != CL_SUCCESS) goto error;
2329
2330 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 0, sizeof(cl_mem), (void *)&temp);
2331 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 1, sizeof(cl_mem), (void *)&HF_RGB);
2332 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 2, sizeof(int), (void *)&width);
2333 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 3, sizeof(int), (void *)&height);
2334 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 4, sizeof(int), (void *)&blur_size);
2335 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 5, sizeof(int), (void *)&clamp_hf);
2337 if(err != CL_SUCCESS) goto error;
2338
2339 // Reconstruct clipped parts
2340 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 0, sizeof(cl_mem), (void *)&HF_RGB);
2341 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 1, sizeof(cl_mem), (void *)&LF);
2342 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 2, sizeof(cl_mem), (void *)&HF_grey);
2343 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 3, sizeof(cl_mem), (void *)&mask);
2344 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 4, sizeof(cl_mem), (void *)&reconstructed);
2345 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 5, sizeof(cl_mem), (void *)&reconstructed);
2346 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 6, sizeof(int), (void *)&width);
2347 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 7, sizeof(int), (void *)&height);
2348 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 8, sizeof(float), (void *)&gamma);
2349 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 9, sizeof(float), (void *)&gamma_comp);
2350 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 10, sizeof(float), (void *)&beta);
2351 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 11, sizeof(float), (void *)&beta_comp);
2352 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 12, sizeof(float), (void *)&delta);
2353 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 13, sizeof(int), (void *)&s);
2354 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 14, sizeof(int), (void *)&scales);
2355 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 15, sizeof(int), (void *)&variant);
2357 if(err != CL_SUCCESS) goto error;
2358 }
2359
2360error:
2366 return err;
2367}
2368
2369
2370int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
2371 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
2372{
2373 const dt_iop_filmicrgb_data_t *const d = (dt_iop_filmicrgb_data_t *)piece->data;
2374 dt_iop_filmicrgb_global_data_t *const gd = (dt_iop_filmicrgb_global_data_t *)self->global_data;
2375
2376 cl_int err = -999;
2377
2378 if(piece->colors != 4)
2379 {
2380 dt_control_log(_("filmic works only on RGB input"));
2381 return err;
2382 }
2383
2384 const int devid = piece->pipe->devid;
2385 const int width = roi_in->width;
2386 const int height = roi_in->height;
2387
2388 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
2389
2390 cl_mem in = dev_in;
2391 cl_mem inpainted = NULL;
2392 cl_mem reconstructed = NULL;
2393 cl_mem mask = NULL;
2394 cl_mem ratios = NULL;
2395 cl_mem norms = NULL;
2396
2397 // fetch working color profile
2400 const int use_work_profile = (work_profile == NULL) ? 0 : 1;
2401
2402 // See colorbalancergb.c for details
2403 dt_colormatrix_t input_matrix; // pipeline RGB -> LMS 2006
2404 dt_colormatrix_t output_matrix; // LMS 2006 -> pipeline RGB
2405 dt_colormatrix_t export_input_matrix; // output RGB -> LMS 2006
2406 dt_colormatrix_t export_output_matrix; // LMS 2006 -> output RGB
2407
2408 const int use_output_profile = filmic_v4_prepare_matrices(input_matrix, output_matrix, export_input_matrix,
2409 export_output_matrix, work_profile, export_profile);
2410
2411 const float norm_min = exp_tonemapping_v2(0.f, d->grey_source, d->black_source, d->dynamic_range);
2412 const float norm_max = exp_tonemapping_v2(1.f, d->grey_source, d->black_source, d->dynamic_range);
2413
2414 cl_mem input_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, 12 * sizeof(float), input_matrix);
2415 cl_mem output_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, 12 * sizeof(float), output_matrix);
2416 cl_mem export_input_matrix_cl = NULL;
2417 cl_mem export_output_matrix_cl = NULL;
2418
2419 cl_mem dev_profile_info = NULL;
2420 cl_mem dev_profile_lut = NULL;
2421 dt_colorspaces_iccprofile_info_cl_t *profile_info_cl;
2422 cl_float *profile_lut_cl = NULL;
2423
2424 cl_mem clipped = NULL;
2425
2426 err = dt_ioppr_build_iccprofile_params_cl(work_profile, devid, &profile_info_cl, &profile_lut_cl,
2427 &dev_profile_info, &dev_profile_lut);
2428 if(err != CL_SUCCESS) goto error;
2429
2430 if(use_output_profile)
2431 {
2432 export_input_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, 12 * sizeof(float), export_input_matrix);
2433 export_output_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, 12 * sizeof(float), export_output_matrix);
2434 }
2435
2436 // used to adjust noise level depending on size. Don't amplify noise if magnified > 100%
2437 const float scale = fmaxf(piece->iscale / roi_in->scale, 1.f);
2438
2439 uint32_t is_clipped = 0;
2440 clipped = dt_opencl_alloc_device_buffer(devid, sizeof(uint32_t));
2441 err = dt_opencl_write_buffer_to_device(devid, &is_clipped, clipped, 0, sizeof(uint32_t), CL_TRUE);
2442 if(err != CL_SUCCESS) goto error;
2443
2444 // build a mask of clipped pixels
2445 mask = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float));
2446 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 0, sizeof(cl_mem), (void *)&in);
2447 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 1, sizeof(cl_mem), (void *)&mask);
2448 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 2, sizeof(int), (void *)&width);
2449 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 3, sizeof(int), (void *)&height);
2450 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 4, sizeof(float), (void *)&d->normalize);
2451 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 5, sizeof(float), (void *)&d->reconstruct_feather);
2452 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 6, sizeof(cl_mem), (void *)&clipped);
2453 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_mask, sizes);
2454 if(err != CL_SUCCESS) goto error;
2455
2456 // check for clipped pixels
2457 err = dt_opencl_read_buffer_from_device(devid, &is_clipped, clipped, 0, sizeof(uint32_t), CL_TRUE);
2458 if(err != CL_SUCCESS) goto error;
2460 clipped = NULL;
2461
2462 // display mask and exit
2463 if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL)
2464 {
2466
2467 if(g->show_mask)
2468 {
2469 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 0, sizeof(cl_mem), (void *)&mask);
2470 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 1, sizeof(cl_mem), (void *)&dev_out);
2471 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 2, sizeof(int), (void *)&width);
2472 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 3, sizeof(int), (void *)&height);
2475 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
2476 return TRUE;
2477 }
2478 }
2479
2480 if(is_clipped > 0)
2481 {
2482 // Inpaint noise
2483 const float noise_level = d->noise_level / scale;
2484 inpainted = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
2485 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 0, sizeof(cl_mem), (void *)&in);
2486 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 1, sizeof(cl_mem), (void *)&mask);
2487 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 2, sizeof(cl_mem), (void *)&inpainted);
2488 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 3, sizeof(int), (void *)&width);
2489 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 4, sizeof(int), (void *)&height);
2490 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 5, sizeof(float), (void *)&noise_level);
2491 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 6, sizeof(float), (void *)&d->reconstruct_threshold);
2492 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 7, sizeof(float), (void *)&d->noise_distribution);
2494 if(err != CL_SUCCESS) goto error;
2495
2496 // first step of highlight reconstruction in RGB
2497 reconstructed = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
2498 err = reconstruct_highlights_cl(inpainted, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RGB, gd, d, piece, roi_in);
2499 if(err != CL_SUCCESS) goto error;
2501 inpainted = NULL;
2502
2503 if(d->high_quality_reconstruction > 0)
2504 {
2505 ratios = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
2506 norms = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float));
2507
2508 if(norms && ratios)
2509 {
2510 for(int i = 0; i < d->high_quality_reconstruction; i++)
2511 {
2512 // break ratios and norms
2513 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 0, sizeof(cl_mem), (void *)&reconstructed);
2514 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 1, sizeof(cl_mem), (void *)&norms);
2515 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 2, sizeof(cl_mem), (void *)&ratios);
2516 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 3, sizeof(int), (void *)&d->preserve_color);
2517 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 4, sizeof(int), (void *)&width);
2518 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 5, sizeof(int), (void *)&height);
2520 if(err != CL_SUCCESS) goto error;
2521
2522 // second step of reconstruction over ratios
2523 err = reconstruct_highlights_cl(ratios, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RATIOS, gd, d, piece, roi_in);
2524 if(err != CL_SUCCESS) goto error;
2525
2526 // restore ratios to RGB
2527 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 0, sizeof(cl_mem), (void *)&reconstructed);
2528 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 1, sizeof(cl_mem), (void *)&norms);
2529 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 2, sizeof(cl_mem), (void *)&reconstructed);
2530 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 3, sizeof(int), (void *)&width);
2531 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 4, sizeof(int), (void *)&height);
2533 if(err != CL_SUCCESS) goto error;
2534 }
2535 }
2536
2539 ratios = NULL;
2540 norms = NULL;
2541 }
2542
2543 in = reconstructed;
2544 }
2545
2546 dt_opencl_release_mem_object(mask); // mask is only used for highlights reconstruction.
2547 mask = NULL;
2548
2550
2551 const float white_display = powf(spline.y[4], d->output_power);
2552 const float black_display = powf(spline.y[0], d->output_power);
2553
2554 if(d->preserve_color == DT_FILMIC_METHOD_NONE && d->version != DT_FILMIC_COLORSCIENCE_V5)
2555 {
2556 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 0, sizeof(cl_mem), (void *)&in);
2557 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 1, sizeof(cl_mem), (void *)&dev_out);
2558 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 2, sizeof(int), (void *)&width);
2559 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 3, sizeof(int), (void *)&height);
2560 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 4, sizeof(float), (void *)&d->dynamic_range);
2561 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 5, sizeof(float), (void *)&d->black_source);
2562 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 6, sizeof(float), (void *)&d->grey_source);
2563 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 7, sizeof(cl_mem), (void *)&dev_profile_info);
2564 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 8, sizeof(cl_mem), (void *)&dev_profile_lut);
2565 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 9, sizeof(int), (void *)&use_work_profile);
2566 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 10, sizeof(float), (void *)&d->sigma_toe);
2567 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 11, sizeof(float), (void *)&d->sigma_shoulder);
2568 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 12, sizeof(float), (void *)&d->saturation);
2569 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 13, 4 * sizeof(float), (void *)&spline.M1);
2570 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 14, 4 * sizeof(float), (void *)&spline.M2);
2571 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 15, 4 * sizeof(float), (void *)&spline.M3);
2572 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 16, 4 * sizeof(float), (void *)&spline.M4);
2573 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 17, 4 * sizeof(float), (void *)&spline.M5);
2574 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 18, sizeof(float), (void *)&spline.latitude_min);
2575 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 19, sizeof(float), (void *)&spline.latitude_max);
2576 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 20, sizeof(float), (void *)&d->output_power);
2577 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 21, sizeof(int), (void *)&d->version);
2578 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 22, sizeof(int), (void *)&spline.type[0]);
2579 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 23, sizeof(int), (void *)&spline.type[1]);
2580 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 24, sizeof(cl_mem), (void *)&input_matrix_cl);
2581 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 25, sizeof(cl_mem), (void *)&output_matrix_cl);
2582 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 26, sizeof(float), (void *)&black_display);
2583 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 27, sizeof(float), (void *)&white_display);
2584 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 28, sizeof(int), (void *)&use_output_profile);
2585 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 29, sizeof(cl_mem), (void *)&export_input_matrix_cl);
2586 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 30, sizeof(cl_mem), (void *)&export_output_matrix_cl);
2587 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 31, sizeof(float), (void *)&spline.y[0]);
2588 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 32, sizeof(float), (void *)&spline.y[4]);
2589
2591 if(err != CL_SUCCESS) goto error;
2592 }
2593 else
2594 {
2595 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 0, sizeof(cl_mem), (void *)&in);
2596 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 1, sizeof(cl_mem), (void *)&dev_out);
2597 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 2, sizeof(int), (void *)&width);
2598 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 3, sizeof(int), (void *)&height);
2599 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 4, sizeof(float), (void *)&d->dynamic_range);
2600 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 5, sizeof(float), (void *)&d->black_source);
2601 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 6, sizeof(float), (void *)&d->grey_source);
2602 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 7, sizeof(cl_mem), (void *)&dev_profile_info);
2603 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 8, sizeof(cl_mem), (void *)&dev_profile_lut);
2604 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 9, sizeof(int), (void *)&use_work_profile);
2605 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 10, sizeof(float), (void *)&d->sigma_toe);
2606 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 11, sizeof(float), (void *)&d->sigma_shoulder);
2607 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 12, sizeof(float), (void *)&d->saturation);
2608 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 13, 4 * sizeof(float), (void *)&spline.M1);
2609 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 14, 4 * sizeof(float), (void *)&spline.M2);
2610 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 15, 4 * sizeof(float), (void *)&spline.M3);
2611 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 16, 4 * sizeof(float), (void *)&spline.M4);
2612 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 17, 4 * sizeof(float), (void *)&spline.M5);
2613 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 18, sizeof(float), (void *)&spline.latitude_min);
2614 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 19, sizeof(float), (void *)&spline.latitude_max);
2615 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 20, sizeof(float), (void *)&d->output_power);
2616 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 21, sizeof(int), (void *)&d->preserve_color);
2617 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 22, sizeof(int), (void *)&d->version);
2618 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 23, sizeof(int), (void *)&spline.type[0]);
2619 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 24, sizeof(int), (void *)&spline.type[1]);
2620 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 25, sizeof(cl_mem), (void *)&input_matrix_cl);
2621 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 26, sizeof(cl_mem), (void *)&output_matrix_cl);
2622 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 27, sizeof(float), (void *)&black_display);
2623 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 28, sizeof(float), (void *)&white_display);
2624 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 29, sizeof(int), (void *)&use_output_profile);
2625 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 30, sizeof(cl_mem), (void *)&export_input_matrix_cl);
2626 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 31, sizeof(cl_mem), (void *)&export_output_matrix_cl);
2627 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 32, sizeof(float), (void *)&norm_min);
2628 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 33, sizeof(float), (void *)&norm_max);
2629 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 34, sizeof(float), (void *)&spline.y[0]);
2630 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 35, sizeof(float), (void *)&spline.y[4]);
2631
2633 if(err != CL_SUCCESS) goto error;
2634 }
2635
2636 dt_opencl_release_mem_object(reconstructed);
2637 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
2638 dt_opencl_release_mem_object(input_matrix_cl);
2639 dt_opencl_release_mem_object(output_matrix_cl);
2640 dt_opencl_release_mem_object(export_input_matrix_cl);
2641 dt_opencl_release_mem_object(export_output_matrix_cl);
2642 return TRUE;
2643
2644error:
2645 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
2646 dt_opencl_release_mem_object(reconstructed);
2651 dt_opencl_release_mem_object(input_matrix_cl);
2652 dt_opencl_release_mem_object(output_matrix_cl);
2653 dt_opencl_release_mem_object(export_input_matrix_cl);
2654 dt_opencl_release_mem_object(export_output_matrix_cl);
2656 dt_print(DT_DEBUG_OPENCL, "[opencl_filmicrgb] couldn't enqueue kernel! %d\n", err);
2657 return FALSE;
2658}
2659#endif
2660
2661
2663{
2664 if(darktable.gui->reset) return;
2667
2668 const dt_iop_order_iccprofile_info_t *const work_profile
2670 const float grey = get_pixel_norm(self->picked_color, p->preserve_color, work_profile) / 2.0f;
2671
2672 const float prev_grey = p->grey_point_source;
2673 p->grey_point_source = CLAMP(100.f * grey, 0.001f, 100.0f);
2674 const float grey_var = log2f(prev_grey / p->grey_point_source);
2675 p->black_point_source = p->black_point_source - grey_var;
2676 p->white_point_source = p->white_point_source + grey_var;
2677 p->output_power = logf(p->grey_point_target / 100.0f)
2678 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
2679
2680 ++darktable.gui->reset;
2681 dt_bauhaus_slider_set(g->grey_point_source, p->grey_point_source);
2682 dt_bauhaus_slider_set(g->black_point_source, p->black_point_source);
2683 dt_bauhaus_slider_set(g->white_point_source, p->white_point_source);
2684 dt_bauhaus_slider_set(g->output_power, p->output_power);
2685 --darktable.gui->reset;
2686
2687 gtk_widget_queue_draw(self->widget);
2689}
2690
2692{
2693 if(darktable.gui->reset) return;
2696
2697 // Black
2698 const dt_iop_order_iccprofile_info_t *const work_profile
2700 const float black = get_pixel_norm(self->picked_color_min, DT_FILMIC_METHOD_MAX_RGB, work_profile);
2701
2702 float EVmin = CLAMP(log2f(black / (p->grey_point_source / 100.0f)), -16.0f, -1.0f);
2703 EVmin *= (1.0f + p->security_factor / 100.0f);
2704
2705 p->black_point_source = fmaxf(EVmin, -16.0f);
2706 p->output_power = logf(p->grey_point_target / 100.0f)
2707 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
2708
2709 ++darktable.gui->reset;
2710 dt_bauhaus_slider_set(g->black_point_source, p->black_point_source);
2711 dt_bauhaus_slider_set(g->output_power, p->output_power);
2712 --darktable.gui->reset;
2713
2714 gtk_widget_queue_draw(self->widget);
2716}
2717
2718
2720{
2721 if(darktable.gui->reset) return;
2724
2725 // White
2726 const dt_iop_order_iccprofile_info_t *const work_profile
2728 const float white = get_pixel_norm(self->picked_color_max, DT_FILMIC_METHOD_MAX_RGB, work_profile);
2729
2730 float EVmax = CLAMP(log2f(white / (p->grey_point_source / 100.0f)), 1.0f, 16.0f);
2731 EVmax *= (1.0f + p->security_factor / 100.0f);
2732
2733 p->white_point_source = EVmax;
2734 p->output_power = logf(p->grey_point_target / 100.0f)
2735 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
2736
2737 ++darktable.gui->reset;
2738 dt_bauhaus_slider_set(g->white_point_source, p->white_point_source);
2739 dt_bauhaus_slider_set(g->output_power, p->output_power);
2740 --darktable.gui->reset;
2741
2742 gtk_widget_queue_draw(self->widget);
2744}
2745
2747{
2750 const dt_iop_order_iccprofile_info_t *const work_profile
2752
2753 // Grey
2754 if(p->custom_grey)
2755 {
2756 const float grey = get_pixel_norm(self->picked_color, p->preserve_color, work_profile) / 2.0f;
2757 p->grey_point_source = CLAMP(100.f * grey, 0.001f, 100.0f);
2758 }
2759
2760 // White
2761 const float white = get_pixel_norm(self->picked_color_max, DT_FILMIC_METHOD_MAX_RGB, work_profile);
2762 float EVmax = CLAMP(log2f(white / (p->grey_point_source / 100.0f)), 1.0f, 16.0f);
2763 EVmax *= (1.0f + p->security_factor / 100.0f);
2764
2765 // Black
2766 const float black = get_pixel_norm(self->picked_color_min, DT_FILMIC_METHOD_MAX_RGB, work_profile);
2767 float EVmin = CLAMP(log2f(black / (p->grey_point_source / 100.0f)), -16.0f, -1.0f);
2768 EVmin *= (1.0f + p->security_factor / 100.0f);
2769
2770 p->black_point_source = fmaxf(EVmin, -16.0f);
2771 p->white_point_source = EVmax;
2772 p->output_power = logf(p->grey_point_target / 100.0f)
2773 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
2774
2775 ++darktable.gui->reset;
2776 dt_bauhaus_slider_set(g->grey_point_source, p->grey_point_source);
2777 dt_bauhaus_slider_set(g->black_point_source, p->black_point_source);
2778 dt_bauhaus_slider_set(g->white_point_source, p->white_point_source);
2779 dt_bauhaus_slider_set(g->output_power, p->output_power);
2780 --darktable.gui->reset;
2781
2782 gtk_widget_queue_draw(self->widget);
2784}
2785
2786void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_iop_t *piece)
2787{
2789
2790 if(picker == g->grey_point_source)
2791 apply_auto_grey(self);
2792 else if(picker == g->black_point_source)
2793 apply_auto_black(self);
2794 else if(picker == g->white_point_source)
2796 else if(picker == g->auto_button)
2797 apply_autotune(self);
2798}
2799
2800static void show_mask_callback(GtkToggleButton *button, GdkEventButton *event, gpointer user_data)
2801{
2802 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2803 if(darktable.gui->reset) return;
2804 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
2806
2807 // if blend module is displaying mask do not display it here
2810
2811 g->show_mask = !(g->show_mask);
2812
2813 if(g->show_mask)
2815
2816 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_highlight_mask), !g->show_mask);
2817 dt_iop_set_cache_bypass(self, g->show_mask);
2818
2819 dt_dev_invalidate(self->dev);
2821}
2822
2823#define ORDER_4 5
2824#define ORDER_3 4
2825
2826// returns true if contrast was clamped, false otherwise
2827// used in GUI, to show user when contrast clamping is happening
2829 struct dt_iop_filmic_rgb_spline_t *const spline)
2830{
2831 float grey_display = 0.4638f;
2832 gboolean clamping = FALSE;
2833
2834 if(p->custom_grey)
2835 {
2836 // user set a custom value
2837 grey_display = powf(CLAMP(p->grey_point_target, p->black_point_target, p->white_point_target) / 100.0f,
2838 1.0f / (p->output_power));
2839 }
2840 else
2841 {
2842 // use 18.45% grey and don't bother
2843 grey_display = powf(0.1845f, 1.0f / (p->output_power));
2844 }
2845
2846 const float white_source = p->white_point_source;
2847 const float black_source = p->black_point_source;
2848 const float dynamic_range = white_source - black_source;
2849
2850 // luminance after log encoding
2851 const float black_log = 0.0f; // assumes user set log as in the autotuner
2852 const float grey_log = fabsf(p->black_point_source) / dynamic_range;
2853 const float white_log = 1.0f; // assumes user set log as in the autotuner
2854
2855 // target luminance desired after filmic curve
2856 float black_display, white_display;
2857
2858 if(p->spline_version == DT_FILMIC_SPLINE_VERSION_V1)
2859 {
2860 // this is a buggy version that doesn't take the output power function into account
2861 // it was silent because black and white display were set to 0 and 1 and users were advised to not touch them.
2862 // (since 0^x = 0 and 1^x = 1). It's not silent anymore if black display > 0,
2863 // for example if compensating ICC black level for target medium
2864 black_display = CLAMP(p->black_point_target, 0.0f, p->grey_point_target) / 100.0f; // in %
2865 white_display = fmaxf(p->white_point_target, p->grey_point_target) / 100.0f; // in %
2866 }
2867 else //(p->spline_version >= DT_FILMIC_SPLINE_VERSION_V2)
2868 {
2869 // this is the fixed version
2870 black_display = powf(CLAMP(p->black_point_target, 0.0f, p->grey_point_target) / 100.0f,
2871 1.0f / (p->output_power)); // in %
2872 white_display
2873 = powf(fmaxf(p->white_point_target, p->grey_point_target) / 100.0f, 1.0f / (p->output_power)); // in %
2874 }
2875
2876 float toe_log, shoulder_log, toe_display, shoulder_display, contrast;
2877 float balance = CLAMP(p->balance, -50.0f, 50.0f) / 100.0f; // in %
2878 if(p->spline_version < DT_FILMIC_SPLINE_VERSION_V3)
2879 {
2880 float latitude = CLAMP(p->latitude, 0.0f, 100.0f) / 100.0f * dynamic_range; // in % of dynamic range
2881 contrast = CLAMP(p->contrast, 1.00001f, 6.0f);
2882
2883 // nodes for mapping from log encoding to desired target luminance
2884 // X coordinates
2885 toe_log = grey_log - latitude / dynamic_range * fabsf(black_source / dynamic_range);
2886 shoulder_log = grey_log + latitude / dynamic_range * fabsf(white_source / dynamic_range);
2887
2888 // interception
2889 float linear_intercept = grey_display - (contrast * grey_log);
2890
2891 // y coordinates
2892 toe_display = (toe_log * contrast + linear_intercept);
2893 shoulder_display = (shoulder_log * contrast + linear_intercept);
2894
2895 // Apply the highlights/shadows balance as a shift along the contrast slope
2896 const float norm = sqrtf(contrast * contrast + 1.0f);
2897
2898 // negative values drag to the left and compress the shadows, on the UI negative is the inverse
2899 const float coeff = -((2.0f * latitude) / dynamic_range) * balance;
2900
2901 toe_display += coeff * contrast / norm;
2902 shoulder_display += coeff * contrast / norm;
2903 toe_log += coeff / norm;
2904 shoulder_log += coeff / norm;
2905 }
2906 else // p->spline_version >= DT_FILMIC_SPLINE_VERSION_V3. Slope dependent on contrast only, and latitude as % of display range.
2907 {
2908 const float hardness = p->output_power;
2909 // latitude in %
2910 float latitude = CLAMP(p->latitude, 0.0f, 100.0f) / 100.0f;
2911 float slope = p->contrast * dynamic_range / 8.0f;
2912 float min_contrast = 1.0f; // otherwise, white_display and black_display cannot be reached
2913 // make sure there is enough contrast to be able to construct the top right part of the curve
2914 min_contrast = fmaxf(min_contrast, (white_display - grey_display) / (white_log - grey_log));
2915 // make sure there is enough contrast to be able to construct the bottom left part of the curve
2916 min_contrast = fmaxf(min_contrast, (grey_display - black_display) / (grey_log - black_log));
2917 min_contrast += SAFETY_MARGIN;
2918 // we want a slope that depends only on contrast at gray point.
2919 // let's consider f(x) = (contrast*x+linear_intercept)^hardness
2920 // f'(x) = contrast * hardness * (contrast*x+linear_intercept)^(hardness-1)
2921 // linear_intercept = grey_display - (contrast * grey_log);
2922 // f'(grey_log) = contrast * hardness * (contrast * grey_log + grey_display - (contrast * grey_log))^(hardness-1)
2923 // = contrast * hardness * grey_display^(hardness-1)
2924 // f'(grey_log) = target_contrast <=> contrast = target_contrast / (hardness * grey_display^(hardness-1))
2925 contrast = slope / (hardness * powf(grey_display, hardness - 1.0f));
2926 float clamped_contrast = CLAMP(contrast, min_contrast, 100.0f);
2927 clamping = (clamped_contrast != contrast);
2928 contrast = clamped_contrast;
2929
2930 // interception
2931 float linear_intercept = grey_display - (contrast * grey_log);
2932
2933 // consider the line of equation y = contrast * x + linear_intercept
2934 // we want to keep y in [black_display, white_display] (with some safety margin)
2935 // thus, we compute x values such as y=black_display and y=white_display
2936 // latitude will influence position of toe and shoulder in the [xmin, xmax] segment
2937 const float xmin = (black_display + SAFETY_MARGIN * (white_display - black_display) - linear_intercept) / contrast;
2938 const float xmax = (white_display - SAFETY_MARGIN * (white_display - black_display) - linear_intercept) / contrast;
2939
2940 // nodes for mapping from log encoding to desired target luminance
2941 // X coordinates
2942 toe_log = (1.0f - latitude) * grey_log + latitude * xmin;
2943 shoulder_log = (1.0f - latitude) * grey_log + latitude * xmax;
2944
2945 // Apply the highlights/shadows balance as a shift along the contrast slope
2946 // negative values drag to the left and compress the shadows, on the UI negative is the inverse
2947 float balance_correction = (balance > 0.0f) ? 2.0f * balance * (shoulder_log - grey_log)
2948 : 2.0f * balance * (grey_log - toe_log);
2949 toe_log -= balance_correction;
2950 shoulder_log -= balance_correction;
2951 toe_log = fmaxf(toe_log, xmin);
2952 shoulder_log = fminf(shoulder_log, xmax);
2953
2954 // y coordinates
2955 toe_display = (toe_log * contrast + linear_intercept);
2956 shoulder_display = (shoulder_log * contrast + linear_intercept);
2957 }
2958
2969 // Build the curve from the nodes
2970 spline->x[0] = black_log;
2971 spline->x[1] = toe_log;
2972 spline->x[2] = grey_log;
2973 spline->x[3] = shoulder_log;
2974 spline->x[4] = white_log;
2975
2976 spline->y[0] = black_display;
2977 spline->y[1] = toe_display;
2978 spline->y[2] = grey_display;
2979 spline->y[3] = shoulder_display;
2980 spline->y[4] = white_display;
2981
2982 spline->latitude_min = spline->x[1];
2983 spline->latitude_max = spline->x[3];
2984
2985 spline->type[0] = p->shadows;
2986 spline->type[1] = p->highlights;
2987
2993 const double Tl = spline->x[1];
2994 const double Tl2 = Tl * Tl;
2995 const double Tl3 = Tl2 * Tl;
2996 const double Tl4 = Tl3 * Tl;
2997
2998 const double Sl = spline->x[3];
2999 const double Sl2 = Sl * Sl;
3000 const double Sl3 = Sl2 * Sl;
3001 const double Sl4 = Sl3 * Sl;
3002
3003 // if type polynomial :
3004 // y = M5 * x⁴ + M4 * x³ + M3 * x² + M2 * x¹ + M1 * x⁰
3005 // else if type rational :
3006 // y = M1 * (M2 * (x - x_0)² + (x - x_0)) / (M2 * (x - x_0)² + (x - x_0) + M3)
3007 // We then compute M1 to M5 coeffs using the imposed conditions over the curve.
3008 // M1 to M5 are 3x1 vectors, where each element belongs to a part of the curve.
3009
3010 // solve the linear central part - affine function
3011 spline->M2[2] = contrast; // * x¹ (slope)
3012 spline->M1[2] = spline->y[1] - spline->M2[2] * spline->x[1]; // * x⁰ (offset)
3013 spline->M3[2] = 0.f; // * x²
3014 spline->M4[2] = 0.f; // * x³
3015 spline->M5[2] = 0.f; // * x⁴
3016
3017 // solve the toe part
3018 if(p->shadows == DT_FILMIC_CURVE_POLY_4)
3019 {
3020 // fourth order polynom - only mode in darktable 3.0.0
3021 double A0[ORDER_4 * ORDER_4] = { 0., 0., 0., 0., 1., // position in 0
3022 0., 0., 0., 1., 0., // first derivative in 0
3023 Tl4, Tl3, Tl2, Tl, 1., // position at toe node
3024 4. * Tl3, 3. * Tl2, 2. * Tl, 1., 0., // first derivative at toe node
3025 12. * Tl2, 6. * Tl, 2., 0., 0. }; // second derivative at toe node
3026
3027 double b0[ORDER_4] = { spline->y[0], 0., spline->y[1], spline->M2[2], 0. };
3028
3029 gauss_solve(A0, b0, ORDER_4);
3030
3031 spline->M5[0] = b0[0]; // * x⁴
3032 spline->M4[0] = b0[1]; // * x³
3033 spline->M3[0] = b0[2]; // * x²
3034 spline->M2[0] = b0[3]; // * x¹
3035 spline->M1[0] = b0[4]; // * x⁰
3036 }
3037 else if(p->shadows == DT_FILMIC_CURVE_POLY_3)
3038 {
3039 // third order polynom
3040 double A0[ORDER_3 * ORDER_3] = { 0., 0., 0., 1., // position in 0
3041 Tl3, Tl2, Tl, 1., // position at toe node
3042 3. * Tl2, 2. * Tl, 1., 0., // first derivative at toe node
3043 6. * Tl, 2., 0., 0. }; // second derivative at toe node
3044
3045 double b0[ORDER_3] = { spline->y[0], spline->y[1], spline->M2[2], 0. };
3046
3047 gauss_solve(A0, b0, ORDER_3);
3048
3049 spline->M5[0] = 0.0f; // * x⁴
3050 spline->M4[0] = b0[0]; // * x³
3051 spline->M3[0] = b0[1]; // * x²
3052 spline->M2[0] = b0[2]; // * x¹
3053 spline->M1[0] = b0[3]; // * x⁰
3054 }
3055 else
3056 {
3057 const float P1[2] = { black_log, black_display };
3058 const float P0[2] = { toe_log, toe_display };
3059 const float x = P0[0] - P1[0];
3060 const float y = P0[1] - P1[1];
3061 const float g = contrast;
3062 const float b = g / (2.f * y) + (sqrtf(sqf(x * g / y + 1.f) - 4.f) - 1.f) / (2.f * x);
3063 const float c = y / g * (b * sqf(x) + x) / (b * sqf(x) + x - (y / g));
3064 const float a = c * g;
3065 spline->M1[0] = a;
3066 spline->M2[0] = b;
3067 spline->M3[0] = c;
3068 spline->M4[0] = toe_display;
3069 }
3070
3071 // solve the shoulder part
3072 if(p->highlights == DT_FILMIC_CURVE_POLY_3)
3073 {
3074 // 3rd order polynom - only mode in darktable 3.0.0
3075 double A1[ORDER_3 * ORDER_3] = { 1., 1., 1., 1., // position in 1
3076 Sl3, Sl2, Sl, 1., // position at shoulder node
3077 3. * Sl2, 2. * Sl, 1., 0., // first derivative at shoulder node
3078 6. * Sl, 2., 0., 0. }; // second derivative at shoulder node
3079
3080 double b1[ORDER_3] = { spline->y[4], spline->y[3], spline->M2[2], 0. };
3081
3082 gauss_solve(A1, b1, ORDER_3);
3083
3084 spline->M5[1] = 0.0f; // * x⁴
3085 spline->M4[1] = b1[0]; // * x³
3086 spline->M3[1] = b1[1]; // * x²
3087 spline->M2[1] = b1[2]; // * x¹
3088 spline->M1[1] = b1[3]; // * x⁰
3089 }
3090 else if(p->highlights == DT_FILMIC_CURVE_POLY_4)
3091 {
3092 // 4th order polynom
3093 double A1[ORDER_4 * ORDER_4] = { 1., 1., 1., 1., 1., // position in 1
3094 4., 3., 2., 1., 0., // first derivative in 1
3095 Sl4, Sl3, Sl2, Sl, 1., // position at shoulder node
3096 4. * Sl3, 3. * Sl2, 2. * Sl, 1., 0., // first derivative at shoulder node
3097 12. * Sl2, 6. * Sl, 2., 0., 0. }; // second derivative at shoulder node
3098
3099 double b1[ORDER_4] = { spline->y[4], 0., spline->y[3], spline->M2[2], 0. };
3100
3101 gauss_solve(A1, b1, ORDER_4);
3102
3103 spline->M5[1] = b1[0]; // * x⁴
3104 spline->M4[1] = b1[1]; // * x³
3105 spline->M3[1] = b1[2]; // * x²
3106 spline->M2[1] = b1[3]; // * x¹
3107 spline->M1[1] = b1[4]; // * x⁰
3108 }
3109 else
3110 {
3111 const float P1[2] = { white_log, white_display };
3112 const float P0[2] = { shoulder_log, shoulder_display };
3113 const float x = P1[0] - P0[0];
3114 const float y = P1[1] - P0[1];
3115 const float g = contrast;
3116 const float b = g / (2.f * y) + (sqrtf(sqf(x * g / y + 1.f) - 4.f) - 1.f) / (2.f * x);
3117 const float c = y / g * (b * sqf(x) + x) / (b * sqf(x) + x - (y / g));
3118 const float a = c * g;
3119 spline->M1[1] = a;
3120 spline->M2[1] = b;
3121 spline->M3[1] = c;
3122 spline->M4[1] = shoulder_display;
3123 }
3124 return clamping;
3125}
3126
3129{
3132
3133 // source and display greys
3134 float grey_source = 0.1845f, grey_display = 0.4638f;
3135 if(p->custom_grey)
3136 {
3137 // user set a custom value
3138 grey_source = p->grey_point_source / 100.0f; // in %
3139 grey_display = powf(p->grey_point_target / 100.0f, 1.0f / (p->output_power));
3140 }
3141 else
3142 {
3143 // use 18.45% grey and don't bother
3144 grey_source = 0.1845f; // in %
3145 grey_display = powf(0.1845f, 1.0f / (p->output_power));
3146 }
3147
3148 // source luminance - Used only in the log encoding
3149 const float white_source = p->white_point_source;
3150 const float black_source = p->black_point_source;
3151 const float dynamic_range = white_source - black_source;
3152
3153 // luminance after log encoding
3154 const float grey_log = fabsf(p->black_point_source) / dynamic_range;
3155
3156
3157 float contrast = p->contrast;
3158 if((p->spline_version < DT_FILMIC_SPLINE_VERSION_V3) && (contrast < grey_display / grey_log))
3159 {
3160 // We need grey_display - (contrast * grey_log) <= 0.0
3161 // this clamping is handled automatically for spline_version >= DT_FILMIC_SPLINE_VERSION_V3
3162 contrast = 1.0001f * grey_display / grey_log;
3163 }
3164
3165 // commit
3166 d->dynamic_range = dynamic_range;
3167 d->black_source = black_source;
3168 d->grey_source = grey_source;
3169 d->output_power = p->output_power;
3170 d->contrast = contrast;
3171 d->version = p->version;
3172 d->spline_version = p->spline_version;
3173 d->preserve_color = p->preserve_color;
3174 d->high_quality_reconstruction = p->high_quality_reconstruction;
3175 d->noise_level = p->noise_level;
3176 d->noise_distribution = (dt_noise_distribution_t)p->noise_distribution;
3177
3178 // compute the curves and their LUT
3179 dt_iop_filmic_rgb_compute_spline(p, &d->spline);
3180
3181 if(p->version >= DT_FILMIC_COLORSCIENCE_V4)
3182 d->saturation = p->saturation / 100.0f;
3183 else
3184 d->saturation = (2.0f * p->saturation / 100.0f + 1.0f);
3185
3186 d->sigma_toe = powf(d->spline.latitude_min / 3.0f, 2.0f);
3187 d->sigma_shoulder = powf((1.0f - d->spline.latitude_max) / 3.0f, 2.0f);
3188
3189 d->reconstruct_threshold = powf(2.0f, white_source + p->reconstruct_threshold) * grey_source;
3190 d->reconstruct_feather = exp2f(12.f / p->reconstruct_feather);
3191
3192 // offset and rescale user param to alpha blending so 0 -> 50% and 1 -> 100%
3194 d->reconstruct_structure_vs_texture = (p->reconstruct_structure_vs_texture / 100.0f + 1.f) / 2.f;
3195 d->reconstruct_bloom_vs_details = (p->reconstruct_bloom_vs_details / 100.0f + 1.f) / 2.f;
3196 d->reconstruct_grey_vs_color = (p->reconstruct_grey_vs_color / 100.0f + 1.f) / 2.f;
3197}
3198
3199void gui_focus(struct dt_iop_module_t *self, gboolean in)
3200{
3202
3203 if(!in)
3204 {
3205 // lost focus - hide the mask
3206 gint mask_was_shown = g->show_mask;
3207 g->show_mask = FALSE;
3208 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_highlight_mask), FALSE);
3209 if(mask_was_shown) dt_dev_invalidate(self->dev);
3211 }
3212}
3213
3219
3221{
3222 dt_free_align(piece->data);
3223 piece->data = NULL;
3224}
3225
3227{
3230
3232
3233 g->show_mask = FALSE;
3234 g->gui_mode = dt_conf_get_int("plugins/darkroom/filmicrgb/graph_view");
3235 g->gui_show_labels = dt_conf_get_int("plugins/darkroom/filmicrgb/graph_show_labels");
3236 g->gui_hover = FALSE;
3237 g->gui_sizes_inited = FALSE;
3238
3239 // fetch last view in dartablerc
3240
3241 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->auto_hardness), p->auto_hardness);
3242 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->custom_grey), p->custom_grey);
3243
3244 gui_changed(self, NULL, NULL);
3245}
3246
3248{
3249 dt_iop_filmicrgb_params_t *d = module->default_params;
3250
3251 d->black_point_source = module->so->get_f("black_point_source")->Float.Default;
3252 d->white_point_source = module->so->get_f("white_point_source")->Float.Default;
3253 d->output_power = module->so->get_f("output_power")->Float.Default;
3254
3255 if(dt_image_is_raw(&module->dev->image_storage))
3256 {
3257 // For scene-referred workflow, auto-enable and adjust based on exposure
3258 // TODO: fetch actual exposure in module, don't assume 1.
3259 const float exposure = 0.7f - dt_image_get_exposure_bias(&module->dev->image_storage);
3260
3261 // As global exposure increases, white exposure increases faster than black
3262 // this is probably because raw black/white points offsets the lower bound of the dynamic range to 0
3263 // so exposure compensation actually increases the dynamic range too (stretches only white).
3264 d->white_point_source = exposure + 2.45f;
3265 d->black_point_source = d->white_point_source - 12.f; // 12 EV of dynamic range is a good default for modern cameras
3266 d->output_power = logf(d->grey_point_target / 100.0f)
3268
3269 module->workflow_enabled = TRUE;
3270 }
3271}
3272
3273
3275{
3276 const int program = 22; // filmic.cl, from programs.conf
3279
3280 module->data = gd;
3281 gd->kernel_filmic_rgb_split = dt_opencl_create_kernel(program, "filmicrgb_split");
3282 gd->kernel_filmic_rgb_chroma = dt_opencl_create_kernel(program, "filmicrgb_chroma");
3283 gd->kernel_filmic_mask = dt_opencl_create_kernel(program, "filmic_mask_clipped_pixels");
3284 gd->kernel_filmic_show_mask = dt_opencl_create_kernel(program, "filmic_show_mask");
3285 gd->kernel_filmic_inpaint_noise = dt_opencl_create_kernel(program, "filmic_inpaint_noise");
3286 gd->kernel_filmic_init_reconstruct = dt_opencl_create_kernel(program, "init_reconstruct");
3287 gd->kernel_filmic_wavelets_reconstruct = dt_opencl_create_kernel(program, "wavelets_reconstruct");
3288 gd->kernel_filmic_compute_ratios = dt_opencl_create_kernel(program, "compute_ratios");
3289 gd->kernel_filmic_restore_ratios = dt_opencl_create_kernel(program, "restore_ratios");
3290
3291 const int wavelets = 35; // bspline.cl, from programs.conf
3292 gd->kernel_filmic_bspline_horizontal = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_horizontal");
3293 gd->kernel_filmic_bspline_vertical = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_vertical");
3294 gd->kernel_filmic_wavelets_detail = dt_opencl_create_kernel(wavelets, "wavelets_detail_level");
3295}
3296
3315
3316
3318{
3320}
3321
3322#define LOGBASE 20.f
3323
3324static inline void dt_cairo_draw_arrow(cairo_t *cr, double origin_x, double origin_y, double destination_x,
3325 double destination_y, gboolean show_head)
3326{
3327 cairo_move_to(cr, origin_x, origin_y);
3328 cairo_line_to(cr, destination_x, destination_y);
3329 cairo_stroke(cr);
3330
3331 if(show_head)
3332 {
3333 // arrow head is hard set to 45° - convert to radians
3334 const float angle_arrow = 45.f / 360.f * M_PI;
3335 const float angle_trunk = atan2f((destination_y - origin_y), (destination_x - origin_x));
3336 const float radius = DT_PIXEL_APPLY_DPI(3);
3337
3338 const float x_1 = destination_x + radius / sinf(angle_arrow + angle_trunk);
3339 const float y_1 = destination_y + radius / cosf(angle_arrow + angle_trunk);
3340
3341 const float x_2 = destination_x - radius / sinf(-angle_arrow + angle_trunk);
3342 const float y_2 = destination_y - radius / cosf(-angle_arrow + angle_trunk);
3343
3344 cairo_move_to(cr, x_1, y_1);
3345 cairo_line_to(cr, destination_x, destination_y);
3346 cairo_line_to(cr, x_2, y_2);
3347 cairo_stroke(cr);
3348 }
3349}
3350
3353{
3354 if(!g->gui_sizes_inited) return;
3355
3356 cairo_save(cr);
3357
3358 GdkRGBA color;
3359
3360 // copy color
3361 color.red = darktable.bauhaus->graph_fg.red;
3362 color.green = darktable.bauhaus->graph_fg.green;
3363 color.blue = darktable.bauhaus->graph_fg.blue;
3364 color.alpha = darktable.bauhaus->graph_fg.alpha;
3365
3366 if(button->mouse_hover)
3367 {
3368 // use graph_fg color as-is if mouse hover
3369 cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha);
3370 }
3371 else
3372 {
3373 // use graph_fg color with transparency else
3374 cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha * 0.5);
3375 }
3376
3377 cairo_rectangle(cr, button->left, button->top, button->w - DT_PIXEL_APPLY_DPI(0.5),
3378 button->h - DT_PIXEL_APPLY_DPI(0.5));
3379 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
3380 cairo_stroke(cr);
3381 cairo_translate(cr, button->left + button->w / 2. - DT_PIXEL_APPLY_DPI(0.25),
3382 button->top + button->h / 2. - DT_PIXEL_APPLY_DPI(0.25));
3383
3384 const float scale = 0.85;
3385 cairo_scale(cr, scale, scale);
3386 button->icon(cr, -scale * button->w / 2., -scale * button->h / 2., scale * button->w, scale * button->h, 0, NULL);
3387 cairo_restore(cr);
3388}
3389
3390
3391static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
3392{
3393 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3396 gboolean contrast_clamped = dt_iop_filmic_rgb_compute_spline(p, &g->spline);
3397
3398 // Cache the graph objects to avoid recomputing all the view at each redraw
3399 gtk_widget_get_allocation(widget, &g->allocation);
3400
3401 cairo_surface_t *cst =
3402 dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, g->allocation.width, g->allocation.height);
3403 PangoFontDescription *desc =
3404 pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
3405 cairo_t *cr = cairo_create(cst);
3406 PangoLayout *layout = pango_cairo_create_layout(cr);
3407
3408 pango_layout_set_font_description(layout, desc);
3409 pango_cairo_context_set_resolution(pango_layout_get_context(layout), darktable.gui->dpi);
3410 g->context = gtk_widget_get_style_context(widget);
3411
3412 char text[256];
3413
3414 // reduce a bit the font size
3415 const gint font_size = pango_font_description_get_size(desc);
3416 pango_font_description_set_size(desc, 0.95 * font_size);
3417 pango_layout_set_font_description(layout, desc);
3418
3419 // Get the text line height for spacing
3420 g_strlcpy(text, "X", sizeof(text));
3421 pango_layout_set_text(layout, text, -1);
3422 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3423 g->line_height = g->ink.height;
3424
3425 // Get the width of a minus sign for legend labels spacing
3426 g_strlcpy(text, "-", sizeof(text));
3427 pango_layout_set_text(layout, text, -1);
3428 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3429 g->sign_width = g->ink.width / 2.0;
3430
3431 // Get the width of a zero for legend labels spacing
3432 g_strlcpy(text, "0", sizeof(text));
3433 pango_layout_set_text(layout, text, -1);
3434 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3435 g->zero_width = g->ink.width;
3436
3437 // Set the sizes, margins and paddings
3438 g->inset = INNER_PADDING;
3439
3440 float margin_left;
3441 float margin_bottom;
3442 if(g->gui_show_labels)
3443 {
3444 // leave room for labels
3445 margin_left = 3. * g->zero_width + 2. * g->inset;
3446 margin_bottom = 2. * g->line_height + 4. * g->inset;
3447 }
3448 else
3449 {
3450 margin_left = g->inset;
3451 margin_bottom = g->inset;
3452 }
3453
3454 const float margin_top = 2. * g->line_height + g->inset;
3455 const float margin_right = darktable.bauhaus->quad_width + 2. * g->inset;
3456
3457 g->graph_width = g->allocation.width - margin_right - margin_left; // align the right border on sliders
3458 g->graph_height = g->allocation.height - margin_bottom - margin_top; // give room to nodes
3459
3460 gtk_render_background(g->context, cr, 0, 0, g->allocation.width, g->allocation.height);
3461
3462 // Init icons bounds and cache them for mouse events
3463 for(int i = 0; i < DT_FILMIC_GUI_BUTTON_LAST; i++)
3464 {
3465 // put the buttons in the right margin and increment vertical position
3466 g->buttons[i].right = g->allocation.width;
3467 g->buttons[i].left = g->buttons[i].right - darktable.bauhaus->quad_width;
3468 g->buttons[i].top = margin_top + i * (g->inset + darktable.bauhaus->quad_width);
3469 g->buttons[i].bottom = g->buttons[i].top + darktable.bauhaus->quad_width;
3470 g->buttons[i].w = g->buttons[i].right - g->buttons[i].left;
3471 g->buttons[i].h = g->buttons[i].bottom - g->buttons[i].top;
3472 g->buttons[i].state = GTK_STATE_FLAG_NORMAL;
3473 }
3474
3475 g->gui_sizes_inited = TRUE;
3476
3477 g->buttons[0].icon = dtgtk_cairo_paint_refresh;
3478 g->buttons[1].icon = dtgtk_cairo_paint_text_label;
3479
3480 if(g->gui_hover)
3481 {
3482 for(int i = 0; i < DT_FILMIC_GUI_BUTTON_LAST; i++) filmic_gui_draw_icon(cr, &g->buttons[i], g);
3483 }
3484
3485 const float grey = p->grey_point_source / 100.f;
3486 const float DR = p->white_point_source - p->black_point_source;
3487
3488 // set the graph as the origin of the coordinates
3489 cairo_translate(cr, margin_left, margin_top);
3490
3491 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
3492
3493 // write the graph legend at GUI default size
3494 pango_font_description_set_size(desc, font_size);
3495 pango_layout_set_font_description(layout, desc);
3496 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3497 g_strlcpy(text, _("look only"), sizeof(text));
3498 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
3499 g_strlcpy(text, _("look + mapping (lin)"), sizeof(text));
3500 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3501 g_strlcpy(text, _("look + mapping (log)"), sizeof(text));
3502 else if(g->gui_mode == DT_FILMIC_GUI_RANGES)
3503 g_strlcpy(text, _("dynamic range mapping"), sizeof(text));
3504
3505 pango_layout_set_text(layout, text, -1);
3506 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3507
3508 // legend background
3510 cairo_rectangle(cr, g->allocation.width - margin_left - g->ink.width - g->ink.x - 2. * g->inset,
3511 -g->line_height - g->inset - 0.5 * g->ink.height - g->ink.y - g->inset,
3512 g->ink.width + 3. * g->inset, g->ink.height + 2. * g->inset);
3513 cairo_fill(cr);
3514
3515 // legend text
3517 cairo_move_to(cr, g->allocation.width - margin_left - g->ink.width - g->ink.x - g->inset,
3518 -g->line_height - g->inset - 0.5 * g->ink.height - g->ink.y);
3519 pango_cairo_show_layout(cr, layout);
3520 cairo_stroke(cr);
3521
3522 // reduce font size for the rest of the graph
3523 pango_font_description_set_size(desc, 0.95 * font_size);
3524 pango_layout_set_font_description(layout, desc);
3525
3526 if(g->gui_mode != DT_FILMIC_GUI_RANGES)
3527 {
3528 // Draw graph background then border
3529 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(0.5));
3530 cairo_rectangle(cr, 0, 0, g->graph_width, g->graph_height);
3532 cairo_fill_preserve(cr);
3534 cairo_stroke(cr);
3535
3536 // draw grid
3537 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(0.5));
3539
3540 // we need to tweak the coordinates system to match dt_draw_grid expectations
3541 cairo_save(cr);
3542 cairo_scale(cr, 1., -1.);
3543 cairo_translate(cr, 0., -g->graph_height);
3544
3545 if(g->gui_mode == DT_FILMIC_GUI_LOOK || g->gui_mode == DT_FILMIC_GUI_BASECURVE)
3546 dt_draw_grid(cr, 4, 0, 0, g->graph_width, g->graph_height);
3547 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3548 dt_draw_loglog_grid(cr, 4, 0, 0, g->graph_width, g->graph_height, LOGBASE);
3549
3550 // reset coordinates
3551 cairo_restore(cr);
3552
3553 // draw identity line
3554 cairo_move_to(cr, 0, g->graph_height);
3555 cairo_line_to(cr, g->graph_width, 0);
3556 cairo_stroke(cr);
3557
3558 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
3559
3560 // Draw the saturation curve
3561 const float saturation = (2.0f * p->saturation / 100.0f + 1.0f);
3562 const float sigma_toe = powf(g->spline.latitude_min / 3.0f, 2.0f);
3563 const float sigma_shoulder = powf((1.0f - g->spline.latitude_max) / 3.0f, 2.0f);
3564
3565 cairo_set_source_rgb(cr, .5, .5, .5);
3566
3567 // prevent graph overflowing
3568 cairo_save(cr);
3569 cairo_rectangle(cr, -DT_PIXEL_APPLY_DPI(2.), -DT_PIXEL_APPLY_DPI(2.),
3570 g->graph_width + 2. * DT_PIXEL_APPLY_DPI(2.), g->graph_height + 2. * DT_PIXEL_APPLY_DPI(2.));
3571 cairo_clip(cr);
3572
3573 if(p->version == DT_FILMIC_COLORSCIENCE_V1)
3574 {
3575 cairo_move_to(cr, 0,
3576 g->graph_height * (1.0 - filmic_desaturate_v1(0.0f, sigma_toe, sigma_shoulder, saturation)));
3577 for(int k = 1; k < 256; k++)
3578 {
3579 float x = k / 255.0;
3580 const float y = filmic_desaturate_v1(x, sigma_toe, sigma_shoulder, saturation);
3581
3582 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
3583 x = exp_tonemapping_v2(x, grey, p->black_point_source, DR);
3584 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3585 x = dt_log_scale_axis(exp_tonemapping_v2(x, grey, p->black_point_source, DR), LOGBASE);
3586
3587 cairo_line_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
3588 }
3589 }
3590 else if(p->version == DT_FILMIC_COLORSCIENCE_V2 || p->version == DT_FILMIC_COLORSCIENCE_V3)
3591 {
3592 cairo_move_to(cr, 0,
3593 g->graph_height * (1.0 - filmic_desaturate_v2(0.0f, sigma_toe, sigma_shoulder, saturation)));
3594 for(int k = 1; k < 256; k++)
3595 {
3596 float x = k / 255.0;
3597 const float y = filmic_desaturate_v2(x, sigma_toe, sigma_shoulder, saturation);
3598
3599 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
3600 x = exp_tonemapping_v2(x, grey, p->black_point_source, DR);
3601 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3602 x = dt_log_scale_axis(exp_tonemapping_v2(x, grey, p->black_point_source, DR), LOGBASE);
3603
3604 cairo_line_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
3605 }
3606 }
3607 cairo_stroke(cr);
3608
3609 // draw the tone curve
3610 float x_start = 0.f;
3611 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3612 x_start = log_tonemapping(x_start, grey, p->black_point_source, DR);
3613
3614 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG) x_start = dt_log_scale_axis(x_start, LOGBASE);
3615
3616 float y_start = clamp_simd(filmic_spline(x_start, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
3617 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type));
3618
3619 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
3620 y_start = powf(y_start, p->output_power);
3621 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3622 y_start = dt_log_scale_axis(powf(y_start, p->output_power), LOGBASE);
3623
3624 cairo_move_to(cr, 0, g->graph_height * (1.0 - y_start));
3625
3626 for(int k = 1; k < 256; k++)
3627 {
3628 // k / 255 step defines a linearly scaled space. This might produce large gaps in lowlights when using log
3629 // GUI scaling so we non-linearly rescale that step to get more points in lowlights
3630 float x = powf(k / 255.0f, 2.4f);
3631 float value = x;
3632
3633 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3634 value = log_tonemapping(x, grey, p->black_point_source, DR);
3635
3636 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG) x = dt_log_scale_axis(x, LOGBASE);
3637
3638 float y = filmic_spline(value, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4, g->spline.M5,
3639 g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
3640
3641 // curve is drawn in orange when above maximum
3642 // or below minimum.
3643 // we use a small margin in the comparison
3644 // to avoid drawing curve in orange when it
3645 // is right above or right below the limit
3646 // due to floating point errors
3647 const float margin = 1E-5;
3648 if(y > g->spline.y[4] + margin)
3649 {
3650 y = fminf(y, 1.0f);
3651 cairo_set_source_rgb(cr, 0.75, .5, 0.);
3652 }
3653 else if(y < g->spline.y[0] - margin)
3654 {
3655 y = fmaxf(y, 0.f);
3656 cairo_set_source_rgb(cr, 0.75, .5, 0.);
3657 }
3658 else
3659 {
3661 }
3662
3663 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
3664 y = powf(y, p->output_power);
3665 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3666 y = dt_log_scale_axis(powf(y, p->output_power), LOGBASE);
3667
3668 cairo_line_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
3669 cairo_stroke(cr);
3670 cairo_move_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
3671 }
3672
3673 cairo_restore(cr);
3674
3675 // draw nodes
3676
3677 // special case for the grey node
3678 cairo_save(cr);
3679 cairo_rectangle(cr, -DT_PIXEL_APPLY_DPI(4.), -DT_PIXEL_APPLY_DPI(4.),
3680 g->graph_width + 2. * DT_PIXEL_APPLY_DPI(4.), g->graph_height + 2. * DT_PIXEL_APPLY_DPI(4.));
3681 cairo_clip(cr);
3682 float x_grey = g->spline.x[2];
3683 float y_grey = g->spline.y[2];
3684
3685 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
3686 {
3687 x_grey = exp_tonemapping_v2(x_grey, grey, p->black_point_source, DR);
3688 y_grey = powf(y_grey, p->output_power);
3689 }
3690 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3691 {
3692 x_grey = dt_log_scale_axis(exp_tonemapping_v2(x_grey, grey, p->black_point_source, DR), LOGBASE);
3693 y_grey = dt_log_scale_axis(powf(y_grey, p->output_power), LOGBASE);
3694 }
3695
3696 cairo_set_source_rgb(cr, 0.75, 0.5, 0.0);
3697 cairo_arc(cr, x_grey * g->graph_width, (1.0 - y_grey) * g->graph_height, DT_PIXEL_APPLY_DPI(6), 0,
3698 2. * M_PI);
3699 cairo_fill(cr);
3700 cairo_stroke(cr);
3701
3702 // latitude nodes
3703 float x_black = 0.f;
3704 float y_black = 0.f;
3705
3706 float x_white = 1.f;
3707 float y_white = 1.f;
3708
3709 const float central_slope = (g->spline.y[3] - g->spline.y[1]) * g->graph_width / ((g->spline.x[3] - g->spline.x[1]) * g->graph_height);
3710 const float central_slope_angle = atanf(central_slope) + M_PI / 2.0f;
3712 for(int k = 0; k < 5; k++)
3713 {
3714 if(k != 2) // k == 2 : grey point, already processed above
3715 {
3716 float x = g->spline.x[k];
3717 float y = g->spline.y[k];
3718 const float ymin = g->spline.y[0];
3719 const float ymax = g->spline.y[4];
3720 // we multiply SAFETY_MARGIN by 1.1f to avoid possible false negatives due to float errors
3721 const float y_margin = SAFETY_MARGIN * 1.1f * (ymax - ymin);
3722 gboolean red = (((k == 1) && (y - ymin <= y_margin))
3723 || ((k == 3) && (ymax - y <= y_margin)));
3724 float start_angle = 0.0f;
3725 float end_angle = 2.f * M_PI;
3726 // if contrast is clamped, show it on GUI with half circles
3727 // for points 1 and 3
3728 if(contrast_clamped)
3729 {
3730 if(k == 1)
3731 {
3732 start_angle = central_slope_angle + M_PI;
3733 end_angle = central_slope_angle;
3734 }
3735 if(k == 3)
3736 {
3737 start_angle = central_slope_angle;
3738 end_angle = start_angle + M_PI;
3739 }
3740 }
3741
3742 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
3743 {
3744 x = exp_tonemapping_v2(x, grey, p->black_point_source, DR);
3745 y = powf(y, p->output_power);
3746 }
3747 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3748 {
3749 x = dt_log_scale_axis(exp_tonemapping_v2(x, grey, p->black_point_source, DR), LOGBASE);
3750 y = dt_log_scale_axis(powf(y, p->output_power), LOGBASE);
3751 }
3752
3753 // save the bounds of the curve to mark the axis graduation
3754 if(k == 0) // black point
3755 {
3756 x_black = x;
3757 y_black = y;
3758 }
3759 else if(k == 4) // white point
3760 {
3761 x_white = x;
3762 y_white = y;
3763 }
3764
3765 if(red) cairo_set_source_rgb(cr, 0.8, 0.35, 0.35);
3766
3767 // draw bullet
3768 cairo_arc(cr, x * g->graph_width, (1.0 - y) * g->graph_height, DT_PIXEL_APPLY_DPI(4), start_angle, end_angle);
3769 cairo_fill(cr);
3770 cairo_stroke(cr);
3771
3772 // reset color for next points
3773 if(red) set_color(cr, darktable.bauhaus->graph_fg);
3774 }
3775 }
3776 cairo_restore(cr);
3777
3778 if(g->gui_show_labels)
3779 {
3780 // position of the upper bound of x axis labels
3781 const float x_legend_top = g->graph_height + 0.5 * g->line_height;
3782
3783 // mark the y axis graduation at grey spot
3785 snprintf(text, sizeof(text), "%.0f", p->grey_point_target);
3786 pango_layout_set_text(layout, text, -1);
3787 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3788 cairo_move_to(cr, -2. * g->inset - g->ink.width - g->ink.x,
3789 (1.0 - y_grey) * g->graph_height - 0.5 * g->ink.height - g->ink.y);
3790 pango_cairo_show_layout(cr, layout);
3791 cairo_stroke(cr);
3792
3793 // mark the x axis graduation at grey spot
3795 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3796 snprintf(text, sizeof(text), "%+.1f", 0.f);
3797 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3798 snprintf(text, sizeof(text), "%.0f", p->grey_point_source);
3799
3800 pango_layout_set_text(layout, text, -1);
3801 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3802 cairo_move_to(cr, x_grey * g->graph_width - 0.5 * g->ink.width - g->ink.x, x_legend_top);
3803 pango_cairo_show_layout(cr, layout);
3804 cairo_stroke(cr);
3805
3806 // mark the y axis graduation at black spot
3808 snprintf(text, sizeof(text), "%.0f", p->black_point_target);
3809 pango_layout_set_text(layout, text, -1);
3810 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3811 cairo_move_to(cr, -2. * g->inset - g->ink.width - g->ink.x,
3812 (1.0 - y_black) * g->graph_height - 0.5 * g->ink.height - g->ink.y);
3813 pango_cairo_show_layout(cr, layout);
3814 cairo_stroke(cr);
3815
3816 // mark the y axis graduation at black spot
3818 snprintf(text, sizeof(text), "%.0f", p->white_point_target);
3819 pango_layout_set_text(layout, text, -1);
3820 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3821 cairo_move_to(cr, -2. * g->inset - g->ink.width - g->ink.x,
3822 (1.0 - y_white) * g->graph_height - 0.5 * g->ink.height - g->ink.y);
3823 pango_cairo_show_layout(cr, layout);
3824 cairo_stroke(cr);
3825
3826 // mark the x axis graduation at black spot
3828 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3829 snprintf(text, sizeof(text), "%+.1f", p->black_point_source);
3830 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3831 snprintf(text, sizeof(text), "%.0f", exp2f(p->black_point_source) * p->grey_point_source);
3832
3833 pango_layout_set_text(layout, text, -1);
3834 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3835 cairo_move_to(cr, x_black * g->graph_width - 0.5 * g->ink.width - g->ink.x, x_legend_top);
3836 pango_cairo_show_layout(cr, layout);
3837 cairo_stroke(cr);
3838
3839 // mark the x axis graduation at white spot
3841 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3842 snprintf(text, sizeof(text), "%+.1f", p->white_point_source);
3843 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3844 {
3845 if(x_white > 1.f)
3846 snprintf(text, sizeof(text), "%.0f \342\206\222", 100.f); // this marks the bound of the graph, not the actual white
3847 else
3848 snprintf(text, sizeof(text), "%.0f", exp2f(p->white_point_source) * p->grey_point_source);
3849 }
3850
3851 pango_layout_set_text(layout, text, -1);
3852 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3853 cairo_move_to(cr,
3854 fminf(x_white, 1.f) * g->graph_width - 0.5 * g->ink.width - g->ink.x
3855 + 2. * (x_white > 1.f) * g->sign_width,
3856 x_legend_top);
3857 pango_cairo_show_layout(cr, layout);
3858 cairo_stroke(cr);
3859
3860 // handle the case where white > 100 %, so the node is out of the graph.
3861 // we still want to display the value to get a hint.
3863 if((g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG) && (x_white > 1.f))
3864 {
3865 // set to italic font
3866 PangoStyle backup = pango_font_description_get_style(desc);
3867 pango_font_description_set_style(desc, PANGO_STYLE_ITALIC);
3868 pango_layout_set_font_description(layout, desc);
3869
3870 snprintf(text, sizeof(text), _("(%.0f %%)"), exp2f(p->white_point_source) * p->grey_point_source);
3871 pango_layout_set_text(layout, text, -1);
3872 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3873 cairo_move_to(cr, g->allocation.width - g->ink.width - g->ink.x - margin_left,
3874 g->graph_height + 3. * g->inset + g->line_height - g->ink.y);
3875 pango_cairo_show_layout(cr, layout);
3876 cairo_stroke(cr);
3877
3878 // restore font
3879 pango_font_description_set_style(desc, backup);
3880 pango_layout_set_font_description(layout, desc);
3881 }
3882
3883 // mark the y axis legend
3885 /* xgettext:no-c-format */
3886 g_strlcpy(text, _("% display"), sizeof(text));
3887 pango_layout_set_text(layout, text, -1);
3888 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3889 cairo_move_to(cr, -2. * g->inset - g->zero_width - g->ink.x,
3890 -g->line_height - g->inset - 0.5 * g->ink.height - g->ink.y);
3891 pango_cairo_show_layout(cr, layout);
3892 cairo_stroke(cr);
3893
3894
3895 // mark the x axis legend
3897 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
3898 g_strlcpy(text, _("EV scene"), sizeof(text));
3899 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
3900 {
3901 /* xgettext:no-c-format */
3902 g_strlcpy(text, _("% camera"), sizeof(text));
3903 }
3904 pango_layout_set_text(layout, text, -1);
3905 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3906 cairo_move_to(cr, 0.5 * g->graph_width - 0.5 * g->ink.width - g->ink.x,
3907 g->graph_height + 3. * g->inset + g->line_height - g->ink.y);
3908 pango_cairo_show_layout(cr, layout);
3909 cairo_stroke(cr);
3910 }
3911 }
3912 else
3913 {
3914 // mode ranges
3915 cairo_identity_matrix(cr); // reset coordinates
3916
3917 // draw the dynamic range of display
3918 // if white = 100%, assume -11.69 EV because of uint8 output + sRGB OETF.
3919 // for uint10 output, white should be set to 400%, so anything above 100% increases DR
3920 // FIXME : if darktable becomes HDR-10bits compatible (for output), this needs to be updated
3921 const float display_DR = 12.f + log2f(p->white_point_target / 100.f);
3922
3923 const float y_display = g->allocation.height / 3.f + g->line_height;
3924 const float y_scene = 2. * g->allocation.height / 3.f + g->line_height;
3925
3926 const float display_top = y_display - g->line_height / 2;
3927 const float display_bottom = display_top + g->line_height;
3928
3929 const float scene_top = y_scene - g->line_height / 2;
3930 const float scene_bottom = scene_top + g->line_height;
3931
3932 float column_left;
3933
3934 if(g->gui_show_labels)
3935 {
3936 // labels
3938 g_strlcpy(text, _("display"), sizeof(text));
3939 pango_layout_set_text(layout, text, -1);
3940 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3941 cairo_move_to(cr, 0., y_display - 0.5 * g->ink.height - g->ink.y);
3942 pango_cairo_show_layout(cr, layout);
3943 cairo_stroke(cr);
3944 const float display_label_width = g->ink.width;
3945
3946 // axis legend
3947 g_strlcpy(text, _("(%)"), sizeof(text));
3948 pango_layout_set_text(layout, text, -1);
3949 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3950 cairo_move_to(cr, 0.5 * display_label_width - 0.5 * g->ink.width - g->ink.x,
3951 display_top - 4. * g->inset - g->ink.height - g->ink.y);
3952 pango_cairo_show_layout(cr, layout);
3953 cairo_stroke(cr);
3954
3956 g_strlcpy(text, _("scene"), sizeof(text));
3957 pango_layout_set_text(layout, text, -1);
3958 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3959 cairo_move_to(cr, 0., y_scene - 0.5 * g->ink.height - g->ink.y);
3960 pango_cairo_show_layout(cr, layout);
3961 cairo_stroke(cr);
3962 const float scene_label_width = g->ink.width;
3963
3964 // axis legend
3965 g_strlcpy(text, _("(EV)"), sizeof(text));
3966 pango_layout_set_text(layout, text, -1);
3967 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
3968 cairo_move_to(cr, 0.5 * scene_label_width - 0.5 * g->ink.width - g->ink.x,
3969 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
3970 pango_cairo_show_layout(cr, layout);
3971 cairo_stroke(cr);
3972
3973 // arrow between labels
3974 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
3975 dt_cairo_draw_arrow(cr, fminf(scene_label_width, display_label_width) / 2.f, y_scene - g->line_height,
3976 fminf(scene_label_width, display_label_width) / 2.f,
3977 y_display + g->line_height + g->inset, TRUE);
3978
3979 column_left = fmaxf(display_label_width, scene_label_width) + g->inset;
3980 }
3981 else
3982 column_left = darktable.bauhaus->quad_width;
3983
3984 const float column_right = g->allocation.width - column_left - darktable.bauhaus->quad_width;
3985
3986 // compute dynamic ranges left and right to middle grey
3987 const float display_HL_EV = -log2f(p->grey_point_target / p->white_point_target); // compared to white EV
3988 const float display_LL_EV = display_DR - display_HL_EV; // compared to black EV
3989 const float display_real_black_EV
3990 = -fmaxf(log2f(p->black_point_target / p->grey_point_target),
3991 -11.685887601778058f + display_HL_EV - log2f(p->white_point_target / 100.f));
3992 const float scene_HL_EV = p->white_point_source; // compared to white EV
3993 const float scene_LL_EV = -p->black_point_source; // compared to black EV
3994
3995 // compute the max width needed to fit both dynamic ranges and derivate the unit size of a GUI EV
3996 const float max_DR = ceilf(fmaxf(display_HL_EV, scene_HL_EV)) + ceilf(fmaxf(display_LL_EV, scene_LL_EV));
3997 const float EV = (column_right) / max_DR;
3998
3999 // all greys are aligned vertically in GUI since they are the fulcrum of the transform
4000 // so, get their coordinates
4001 const float grey_EV = fmaxf(ceilf(display_HL_EV), ceilf(scene_HL_EV));
4002 const float grey_x = g->allocation.width - (grey_EV)*EV - darktable.bauhaus->quad_width;
4003
4004 // similarly, get black/white coordinates from grey point
4005 const float display_black_x = grey_x - display_real_black_EV * EV;
4006 const float display_DR_start_x = grey_x - display_LL_EV * EV;
4007 const float display_white_x = grey_x + display_HL_EV * EV;
4008
4009 const float scene_black_x = grey_x - scene_LL_EV * EV;
4010 const float scene_white_x = grey_x + scene_HL_EV * EV;
4011 const float scene_lat_bottom = grey_x + (g->spline.x[1] - g->spline.x[2]) * EV * DR;
4012 const float scene_lat_top = grey_x + (g->spline.x[3] - g->spline.x[2]) * EV * DR;
4013
4014 // show EV zones for display - zones are aligned on 0% and 100%
4015 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
4016
4017 // latitude bounds - show contrast expansion
4018
4019 // Compute usual filmic mapping
4020 float display_lat_bottom = filmic_spline(g->spline.latitude_min, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
4021 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
4022 display_lat_bottom = powf(fmaxf(display_lat_bottom, NORM_MIN), p->output_power); // clamp at -16 EV
4023
4024 // rescale output to log scale
4025 display_lat_bottom = log2f(display_lat_bottom/ (p->grey_point_target / 100.f));
4026
4027 // take clamping into account
4028 if(display_lat_bottom < 0.f) // clamp to - 8 EV (black)
4029 display_lat_bottom = fmaxf(display_lat_bottom, -display_real_black_EV);
4030 else if(display_lat_bottom > 0.f) // clamp to 0 EV (white)
4031 display_lat_bottom = fminf(display_lat_bottom, display_HL_EV);
4032
4033 // get destination coordinate
4034 display_lat_bottom = grey_x + display_lat_bottom * EV;
4035
4036 // Compute usual filmic mapping
4037 float display_lat_top = filmic_spline(g->spline.latitude_max, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
4038 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
4039 display_lat_top = powf(fmaxf(display_lat_top, NORM_MIN), p->output_power); // clamp at -16 EV
4040
4041 // rescale output to log scale
4042 display_lat_top = log2f(display_lat_top / (p->grey_point_target / 100.f));
4043
4044 // take clamping into account
4045 if(display_lat_top < 0.f) // clamp to - 8 EV (black)
4046 display_lat_top = fmaxf(display_lat_top, -display_real_black_EV);
4047 else if(display_lat_top > 0.f) // clamp to 0 EV (white)
4048 display_lat_top = fminf(display_lat_top, display_HL_EV);
4049
4050 // get destination coordinate and draw
4051 display_lat_top = grey_x + display_lat_top * EV;
4052
4053 cairo_move_to(cr, scene_lat_bottom, scene_top);
4054 cairo_line_to(cr, scene_lat_top, scene_top);
4055 cairo_line_to(cr, display_lat_top, display_bottom);
4056 cairo_line_to(cr, display_lat_bottom, display_bottom);
4057 cairo_line_to(cr, scene_lat_bottom, scene_top);
4059 cairo_fill(cr);
4060
4061 for(int i = 0; i < (int)ceilf(display_DR); i++)
4062 {
4063 // content
4064 const float shade = powf(exp2f(-11.f + (float)i), 1.f / 2.4f);
4065 cairo_set_source_rgb(cr, shade, shade, shade);
4066 cairo_rectangle(cr, display_DR_start_x + i * EV, display_top, EV, g->line_height);
4067 cairo_fill_preserve(cr);
4068
4069 // borders
4070 cairo_set_source_rgb(cr, 0.75, .5, 0.);
4071 cairo_stroke(cr);
4072 }
4073
4074 // middle grey display
4075 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
4076 cairo_move_to(cr, grey_x, display_bottom + 2. * g->inset);
4077 cairo_line_to(cr, grey_x, display_top - 2. * g->inset);
4078 cairo_stroke(cr);
4079
4080 // show EV zones for scene - zones are aligned on grey
4081
4082 for(int i = floorf(p->black_point_source); i < ceilf(p->white_point_source); i++)
4083 {
4084 // content
4085 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
4086 const float shade = powf(0.1845f * exp2f((float)i), 1.f / 2.4f);
4087 const float x_temp = grey_x + i * EV;
4088 cairo_set_source_rgb(cr, shade, shade, shade);
4089 cairo_rectangle(cr, x_temp, scene_top, EV, g->line_height);
4090 cairo_fill_preserve(cr);
4091
4092 // borders
4093 cairo_set_source_rgb(cr, 0.75, .5, 0.);
4094 cairo_stroke(cr);
4095
4096 // arrows
4097 if(i == 0)
4098 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
4099 else
4100 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
4101
4102 if((float)i > p->black_point_source && (float)i < p->white_point_source)
4103 {
4104 // Compute usual filmic mapping
4105 const float normal_value = ((float)i - p->black_point_source) / DR;
4106 float y_temp = filmic_spline(normal_value, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
4107 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
4108 y_temp = powf(fmaxf(y_temp, NORM_MIN), p->output_power); // clamp at -16 EV
4109
4110 // rescale output to log scale
4111 y_temp = log2f(y_temp / (p->grey_point_target / 100.f));
4112
4113 // take clamping into account
4114 if(y_temp < 0.f) // clamp to - 8 EV (black)
4115 y_temp = fmaxf(y_temp, -display_real_black_EV);
4116 else if(y_temp > 0.f) // clamp to 0 EV (white)
4117 y_temp = fminf(y_temp, display_HL_EV);
4118
4119 // get destination coordinate and draw
4120 y_temp = grey_x + y_temp * EV;
4121 dt_cairo_draw_arrow(cr, x_temp, scene_top, y_temp, display_bottom, FALSE);
4122 }
4123 }
4124
4125 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
4126
4127 // arrows for black and white
4128 float x_temp = grey_x + p->black_point_source * EV;
4129 float y_temp = grey_x - display_real_black_EV * EV;
4130 dt_cairo_draw_arrow(cr, x_temp, scene_top, y_temp, display_bottom, FALSE);
4131
4132 x_temp = grey_x + p->white_point_source * EV;
4133 y_temp = grey_x + display_HL_EV * EV;
4134 dt_cairo_draw_arrow(cr, x_temp, scene_top, y_temp, display_bottom, FALSE);
4135
4136 // draw white - grey - black ticks
4137
4138 // black display
4139 cairo_move_to(cr, display_black_x, display_bottom);
4140 cairo_line_to(cr, display_black_x, display_top - 2. * g->inset);
4141 cairo_stroke(cr);
4142
4143 // middle grey display
4144 cairo_move_to(cr, grey_x, display_bottom);
4145 cairo_line_to(cr, grey_x, display_top - 2. * g->inset);
4146 cairo_stroke(cr);
4147
4148 // white display
4149 cairo_move_to(cr, display_white_x, display_bottom);
4150 cairo_line_to(cr, display_white_x, display_top - 2. * g->inset);
4151 cairo_stroke(cr);
4152
4153 // black scene
4154 cairo_move_to(cr, scene_black_x, scene_bottom + 2. * g->inset);
4155 cairo_line_to(cr, scene_black_x, scene_top);
4156 cairo_stroke(cr);
4157
4158 // middle grey scene
4159 cairo_move_to(cr, grey_x, scene_bottom + 2. * g->inset);
4160 cairo_line_to(cr, grey_x, scene_top);
4161 cairo_stroke(cr);
4162
4163 // white scene
4164 cairo_move_to(cr, scene_white_x, scene_bottom + 2. * g->inset);
4165 cairo_line_to(cr, scene_white_x, scene_top);
4166 cairo_stroke(cr);
4167
4168 // legends
4170
4171 // black scene legend
4172 snprintf(text, sizeof(text), "%+.1f", p->black_point_source);
4173 pango_layout_set_text(layout, text, -1);
4174 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4175 cairo_move_to(cr, scene_black_x - 0.5 * g->ink.width - g->ink.x,
4176 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
4177 pango_cairo_show_layout(cr, layout);
4178 cairo_stroke(cr);
4179
4180 // grey scene legend
4181 snprintf(text, sizeof(text), "%+.1f", 0.f);
4182 pango_layout_set_text(layout, text, -1);
4183 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4184 cairo_move_to(cr, grey_x - 0.5 * g->ink.width - g->ink.x,
4185 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
4186 pango_cairo_show_layout(cr, layout);
4187 cairo_stroke(cr);
4188
4189 // white scene legend
4190 snprintf(text, sizeof(text), "%+.1f", p->white_point_source);
4191 pango_layout_set_text(layout, text, -1);
4192 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4193 cairo_move_to(cr, scene_white_x - 0.5 * g->ink.width - g->ink.x,
4194 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
4195 pango_cairo_show_layout(cr, layout);
4196 cairo_stroke(cr);
4197
4198 // black scene legend
4199 snprintf(text, sizeof(text), "%.0f", p->black_point_target);
4200 pango_layout_set_text(layout, text, -1);
4201 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4202 cairo_move_to(cr, display_black_x - 0.5 * g->ink.width - g->ink.x,
4203 display_top - 4. * g->inset - g->ink.height - g->ink.y);
4204 pango_cairo_show_layout(cr, layout);
4205 cairo_stroke(cr);
4206
4207 // grey scene legend
4208 snprintf(text, sizeof(text), "%.0f", p->grey_point_target);
4209 pango_layout_set_text(layout, text, -1);
4210 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4211 cairo_move_to(cr, grey_x - 0.5 * g->ink.width - g->ink.x,
4212 display_top - 4. * g->inset - g->ink.height - g->ink.y);
4213 pango_cairo_show_layout(cr, layout);
4214 cairo_stroke(cr);
4215
4216 // white scene legend
4217 snprintf(text, sizeof(text), "%.0f", p->white_point_target);
4218 pango_layout_set_text(layout, text, -1);
4219 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4220 cairo_move_to(cr, display_white_x - 0.5 * g->ink.width - g->ink.x,
4221 display_top - 4. * g->inset - g->ink.height - g->ink.y);
4222 pango_cairo_show_layout(cr, layout);
4223 cairo_stroke(cr);
4224 }
4225
4226 // restore font size
4227 pango_font_description_set_size(desc, font_size);
4228 pango_layout_set_font_description(layout, desc);
4229
4230 cairo_destroy(cr);
4231 cairo_set_source_surface(crf, cst, 0, 0);
4232 cairo_paint(crf);
4233 cairo_surface_destroy(cst);
4234 g_object_unref(layout);
4235 pango_font_description_free(desc);
4236 return TRUE;
4237}
4238
4239static gboolean area_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
4240{
4241 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4242 if(darktable.gui->reset) return TRUE;
4243
4245
4247
4248 if(g->active_button != DT_FILMIC_GUI_BUTTON_LAST)
4249 {
4250
4251 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
4252 {
4253 // double click resets view
4254 if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
4255 {
4256 g->gui_mode = DT_FILMIC_GUI_LOOK;
4257 gtk_widget_queue_draw(GTK_WIDGET(g->area));
4258 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_view", g->gui_mode);
4259 return TRUE;
4260 }
4261 else
4262 {
4263 return FALSE;
4264 }
4265 }
4266 else if(event->button == 1)
4267 {
4268 // simple left click cycles through modes in positive direction
4269 if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
4270 {
4271 // cycle type of graph
4272 if(g->gui_mode == DT_FILMIC_GUI_RANGES)
4273 g->gui_mode = DT_FILMIC_GUI_LOOK;
4274 else
4275 g->gui_mode++;
4276
4277 gtk_widget_queue_draw(GTK_WIDGET(g->area));
4278 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_view", g->gui_mode);
4279 return TRUE;
4280 }
4281 else if(g->active_button == DT_FILMIC_GUI_BUTTON_LABELS)
4282 {
4283 g->gui_show_labels = !g->gui_show_labels;
4284 gtk_widget_queue_draw(GTK_WIDGET(g->area));
4285 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_show_labels", g->gui_show_labels);
4286 return TRUE;
4287 }
4288 else
4289 {
4290 // we should never get there since (g->active_button != DT_FILMIC_GUI_BUTTON_LAST)
4291 // and any other case has been processed above.
4292 return FALSE;
4293 }
4294 }
4295 else if(event->button == 3)
4296 {
4297 // simple right click cycles through modes in negative direction
4298 if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
4299 {
4300 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
4301 g->gui_mode = DT_FILMIC_GUI_RANGES;
4302 else
4303 g->gui_mode--;
4304
4305 gtk_widget_queue_draw(GTK_WIDGET(g->area));
4306 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_view", g->gui_mode);
4307 return TRUE;
4308 }
4309 else if(g->active_button == DT_FILMIC_GUI_BUTTON_LABELS)
4310 {
4311 g->gui_show_labels = !g->gui_show_labels;
4312 gtk_widget_queue_draw(GTK_WIDGET(g->area));
4313 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_show_labels", g->gui_show_labels);
4314 return TRUE;
4315 }
4316 else
4317 {
4318 return FALSE;
4319 }
4320 }
4321 }
4322
4323 return FALSE;
4324}
4325
4326static gboolean area_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
4327{
4328 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4329 if(darktable.gui->reset) return 1;
4330 if(!self->enabled) return 0;
4331
4333 g->gui_hover = TRUE;
4334 gtk_widget_queue_draw(GTK_WIDGET(g->area));
4335 return TRUE;
4336}
4337
4338
4339static gboolean area_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
4340{
4341 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4342 if(darktable.gui->reset) return 1;
4343 if(!self->enabled) return 0;
4344
4346 g->gui_hover = FALSE;
4347 gtk_widget_queue_draw(GTK_WIDGET(g->area));
4348 return TRUE;
4349}
4350
4351static gboolean area_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
4352{
4353 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4354 if(darktable.gui->reset) return 1;
4355
4357 if(!g->gui_sizes_inited) return FALSE;
4358
4359 // get in-widget coordinates
4360 const float y = event->y;
4361 const float x = event->x;
4362
4363 if(x > 0. && x < g->allocation.width && y > 0. && y < g->allocation.height) g->gui_hover = TRUE;
4364
4365 gint save_active_button = g->active_button;
4366
4367 if(g->gui_hover)
4368 {
4369 // find out which button is under the mouse
4370 gint found_something = FALSE;
4371 for(int i = 0; i < DT_FILMIC_GUI_BUTTON_LAST; i++)
4372 {
4373 // check if mouse in in the button's bounds
4374 if(x > g->buttons[i].left && x < g->buttons[i].right && y > g->buttons[i].top && y < g->buttons[i].bottom)
4375 {
4376 // yeah, mouse is over that button
4377 g->buttons[i].mouse_hover = TRUE;
4378 g->active_button = i;
4379 found_something = TRUE;
4380 }
4381 else
4382 {
4383 // no luck with this button
4384 g->buttons[i].mouse_hover = FALSE;
4385 }
4386 }
4387
4388 if(!found_something) g->active_button = DT_FILMIC_GUI_BUTTON_LAST; // mouse is over no known button
4389
4390 // update the tooltips
4391 if(g->active_button == DT_FILMIC_GUI_BUTTON_LAST && x < g->buttons[0].left)
4392 {
4393 // we are over the graph area
4394 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("use the parameters below to set the nodes.\n"
4395 "the bright curve is the filmic tone mapping curve\n"
4396 "the dark curve is the desaturation curve."));
4397 }
4398 else if(g->active_button == DT_FILMIC_GUI_BUTTON_LABELS)
4399 {
4400 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("toggle axis labels and values display"));
4401 }
4402 else if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
4403 {
4404 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("cycle through graph views.\n"
4405 "left click: cycle forward.\n"
4406 "right click: cycle backward.\n"
4407 "double-click: reset to look view."));
4408 }
4409 else
4410 {
4411 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), "");
4412 }
4413
4414 if(save_active_button != g->active_button) gtk_widget_queue_draw(GTK_WIDGET(g->area));
4415 return TRUE;
4416 }
4417 else
4418 {
4419 g->active_button = DT_FILMIC_GUI_BUTTON_LAST;
4420 if(save_active_button != g->active_button) (GTK_WIDGET(g->area));
4421 return FALSE;
4422 }
4423}
4424
4425static gboolean area_scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
4426{
4427 if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
4428 {
4429 int delta_y;
4430 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
4431 {
4432 //adjust aspect
4433 const int aspect = dt_conf_get_int("plugins/darkroom/filmicrgb/aspect_percent");
4434 dt_conf_set_int("plugins/darkroom/filmicrgb/aspect_percent", aspect + delta_y);
4435 dtgtk_drawing_area_set_aspect_ratio(widget, aspect / 100.0);
4436 }
4437 return TRUE; // Ensure that scrolling cannot move side panel when no delta
4438 }
4439 return FALSE;
4440}
4441
4443{
4445
4446 g->show_mask = FALSE;
4447 g->gui_mode = DT_FILMIC_GUI_LOOK;
4448 g->gui_show_labels = TRUE;
4449 g->gui_hover = FALSE;
4450 g->gui_sizes_inited = FALSE;
4451
4452 // don't make the area square to safe some vertical space -- it's not interactive anyway
4453 const float aspect = dt_conf_get_int("plugins/darkroom/filmicrgb/aspect_percent") / 100.0;
4454 g->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(aspect));
4455 g_object_set_data(G_OBJECT(g->area), "iop-instance", self);
4456
4457 gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
4458 gtk_widget_add_events(GTK_WIDGET(g->area), GDK_BUTTON_PRESS_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
4459 | GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask);
4460 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(dt_iop_tonecurve_draw), self);
4461 g_signal_connect(G_OBJECT(g->area), "button-press-event", G_CALLBACK(area_button_press), self);
4462 g_signal_connect(G_OBJECT(g->area), "leave-notify-event", G_CALLBACK(area_leave_notify), self);
4463 g_signal_connect(G_OBJECT(g->area), "enter-notify-event", G_CALLBACK(area_enter_notify), self);
4464 g_signal_connect(G_OBJECT(g->area), "motion-notify-event", G_CALLBACK(area_motion_notify), self);
4465 g_signal_connect(G_OBJECT(g->area), "scroll-event", G_CALLBACK(area_scroll_callback), self);
4466
4467 // Init GTK notebook
4468 g->notebook = dt_ui_notebook_new();
4469
4470 // Page SCENE
4471 self->widget = dt_ui_notebook_page(g->notebook, N_("scene"), NULL);
4472
4473 g->grey_point_source
4474 = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "grey_point_source"));
4475 dt_bauhaus_slider_set_soft_range(g->grey_point_source, .1, 36.0);
4476 dt_bauhaus_slider_set_format(g->grey_point_source, "%");
4477 gtk_widget_set_tooltip_text(g->grey_point_source,
4478 _("adjust to match the average luminance of the image's subject.\n"
4479 "the value entered here will then be remapped to 18.45%.\n"
4480 "decrease the value to increase the overall brightness."));
4481
4482 // White slider
4483 g->white_point_source
4484 = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "white_point_source"));
4485 dt_bauhaus_slider_set_soft_range(g->white_point_source, 2.0, 8.0);
4486 dt_bauhaus_slider_set_format(g->white_point_source, _(" EV"));
4487 gtk_widget_set_tooltip_text(g->white_point_source,
4488 _("number of stops between middle gray and pure white.\n"
4489 "this is a reading a lightmeter would give you on the scene.\n"
4490 "adjust so highlights clipping is avoided"));
4491
4492 // Black slider
4493 g->black_point_source
4494 = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "black_point_source"));
4495 dt_bauhaus_slider_set_soft_range(g->black_point_source, -14.0, -3);
4496 dt_bauhaus_slider_set_format(g->black_point_source, _(" EV"));
4497 gtk_widget_set_tooltip_text(
4498 g->black_point_source, _("number of stops between middle gray and pure black.\n"
4499 "this is a reading a lightmeter would give you on the scene.\n"
4500 "increase to get more contrast.\ndecrease to recover more details in low-lights."));
4501
4502 // Dynamic range scaling
4503 g->security_factor = dt_bauhaus_slider_from_params(self, "security_factor");
4504 dt_bauhaus_slider_set_soft_max(g->security_factor, 50);
4505 dt_bauhaus_slider_set_format(g->security_factor, "%");
4506 gtk_widget_set_tooltip_text(g->security_factor, _("symmetrically enlarge or shrink the computed dynamic range.\n"
4507 "useful to give a safety margin to extreme luminances."));
4508
4509 // Auto tune slider
4510 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
4511 gtk_box_pack_start(GTK_BOX(hbox), dt_ui_label_new(_("auto tune levels")), TRUE, TRUE, 0);
4512 g->auto_button = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, NULL);
4513 gtk_box_pack_start(GTK_BOX(hbox), g->auto_button, FALSE, FALSE, 0);
4514 dt_gui_add_class(g->auto_button, "dt_bauhaus_alignment");
4515 gtk_widget_set_tooltip_text(g->auto_button, _("try to optimize the settings with some statistical assumptions.\n"
4516 "this will fit the luminance range inside the histogram bounds.\n"
4517 "works better for landscapes and evenly-lit pictures\n"
4518 "but fails for high-keys, low-keys and high-ISO pictures.\n"
4519 "this is not an artificial intelligence, but a simple guess.\n"
4520 "ensure you understand its assumptions before using it."));
4521 gtk_box_pack_start(GTK_BOX(self->widget), hbox, FALSE, FALSE, 0);
4522
4523 GtkWidget *label = dt_ui_section_label_new(_("advanced"));
4524 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
4525
4526 g->custom_grey = dt_bauhaus_toggle_from_params(self, "custom_grey");
4527 gtk_widget_set_tooltip_text(g->custom_grey, _("enable to input custom middle-gray values.\n"
4528 "this is not recommended in general.\n"
4529 "fix the global exposure in the exposure module instead.\n"
4530 "disable to use standard 18.45 %% middle gray."));
4531
4532 // Page RECONSTRUCT
4533 self->widget = dt_ui_notebook_page(g->notebook, N_("reconstruct"), NULL);
4534
4535 label = dt_ui_section_label_new(_("highlights clipping"));
4536 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
4537
4538 g->reconstruct_threshold = dt_bauhaus_slider_from_params(self, "reconstruct_threshold");
4539 dt_bauhaus_slider_set_format(g->reconstruct_threshold, _(" EV"));
4540 gtk_widget_set_tooltip_text(g->reconstruct_threshold,
4541 _("set the exposure threshold upon which\n"
4542 "clipped highlights get reconstructed.\n"
4543 "values are relative to the scene white point.\n"
4544 "0 EV means the threshold is the same as the scene white point.\n"
4545 "decrease to include more areas,\n"
4546 "increase to exclude more areas."));
4547
4548 g->reconstruct_feather = dt_bauhaus_slider_from_params(self, "reconstruct_feather");
4549 dt_bauhaus_slider_set_format(g->reconstruct_feather, _(" EV"));
4550 gtk_widget_set_tooltip_text(g->reconstruct_feather,
4551 _("soften the transition between clipped highlights and valid pixels.\n"
4552 "decrease to make the transition harder and sharper,\n"
4553 "increase to make the transition softer and blurrier."));
4554
4555 // Highlight Reconstruction Mask
4556 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
4557 gtk_box_pack_start(GTK_BOX(hbox), dt_ui_label_new(_("display highlight reconstruction mask")), TRUE, TRUE, 0);
4558 g->show_highlight_mask = dt_iop_togglebutton_new(self, NULL, N_("display highlight reconstruction mask"), NULL, G_CALLBACK(show_mask_callback),
4559 FALSE, 0, 0, dtgtk_cairo_paint_showmask, hbox);
4561 dt_gui_add_class(g->show_highlight_mask, "dt_bauhaus_alignment");
4562 dt_gui_add_class(g->show_highlight_mask, "dt_transparent_background");
4563 gtk_box_pack_start(GTK_BOX(self->widget), hbox, FALSE, FALSE, 0);
4564
4565 label = dt_ui_section_label_new(_("balance"));
4566 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
4567
4568 g->reconstruct_structure_vs_texture = dt_bauhaus_slider_from_params(self, "reconstruct_structure_vs_texture");
4569 dt_bauhaus_slider_set_format(g->reconstruct_structure_vs_texture, "%");
4570 gtk_widget_set_tooltip_text(g->reconstruct_structure_vs_texture,
4571 /* xgettext:no-c-format */
4572 _("decide which reconstruction strategy to favor,\n"
4573 "between inpainting a smooth color gradient,\n"
4574 "or trying to recover the textured details.\n"
4575 "0% is an equal mix of both.\n"
4576 "increase if at least one RGB channel is not clipped.\n"
4577 "decrease if all RGB channels are clipped over large areas."));
4578
4579 g->reconstruct_bloom_vs_details = dt_bauhaus_slider_from_params(self, "reconstruct_bloom_vs_details");
4580 dt_bauhaus_slider_set_format(g->reconstruct_bloom_vs_details, "%");
4581 gtk_widget_set_tooltip_text(g->reconstruct_bloom_vs_details,
4582 /* xgettext:no-c-format */
4583 _("decide which reconstruction strategy to favor,\n"
4584 "between blooming highlights like film does,\n"
4585 "or trying to recover sharp details.\n"
4586 "0% is an equal mix of both.\n"
4587 "increase if you want more details.\n"
4588 "decrease if you want more blur."));
4589
4590 // Bloom threshold
4591 g->reconstruct_grey_vs_color = dt_bauhaus_slider_from_params(self, "reconstruct_grey_vs_color");
4592 dt_bauhaus_slider_set_format(g->reconstruct_grey_vs_color, "%");
4593 gtk_widget_set_tooltip_text(g->reconstruct_grey_vs_color,
4594 /* xgettext:no-c-format */
4595 _("decide which reconstruction strategy to favor,\n"
4596 "between recovering monochromatic highlights,\n"
4597 "or trying to recover colorful highlights.\n"
4598 "0% is an equal mix of both.\n"
4599 "increase if you want more color.\n"
4600 "decrease if you see magenta or out-of-gamut highlights."));
4601
4602 label = dt_ui_section_label_new(_("advanced"));
4603 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
4604
4605 // Color inpainting
4606 g->high_quality_reconstruction = dt_bauhaus_slider_from_params(self, "high_quality_reconstruction");
4607 gtk_widget_set_tooltip_text(g->high_quality_reconstruction,
4608 _("run extra passes of chromaticity reconstruction.\n"
4609 "more iterations means more color propagation from neighbourhood.\n"
4610 "this will be slower but will yield more neutral highlights.\n"
4611 "it also helps with difficult cases of magenta highlights."));
4612
4613 // Highlight noise
4614 g->noise_level = dt_bauhaus_slider_from_params(self, "noise_level");
4615 gtk_widget_set_tooltip_text(g->noise_level, _("add statistical noise in reconstructed highlights.\n"
4616 "this avoids highlights to look too smooth\n"
4617 "when the picture is noisy overall,\n"
4618 "so they blend with the rest of the picture."));
4619
4620 // Noise distribution
4621 g->noise_distribution = dt_bauhaus_combobox_from_params(self, "noise_distribution");
4622 gtk_widget_set_tooltip_text(g->noise_distribution, _("choose the statistical distribution of noise.\n"
4623 "this is useful to match natural sensor noise pattern.\n"));
4624
4625 // Page LOOK
4626 self->widget = dt_ui_notebook_page(g->notebook, N_("look"), NULL);
4627
4628 label = dt_ui_section_label_new(_("tone mapping"));
4629 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
4630
4631 g->contrast = dt_bauhaus_slider_from_params(self, N_("contrast"));
4632 dt_bauhaus_slider_set_soft_range(g->contrast, 0.5, 3.0);
4633 dt_bauhaus_slider_set_digits(g->contrast, 3);
4634 gtk_widget_set_tooltip_text(g->contrast, _("slope of the linear part of the curve\n"
4635 "affects mostly the mid-tones"));
4636
4637 // brightness slider
4638 g->output_power = dt_bauhaus_slider_from_params(self, "output_power");
4639 gtk_widget_set_tooltip_text(g->output_power, _("equivalent to paper grade in analog.\n"
4640 "increase to make highlights brighter and less compressed.\n"
4641 "decrease to mute highlights."));
4642
4643 g->latitude = dt_bauhaus_slider_from_params(self, N_("latitude"));
4644 dt_bauhaus_slider_set_soft_range(g->latitude, 0.1, 90.0);
4645 dt_bauhaus_slider_set_format(g->latitude, "%");
4646 gtk_widget_set_tooltip_text(g->latitude,
4647 _("width of the linear domain in the middle of the curve,\n"
4648 "increase to get more contrast and less desaturation at extreme luminances,\n"
4649 "decrease otherwise. no desaturation happens in the latitude range.\n"
4650 "this has no effect on mid-tones."));
4651
4652 g->balance = dt_bauhaus_slider_from_params(self, "balance");
4653 dt_bauhaus_slider_set_format(g->balance, "%");
4654 gtk_widget_set_tooltip_text(g->balance, _("slides the latitude along the slope\n"
4655 "to give more room to shadows or highlights.\n"
4656 "use it if you need to protect the details\n"
4657 "at one extremity of the histogram."));
4658
4659 // Curve type
4660 g->highlights = dt_bauhaus_combobox_from_params(self, "highlights");
4661 gtk_widget_set_tooltip_text(g->highlights, _("choose the desired curvature of the filmic spline in highlights.\n"
4662 "hard uses a high curvature resulting in more tonal compression.\n"
4663 "soft uses a low curvature resulting in less tonal compression."));
4664
4665 g->shadows = dt_bauhaus_combobox_from_params(self, "shadows");
4666 gtk_widget_set_tooltip_text(g->shadows, _("choose the desired curvature of the filmic spline in shadows.\n"
4667 "hard uses a high curvature resulting in more tonal compression.\n"
4668 "soft uses a low curvature resulting in less tonal compression."));
4669
4670 label = dt_ui_section_label_new(_("color mapping"));
4671 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
4672
4673 g->saturation = dt_bauhaus_slider_from_params(self, "saturation");
4674 dt_bauhaus_slider_set_soft_range(g->saturation, -50.0, 50.0);
4675 dt_bauhaus_slider_set_format(g->saturation, "%");
4676 gtk_widget_set_tooltip_text(g->saturation, _("desaturates the output of the module\n"
4677 "specifically at extreme luminances.\n"
4678 "increase if shadows and/or highlights are under-saturated."));
4679
4680 g->preserve_color = dt_bauhaus_combobox_from_params(self, "preserve_color");
4681 gtk_widget_set_tooltip_text(g->preserve_color, _("ensure the original color are preserved.\n"
4682 "may reinforce chromatic aberrations and chroma noise,\n"
4683 "so ensure they are properly corrected elsewhere.\n"));
4684
4685 label = dt_ui_section_label_new(_("advanced"));
4686 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
4687
4688 // Color science
4689 g->version = dt_bauhaus_combobox_from_params(self, "version");
4690 gtk_widget_set_tooltip_text(g->version,
4691 _("v3 is darktable 3.0 desaturation method, same as color balance.\n"
4692 "v4 is a newer desaturation method, based on spectral purity of light."));
4693
4694
4695 g->auto_hardness = dt_bauhaus_toggle_from_params(self, "auto_hardness");
4696 gtk_widget_set_tooltip_text(
4697 g->auto_hardness, _("enable to auto-set the look hardness depending on the scene white and black points.\n"
4698 "this keeps the middle gray on the identity line and improves fast tuning.\n"
4699 "disable if you want a manual control."));
4700
4701 // Page DISPLAY
4702 self->widget = dt_ui_notebook_page(g->notebook, N_("display"), NULL);
4703
4704 // Black slider
4705 g->black_point_target = dt_bauhaus_slider_from_params(self, "black_point_target");
4706 dt_bauhaus_slider_set_digits(g->black_point_target, 4);
4707 dt_bauhaus_slider_set_format(g->black_point_target, "%");
4708 gtk_widget_set_tooltip_text(g->black_point_target, _("luminance of output pure black, "
4709 "this should be 0%\nexcept if you want a faded look"));
4710
4711 g->grey_point_target = dt_bauhaus_slider_from_params(self, "grey_point_target");
4712 dt_bauhaus_slider_set_digits(g->grey_point_target, 4);
4713 dt_bauhaus_slider_set_format(g->grey_point_target, "%");
4714 gtk_widget_set_tooltip_text(g->grey_point_target,
4715 _("middle gray value of the target display or color space.\n"
4716 "you should never touch that unless you know what you are doing."));
4717
4718 g->white_point_target = dt_bauhaus_slider_from_params(self, "white_point_target");
4719 dt_bauhaus_slider_set_soft_max(g->white_point_target, 100.0);
4720 dt_bauhaus_slider_set_digits(g->white_point_target, 4);
4721 dt_bauhaus_slider_set_format(g->white_point_target, "%");
4722 gtk_widget_set_tooltip_text(g->white_point_target, _("luminance of output pure white, "
4723 "this should be 100%\nexcept if you want a faded look"));
4724
4725 // start building top level widget
4726 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
4727
4728 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->area), TRUE, TRUE, 0);
4729 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->notebook), FALSE, FALSE, 0);
4730}
4731
4732void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
4733{
4736
4737 if(!w || w == g->auto_hardness || w == g->security_factor || w == g->grey_point_source
4738 || w == g->black_point_source || w == g->white_point_source)
4739 {
4740 ++darktable.gui->reset;
4741
4742 if(w == g->security_factor || w == g->grey_point_source)
4743 {
4744 float prev = *(float *)previous;
4745 if(w == g->security_factor)
4746 {
4747 float ratio = (p->security_factor - prev) / (prev + 100.0f);
4748
4749 float EVmin = p->black_point_source;
4750 EVmin = EVmin + ratio * EVmin;
4751
4752 float EVmax = p->white_point_source;
4753 EVmax = EVmax + ratio * EVmax;
4754
4755 p->white_point_source = EVmax;
4756 p->black_point_source = EVmin;
4757 }
4758 else
4759 {
4760 float grey_var = log2f(prev / p->grey_point_source);
4761 p->black_point_source = p->black_point_source - grey_var;
4762 p->white_point_source = p->white_point_source + grey_var;
4763 }
4764
4765 dt_bauhaus_slider_set(g->white_point_source, p->white_point_source);
4766 dt_bauhaus_slider_set(g->black_point_source, p->black_point_source);
4767 }
4768
4769 if(p->auto_hardness)
4770 p->output_power = logf(p->grey_point_target / 100.0f)
4771 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
4772
4773 gtk_widget_set_visible(GTK_WIDGET(g->output_power), !p->auto_hardness);
4774 dt_bauhaus_slider_set(g->output_power, p->output_power);
4775
4776 --darktable.gui->reset;
4777 }
4778
4779 if(!w || w == g->version)
4780 {
4781 if(p->version == DT_FILMIC_COLORSCIENCE_V1 || p->version == DT_FILMIC_COLORSCIENCE_V4)
4782 {
4783 dt_bauhaus_widget_set_label(g->saturation, N_("extreme luminance saturation"));
4784 gtk_widget_set_tooltip_text(g->saturation, _("desaturates the output of the module\n"
4785 "specifically at extreme luminances.\n"
4786 "increase if shadows and/or highlights are under-saturated."));
4787 }
4788 else if(p->version == DT_FILMIC_COLORSCIENCE_V2 || p->version == DT_FILMIC_COLORSCIENCE_V3)
4789 {
4790 dt_bauhaus_widget_set_label(g->saturation, N_("mid-tones saturation"));
4791 gtk_widget_set_tooltip_text(g->saturation, _("desaturates the output of the module\n"
4792 "specifically at medium luminances.\n"
4793 "increase if midtones are under-saturated."));
4794 }
4795 else if(p->version == DT_FILMIC_COLORSCIENCE_V5)
4796 {
4797 dt_bauhaus_widget_set_label(g->saturation, N_("highlights saturation mix"));
4798 gtk_widget_set_tooltip_text(g->saturation, _("Positive values ensure saturation is kept unchanged over the whole range.\n"
4799 "Negative values bleache highlights at constant hue and luminance.\n"
4800 "Zero is an equal mix of both strategies."));
4801 gtk_widget_set_visible(GTK_WIDGET(g->preserve_color), FALSE);
4802 }
4803
4804 if(p->version != DT_FILMIC_COLORSCIENCE_V5)
4805 gtk_widget_set_visible(GTK_WIDGET(g->preserve_color), TRUE);
4806
4807 }
4808
4809 if(!w || w == g->reconstruct_bloom_vs_details)
4810 {
4811 if(p->reconstruct_bloom_vs_details == -100.f)
4812 {
4813 // user disabled the reconstruction in favor of full blooming
4814 // so the structure vs. texture setting doesn't make any difference
4815 // make it insensitive to not confuse users
4816 gtk_widget_set_sensitive(g->reconstruct_structure_vs_texture, FALSE);
4817 }
4818 else
4819 {
4820 gtk_widget_set_sensitive(g->reconstruct_structure_vs_texture, TRUE);
4821 }
4822 }
4823
4824 if(!w || w == g->custom_grey)
4825 {
4826 gtk_widget_set_visible(g->grey_point_source, p->custom_grey);
4827 gtk_widget_set_visible(g->grey_point_target, p->custom_grey);
4828 }
4829
4830 gtk_widget_queue_draw(GTK_WIDGET(g->area));
4831}
4832
4833// clang-format off
4834// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
4835// vim: shiftwidth=2 expandtab tabstop=2 cindent
4836// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
4837// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:191
#define TRUE
Definition ashift_lsd.c:151
#define FALSE
Definition ashift_lsd.c:147
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1206
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:2759
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1183
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:2731
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1212
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:2819
#define DT_BAUHAUS_SPACE
Definition bauhaus.h:262
static void set_color(cairo_t *cr, GdkRGBA color)
Definition bauhaus.h:380
#define INNER_PADDING
Definition bauhaus.h:54
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static void normalize(float *const buffer, const size_t width, const size_t height, const float norm)
Definition blurs.c:354
static void 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, const gboolean clip_negatives)
Definition bspline.h:136
#define BSPLINE_FSIZE
Definition bspline.h:9
const dt_colormatrix_t XYZ_D65_to_D50_CAT16
Definition chromatic_adaptation.h:367
const dt_colormatrix_t XYZ_D50_to_D65_CAT16
Definition chromatic_adaptation.h:357
static float dt_camera_rgb_luminance(const float4 rgb)
Definition color_conversion.h:152
@ IOP_CS_RGB
Definition color_conversion.h:28
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
Definition color_picker_proxy.c:120
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
Definition color_picker_proxy.c:347
@ DT_COLOR_PICKER_AREA
Definition color_picker_proxy.h:37
static float4 gamut_check_Yrg(float4 Ych)
Definition colorspace.h:730
static float4 Yrg_to_Ych(const float4 Yrg)
Definition colorspace.h:556
static float4 Ych_to_Yrg(const float4 Ych)
Definition colorspace.h:571
static float4 Yrg_to_LMS(const float4 Yrg)
Definition colorspace.h:532
static float4 LMS_to_Yrg(const float4 LMS)
Definition colorspace.h:516
static const dt_colormatrix_t XYZ_D65_to_LMS_2006_D65
Definition colorspaces_inline_conversions.h:1035
static const dt_colormatrix_t LMS_2006_D65_to_XYZ_D65
Definition colorspaces_inline_conversions.h:1040
float dt_image_get_exposure_bias(const struct dt_image_t *image_storage)
Definition common/image.c:2508
int dt_image_is_raw(const dt_image_t *img)
Definition common/image.c:120
int type
Definition common/metadata.c:42
void dt_conf_set_int(const char *name, int val)
Definition conf.c:106
int dt_conf_get_int(const char *name)
Definition conf.c:194
void dt_control_log(const char *msg,...)
Definition control.c:424
darktable_t darktable
Definition darktable.c:111
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1395
static void * dt_calloc_align(size_t size)
Definition darktable.h:339
@ DT_DEBUG_OPENCL
Definition darktable.h:478
#define DT_ALIGNED_ARRAY
Definition darktable.h:270
#define for_each_channel(_var,...)
Definition darktable.h:411
static float * dt_alloc_align_float(size_t pixels)
Definition darktable.h:345
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:108
#define dt_free_align(A)
Definition darktable.h:334
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:665
static void * dt_alloc_sse_ps(size_t pixels)
Definition darktable.h:356
dt_noise_distribution_t
Definition data/kernels/noise_generator.h:23
@ DT_NOISE_GAUSSIAN
Definition data/kernels/noise_generator.h:25
@ DT_NOISE_POISSONIAN
Definition data/kernels/noise_generator.h:26
@ DT_NOISE_UNIFORM
Definition data/kernels/noise_generator.h:24
static float4 dt_noise_generator_simd(const dt_noise_distribution_t distribution, const float4 mu, const float4 param, uint state[4])
Definition data/kernels/noise_generator.h:130
static unsigned int splitmix32(const unsigned long seed)
Definition data/kernels/noise_generator.h:30
static float xoshiro128plus(uint state[4])
Definition data/kernels/noise_generator.h:47
#define dt_dev_add_history_item(dev, module, enable)
Definition dev_history.h:72
void dt_iop_params_t
Definition dev_history.h:22
#define dt_dev_invalidate(dev)
Definition develop.h:354
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:82
@ DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU
Definition develop.h:100
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:81
#define dt_dev_refresh_ui_images(dev)
Definition develop.h:338
static float dt_log_scale_axis(const float x, const float base)
Definition draw.h:137
static void dt_draw_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:92
static void dt_draw_loglog_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom, const float base)
Definition draw.h:142
GtkWidget * dtgtk_drawing_area_new_with_aspect_ratio(double aspect)
Definition drawingarea.c:50
void dtgtk_drawing_area_set_aspect_ratio(GtkWidget *widget, double aspect)
Definition drawingarea.c:59
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
static void gamut_mapping(dt_aligned_pixel_t Ych_final, dt_aligned_pixel_t Ych_original, dt_aligned_pixel_t pix_out, const dt_colormatrix_t input_matrix, const dt_colormatrix_t output_matrix, const dt_colormatrix_t export_input_matrix, const dt_colormatrix_t export_output_matrix, const float display_black, const float display_white, const float saturation, const int use_output_profile)
Definition filmicrgb.c:1715
static void norm_tone_mapping_v4(const dt_aligned_pixel_t pix_in, dt_aligned_pixel_t pix_out, const dt_iop_filmicrgb_methods_type_t type, 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 float norm_min, const float norm_max, const float display_black, const float display_white)
Definition filmicrgb.c:1791
static gboolean area_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition filmicrgb.c:4239
static gboolean area_scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
Definition filmicrgb.c:4425
const char ** description(struct dt_iop_module_t *self)
Definition filmicrgb.c:349
int default_group()
Definition filmicrgb.c:360
static gboolean area_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition filmicrgb.c:4339
dt_iop_filmic_noise_distribution_t
Definition filmicrgb.c:174
@ DT_FILMIC_NOISE_UNIFORM
Definition filmicrgb.c:175
@ DT_FILMIC_NOISE_POISSONIAN
Definition filmicrgb.c:177
@ DT_FILMIC_NOISE_GAUSSIAN
Definition filmicrgb.c:176
dt_iop_filmicrgb_curve_type_t
Definition filmicrgb.c:123
@ DT_FILMIC_CURVE_RATIONAL
Definition filmicrgb.c:126
@ DT_FILMIC_CURVE_POLY_4
Definition filmicrgb.c:124
@ DT_FILMIC_CURVE_POLY_3
Definition filmicrgb.c:125
void gui_reset(dt_iop_module_t *self)
Definition filmicrgb.c:3317
void filmic_gui_draw_icon(cairo_t *cr, struct dt_iop_filmicrgb_gui_button_data_t *button, struct dt_iop_filmicrgb_gui_data_t *g)
Definition filmicrgb.c:3351
static float pixel_rgb_norm_power(const dt_aligned_pixel_t pixel)
Definition filmicrgb.c:708
void tiling_callback(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out, struct dt_develop_tiling_t *tiling)
Definition filmicrgb.c:2049
static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
Definition filmicrgb.c:3391
static int get_scales(const dt_iop_roi_t *roi_in, const dt_dev_pixelpipe_iop_t *const piece)
Definition filmicrgb.c:1158
static void gamut_check_RGB(const dt_colormatrix_t matrix_in, const dt_colormatrix_t matrix_out, const float display_black, const float display_white, const dt_aligned_pixel_t Ych_in, dt_aligned_pixel_t RGB_out)
Definition filmicrgb.c:1678
static void RGB_tone_mapping_v4(const dt_aligned_pixel_t pix_in, dt_aligned_pixel_t pix_out, const dt_iop_filmicrgb_data_t *const data, const dt_iop_filmic_rgb_spline_t spline, const float display_black, const float display_white)
Definition filmicrgb.c:1828
static void 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)
Definition filmicrgb.c:1001
dt_iop_filmic_rgb_gui_mode_t
Definition filmicrgb.c:164
@ DT_FILMIC_GUI_LOOK
Definition filmicrgb.c:165
@ DT_FILMIC_GUI_RANGES
Definition filmicrgb.c:168
@ DT_FILMIC_GUI_BASECURVE
Definition filmicrgb.c:166
@ DT_FILMIC_GUI_BASECURVE_LOG
Definition filmicrgb.c:167
@ DT_FILMIC_GUI_LAST
Definition filmicrgb.c:169
void reload_defaults(dt_iop_module_t *module)
Definition filmicrgb.c:3247
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:2786
dt_iop_filmicrgb_colorscience_type_t
Definition filmicrgb.c:131
@ DT_FILMIC_COLORSCIENCE_V1
Definition filmicrgb.c:132
@ DT_FILMIC_COLORSCIENCE_V2
Definition filmicrgb.c:133
@ DT_FILMIC_COLORSCIENCE_V5
Definition filmicrgb.c:136
@ DT_FILMIC_COLORSCIENCE_V4
Definition filmicrgb.c:135
@ DT_FILMIC_COLORSCIENCE_V3
Definition filmicrgb.c:134
static float clip_chroma(const dt_colormatrix_t matrix_out, const float target_white, const float Y, const float cos_h, const float sin_h, const float chroma)
Definition filmicrgb.c:1655
#define LOGBASE
Definition filmicrgb.c:3322
static float filmic_desaturate_v1(const float x, const float sigma_toe, const float sigma_shoulder, const float saturation)
Definition filmicrgb.c:862
void gui_update(dt_iop_module_t *self)
Definition filmicrgb.c:3226
static void filmic_chroma_v4(const float *const restrict in, float *const restrict out, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const export_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, const float display_black, const float display_white)
Definition filmicrgb.c:1848
static void dt_cairo_draw_arrow(cairo_t *cr, double origin_x, double origin_y, double destination_x, double destination_y, gboolean show_head)
Definition filmicrgb.c:3324
static void 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)
Definition filmicrgb.c:1324
const char * aliases()
Definition filmicrgb.c:344
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:370
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition filmicrgb.c:3199
static void 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)
Definition filmicrgb.c:1280
static void apply_autotune(dt_iop_module_t *self)
Definition filmicrgb.c:2746
#define CIE_Y_1931_to_CIE_Y_2006(x)
Definition filmicrgb.c:1577
static void restore_ratios(float *const restrict ratios, const float *const restrict norms, const size_t width, const size_t height)
Definition filmicrgb.c:2036
const char * name()
Definition filmicrgb.c:339
static void 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)
Definition filmicrgb.c:1424
void gui_init(dt_iop_module_t *self)
Definition filmicrgb.c:4442
dt_iop_filmicrgb_methods_type_t
Definition filmicrgb.c:112
@ DT_FILMIC_METHOD_POWER_NORM
Definition filmicrgb.c:116
@ DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1
Definition filmicrgb.c:118
@ DT_FILMIC_METHOD_NONE
Definition filmicrgb.c:113
@ DT_FILMIC_METHOD_MAX_RGB
Definition filmicrgb.c:114
@ DT_FILMIC_METHOD_EUCLIDEAN_NORM_V2
Definition filmicrgb.c:117
@ DT_FILMIC_METHOD_LUMINANCE
Definition filmicrgb.c:115
static float log_tonemapping(const float x, const float grey, const float black, const float dynamic_range)
Definition filmicrgb.c:774
static float get_pixel_norm(const dt_aligned_pixel_t pixel, const dt_iop_filmicrgb_methods_type_t variant, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition filmicrgb.c:732
static void init_reconstruct(const float *const restrict in, const float *const restrict mask, float *const restrict reconstructed, const size_t width, const size_t height)
Definition filmicrgb.c:1126
dt_iop_filmicrgb_reconstruction_type_t
Definition filmicrgb.c:147
@ DT_FILMIC_RECONSTRUCT_RATIOS
Definition filmicrgb.c:149
@ DT_FILMIC_RECONSTRUCT_RGB
Definition filmicrgb.c:148
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition filmicrgb.c:4732
dt_iop_filmicrgb_gui_button_t
Definition filmicrgb.c:217
@ DT_FILMIC_GUI_BUTTON_LAST
Definition filmicrgb.c:220
@ DT_FILMIC_GUI_BUTTON_LABELS
Definition filmicrgb.c:219
@ DT_FILMIC_GUI_BUTTON_TYPE
Definition filmicrgb.c:218
void 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)
Definition filmicrgb.c:2069
void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:3127
static gint 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)
Definition filmicrgb.c:906
#define ORDER_4
Definition filmicrgb.c:2823
static float exp_tonemapping_v2(const float x, const float grey, const float black, const float dynamic_range)
Definition filmicrgb.c:783
static void pipe_RGB_to_Ych(const dt_aligned_pixel_t in, const dt_colormatrix_t matrix, dt_aligned_pixel_t out)
Definition filmicrgb.c:1502
static float filmic_desaturate_v2(const float x, const float sigma_toe, const float sigma_shoulder, const float saturation)
Definition filmicrgb.c:878
void cleanup_global(dt_iop_module_so_t *module)
Definition filmicrgb.c:3297
static gboolean area_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition filmicrgb.c:4326
void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:3220
static gboolean area_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition filmicrgb.c:4351
static void 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)
Definition filmicrgb.c:2017
static void 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)
Definition filmicrgb.c:955
int flags()
Definition filmicrgb.c:365
static void filmic_desaturate_v4(const dt_aligned_pixel_t Ych_original, dt_aligned_pixel_t Ych_final, const float saturation)
Definition filmicrgb.c:1536
static void show_mask_callback(GtkToggleButton *button, GdkEventButton *event, gpointer user_data)
Definition filmicrgb.c:2800
#define ORDER_3
Definition filmicrgb.c:2824
static void apply_auto_white_point_source(dt_iop_module_t *self)
Definition filmicrgb.c:2719
static float clip_chroma_white(const float coeffs[3], const float target_white, const float Y, const float cos_h, const float sin_h)
Definition filmicrgb.c:1610
static void convert_to_spline_v3(dt_iop_filmicrgb_params_t *n)
Definition filmicrgb.c:379
static gint 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)
Definition filmicrgb.c:1177
#define MAX_NUM_SCALES
Definition filmicrgb.c:900
dt_iop_filmicrgb_spline_version_type_t
Definition filmicrgb.c:140
@ DT_FILMIC_SPLINE_VERSION_V2
Definition filmicrgb.c:142
@ DT_FILMIC_SPLINE_VERSION_V3
Definition filmicrgb.c:143
@ DT_FILMIC_SPLINE_VERSION_V1
Definition filmicrgb.c:141
void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:3214
#define INVERSE_SQRT_3
Definition filmicrgb.c:58
static void filmic_v5(const float *const restrict in, float *const restrict out, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const export_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, const float display_black, const float display_white)
Definition filmicrgb.c:1944
static void Ych_to_pipe_RGB(const dt_aligned_pixel_t in, const dt_colormatrix_t matrix, dt_aligned_pixel_t out)
Definition filmicrgb.c:1521
static float filmic_spline(const float x, const dt_aligned_pixel_t M1, const dt_aligned_pixel_t M2, const dt_aligned_pixel_t M3, const dt_aligned_pixel_t M4, const dt_aligned_pixel_t M5, const float latitude_min, const float latitude_max, const dt_iop_filmicrgb_curve_type_t type[2])
Definition filmicrgb.c:794
static void apply_auto_black(dt_iop_module_t *self)
Definition filmicrgb.c:2691
static int filmic_v4_prepare_matrices(dt_colormatrix_t input_matrix, dt_colormatrix_t output_matrix, dt_colormatrix_t export_input_matrix, dt_colormatrix_t export_output_matrix, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const export_profile)
Definition filmicrgb.c:1755
void init_global(dt_iop_module_so_t *module)
Definition filmicrgb.c:3274
static void display_mask(const float *const restrict mask, float *const restrict out, const size_t width, const size_t height)
Definition filmicrgb.c:2003
static void filmic_split_v4(const float *const restrict in, float *const restrict out, const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_order_iccprofile_info_t *const export_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, const float display_black, const float display_white)
Definition filmicrgb.c:1896
static float clip_chroma_white_raw(const float coeffs[3], const float target_white, const float Y, const float cos_h, const float sin_h)
Definition filmicrgb.c:1580
static void apply_auto_grey(dt_iop_module_t *self)
Definition filmicrgb.c:2662
static void 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)
Definition filmicrgb.c:1143
static void 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)
Definition filmicrgb.c:1368
#define SAFETY_MARGIN
Definition filmicrgb.c:59
static float linear_saturation(const float x, const float luminance, const float saturation)
Definition filmicrgb.c:894
static gboolean dt_iop_filmic_rgb_compute_spline(const dt_iop_filmicrgb_params_t *const p, struct dt_iop_filmic_rgb_spline_t *const spline)
Definition filmicrgb.c:2828
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
Definition filmicrgb.c:442
static void 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)
Definition filmicrgb.c:1059
static float clip_chroma_black(const float coeffs[3], const float cos_h, const float sin_h)
Definition filmicrgb.c:1633
static int gauss_solve(double *A, double *b, int n)
Definition gaussian_elimination.h:97
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
Definition gtk.c:157
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition gtk.c:1391
GtkNotebook * dt_ui_notebook_new()
Definition gtk.c:1386
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:72
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:154
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:283
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:38
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:293
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:1883
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:2819
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:2447
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:107
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:108
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:110
@ IOP_GROUP_TONES
Definition imageop.h:78
#define IOP_GUI_ALLOC(module)
Definition imageop.h:465
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
Definition imageop_gui.c:285
GtkWidget * dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:244
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:75
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:180
static void dt_iop_alpha_copy(const void *const ivoid, void *const ovoid, const size_t width, const size_t height)
Definition imageop_math.h:170
dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
Definition iop_profile.c:765
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(struct dt_dev_pixelpipe_t *pipe)
Definition iop_profile.c:887
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_output_profile_info(struct dt_dev_pixelpipe_t *pipe)
Definition iop_profile.c:897
static float dt_ioppr_get_rgb_matrix_luminance(const dt_aligned_pixel_t rgb, const dt_colormatrix_t matrix_in, float *const lut_in[3], const float unbounded_coeffs_in[3][3], const int lutsize, const int nonlinearlut)
Definition iop_profile.h:258
static void flip(float *x, float *y)
Definition lightroom.c:1035
static float sqf(const float x)
Definition math.h:215
#define NORM_MIN
Definition math.h:27
#define CLAMPF(a, mn, mx)
Definition math.h:81
#define M_PI
Definition math.h:37
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:24
static void dt_colormatrix_mul(dt_colormatrix_t dst, const dt_colormatrix_t m1, const dt_colormatrix_t m2)
Definition matrices.h:140
static void dot_product(const dt_aligned_pixel_t v_in, const dt_colormatrix_t M, dt_aligned_pixel_t v_out)
Definition matrices.h:178
size_t size
Definition mipmap_cache.c:3
g
Definition derive_filmic_v6_gamut_mapping.py:18
mask
Definition dtstyle_to_xmp.py:54
Definition tiling.py:1
static int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.h:560
static int dt_opencl_set_kernel_arg(const int dev, const int kernel, const size_t size, const void *arg)
Definition opencl.h:556
static int dt_opencl_create_kernel(const int program, const char *name)
Definition opencl.h:535
static void dt_opencl_free_kernel(const int kernel)
Definition opencl.h:539
static void dt_opencl_release_mem_object(void *mem)
Definition opencl.h:601
static float fmaxabsf(const float a, const float b)
Definition openmp_maths.h:119
static float clamp_simd(const float x)
Definition openmp_maths.h:141
void dtgtk_cairo_paint_refresh(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Definition paint.c:1380
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Definition paint.c:1947
void dtgtk_cairo_paint_text_label(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Definition paint.c:2096
void(* DTGTKCairoPaintIconFunc)(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Definition paint.h:46
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:31
#define eps
Definition rcd.c:81
int32_t num_openmp_threads
Definition darktable.h:524
struct dt_gui_gtk_t * gui
Definition darktable.h:541
struct dt_bauhaus_t * bauhaus
Definition darktable.h:544
struct dt_develop_t * develop
Definition darktable.h:536
GdkRGBA graph_bg
Definition bauhaus.h:253
GdkRGBA graph_border
Definition bauhaus.h:253
PangoFontDescription * pango_font_desc
Definition bauhaus.h:247
float quad_width
Definition bauhaus.h:246
GdkRGBA graph_fg
Definition bauhaus.h:253
Definition color_conversion.h:36
Definition pixelpipe_hb.h:46
int colors
Definition pixelpipe_hb.h:85
double iscale
Definition pixelpipe_hb.h:70
size_t data_size
Definition pixelpipe_hb.h:59
dt_iop_roi_t buf_in
Definition pixelpipe_hb.h:86
struct dt_iop_module_t *struct dt_dev_pixelpipe_t * pipe
Definition pixelpipe_hb.h:48
void * data
Definition pixelpipe_hb.h:49
Definition pixelpipe_hb.h:127
int32_t gui_attached
Definition develop.h:144
dt_image_t image_storage
Definition develop.h:164
GList * iop
Definition develop.h:173
Definition tiling.h:30
double dpi
Definition gtk.h:117
gint scroll_mask
Definition gtk.h:126
int32_t reset
Definition gtk.h:98
Definition filmicrgb.c:154
dt_aligned_pixel_t M2
Definition filmicrgb.c:155
dt_aligned_pixel_t M4
Definition filmicrgb.c:155
dt_iop_filmicrgb_curve_type_t type[2]
Definition filmicrgb.c:159
dt_aligned_pixel_t M5
Definition filmicrgb.c:155
float latitude_min
Definition filmicrgb.c:156
float y[5]
Definition filmicrgb.c:157
dt_aligned_pixel_t M1
Definition filmicrgb.c:155
float x[5]
Definition filmicrgb.c:158
dt_aligned_pixel_t M3
Definition filmicrgb.c:155
float latitude_max
Definition filmicrgb.c:156
Definition filmicrgb.c:296
int high_quality_reconstruction
Definition filmicrgb.c:316
int spline_version
Definition filmicrgb.c:315
float reconstruct_feather
Definition filmicrgb.c:302
float dynamic_range
Definition filmicrgb.c:307
float reconstruct_structure_vs_texture
Definition filmicrgb.c:305
float max_grad
Definition filmicrgb.c:297
float reconstruct_grey_vs_color
Definition filmicrgb.c:304
float output_power
Definition filmicrgb.c:309
float sigma_shoulder
Definition filmicrgb.c:311
float white_source
Definition filmicrgb.c:298
float noise_level
Definition filmicrgb.c:312
float sigma_toe
Definition filmicrgb.c:311
dt_noise_distribution_t noise_distribution
Definition filmicrgb.c:318
int version
Definition filmicrgb.c:314
float grey_source
Definition filmicrgb.c:299
float normalize
Definition filmicrgb.c:306
float reconstruct_bloom_vs_details
Definition filmicrgb.c:303
float saturation
Definition filmicrgb.c:308
float contrast
Definition filmicrgb.c:310
struct dt_iop_filmic_rgb_spline_t spline DT_ALIGNED_ARRAY
Definition filmicrgb.c:317
float black_source
Definition filmicrgb.c:300
float reconstruct_threshold
Definition filmicrgb.c:301
int preserve_color
Definition filmicrgb.c:313
Definition filmicrgb.c:323
int kernel_filmic_rgb_chroma
Definition filmicrgb.c:325
int kernel_filmic_bspline_vertical
Definition filmicrgb.c:329
int kernel_filmic_rgb_split
Definition filmicrgb.c:324
int kernel_filmic_wavelets_reconstruct
Definition filmicrgb.c:333
int kernel_filmic_wavelets_detail
Definition filmicrgb.c:332
int kernel_filmic_show_mask
Definition filmicrgb.c:327
int kernel_filmic_compute_ratios
Definition filmicrgb.c:334
int kernel_filmic_init_reconstruct
Definition filmicrgb.c:331
int kernel_filmic_restore_ratios
Definition filmicrgb.c:335
int kernel_filmic_mask
Definition filmicrgb.c:326
int kernel_filmic_bspline_horizontal
Definition filmicrgb.c:330
int kernel_filmic_inpaint_noise
Definition filmicrgb.c:328
Definition filmicrgb.c:225
float right
Definition filmicrgb.c:228
float bottom
Definition filmicrgb.c:230
float top
Definition filmicrgb.c:229
DTGTKCairoPaintIconFunc icon
Definition filmicrgb.c:239
float w
Definition filmicrgb.c:231
float h
Definition filmicrgb.c:232
GtkStateFlags state
Definition filmicrgb.c:236
float left
Definition filmicrgb.c:227
gint mouse_hover
Definition filmicrgb.c:235
Definition filmicrgb.c:245
GtkWidget * high_quality_reconstruction
Definition filmicrgb.c:268
GtkWidget * autoset_display_gamma
Definition filmicrgb.c:263
GtkWidget * reconstruct_grey_vs_color
Definition filmicrgb.c:249
GtkWidget * auto_hardness
Definition filmicrgb.c:266
dt_iop_filmicrgb_gui_button_data_t buttons[DT_FILMIC_GUI_BUTTON_LAST]
Definition filmicrgb.c:280
GtkWidget * reconstruct_bloom_vs_details
Definition filmicrgb.c:249
GtkWidget * reconstruct_threshold
Definition filmicrgb.c:249
GtkNotebook * notebook
Definition filmicrgb.c:271
GtkWidget * contrast
Definition filmicrgb.c:259
float graph_height
Definition filmicrgb.c:287
GtkWidget * auto_button
Definition filmicrgb.c:253
GtkWidget * saturation
Definition filmicrgb.c:260
GtkWidget * show_highlight_mask
Definition filmicrgb.c:251
float sign_width
Definition filmicrgb.c:284
GtkWidget * white_point_target
Definition filmicrgb.c:255
GtkWidget * grey_point_source
Definition filmicrgb.c:247
GtkWidget * latitude
Definition filmicrgb.c:258
gint gui_show_labels
Definition filmicrgb.c:276
GtkWidget * highlights
Definition filmicrgb.c:264
GtkWidget * balance
Definition filmicrgb.c:261
GtkDrawingArea * area
Definition filmicrgb.c:272
GtkStyleContext * context
Definition filmicrgb.c:292
GtkWidget * output_power
Definition filmicrgb.c:257
GtkWidget * preserve_color
Definition filmicrgb.c:262
PangoRectangle ink
Definition filmicrgb.c:291
dt_iop_filmicrgb_gui_button_t active_button
Definition filmicrgb.c:279
GtkWidget * shadows
Definition filmicrgb.c:264
gint gui_hover
Definition filmicrgb.c:277
GtkAllocation allocation
Definition filmicrgb.c:290
GtkWidget * noise_level
Definition filmicrgb.c:269
GtkWidget * grey_point_target
Definition filmicrgb.c:254
dt_iop_filmic_rgb_gui_mode_t gui_mode
Definition filmicrgb.c:275
gint gui_sizes_inited
Definition filmicrgb.c:278
GtkWidget * black_point_source
Definition filmicrgb.c:248
GtkWidget * reconstruct_structure_vs_texture
Definition filmicrgb.c:250
float graph_width
Definition filmicrgb.c:286
GtkWidget * reconstruct_feather
Definition filmicrgb.c:250
GtkWidget * version
Definition filmicrgb.c:265
GtkWidget * noise_distribution
Definition filmicrgb.c:269
float zero_width
Definition filmicrgb.c:285
gint show_mask
Definition filmicrgb.c:274
float line_height
Definition filmicrgb.c:283
GtkWidget * black_point_target
Definition filmicrgb.c:256
int inset
Definition filmicrgb.c:288
GtkWidget * custom_grey
Definition filmicrgb.c:267
struct dt_iop_filmic_rgb_spline_t spline DT_ALIGNED_ARRAY
Definition filmicrgb.c:273
GtkWidget * white_point_source
Definition filmicrgb.c:246
GtkWidget * security_factor
Definition filmicrgb.c:252
GtkWidget * compensate_icc_black
Definition filmicrgb.c:270
Definition filmicrgb.c:182
float reconstruct_threshold
Definition filmicrgb.c:186
float reconstruct_grey_vs_color
Definition filmicrgb.c:189
float reconstruct_feather
Definition filmicrgb.c:187
dt_iop_filmicrgb_curve_type_t shadows
Definition filmicrgb.c:207
dt_iop_filmicrgb_spline_version_type_t spline_version
Definition filmicrgb.c:210
float white_point_target
Definition filmicrgb.c:194
gboolean auto_hardness
Definition filmicrgb.c:203
float contrast
Definition filmicrgb.c:197
float latitude
Definition filmicrgb.c:196
float security_factor
Definition filmicrgb.c:191
dt_iop_filmicrgb_colorscience_type_t version
Definition filmicrgb.c:202
float balance
Definition filmicrgb.c:199
dt_iop_filmic_noise_distribution_t noise_distribution
Definition filmicrgb.c:206
dt_iop_filmicrgb_methods_type_t preserve_color
Definition filmicrgb.c:201
int high_quality_reconstruction
Definition filmicrgb.c:205
dt_iop_filmicrgb_curve_type_t highlights
Definition filmicrgb.c:208
float grey_point_source
Definition filmicrgb.c:183
float output_power
Definition filmicrgb.c:195
float reconstruct_bloom_vs_details
Definition filmicrgb.c:188
float grey_point_target
Definition filmicrgb.c:192
float saturation
Definition filmicrgb.c:198
gboolean custom_grey
Definition filmicrgb.c:204
float noise_level
Definition filmicrgb.c:200
float black_point_source
Definition filmicrgb.c:184
gboolean compensate_icc_black
Definition filmicrgb.c:209
float black_point_target
Definition filmicrgb.c:193
float white_point_source
Definition filmicrgb.c:185
float reconstruct_structure_vs_texture
Definition filmicrgb.c:190
Definition imageop.h:155
dt_iop_global_data_t * data
Definition imageop.h:168
Definition imageop.h:182
GtkDarktableToggleButton * off
Definition imageop.h:270
dt_iop_params_t * default_params
Definition imageop.h:238
GtkWidget * widget
Definition imageop.h:268
struct dt_develop_t * dev
Definition imageop.h:227
dt_iop_gui_data_t * gui_data
Definition imageop.h:242
gboolean enabled
Definition imageop.h:229
dt_aligned_pixel_t picked_color_min
Definition imageop.h:210
int request_mask_display
Definition imageop.h:204
dt_aligned_pixel_t picked_color_max
Definition imageop.h:210
dt_aligned_pixel_t picked_color
Definition imageop.h:210
dt_iop_params_t * params
Definition imageop.h:238
Definition iop_profile.h:41
int nonlinearlut
Definition iop_profile.h:52
int lutsize
Definition iop_profile.h:47
dt_colormatrix_t matrix_out
Definition iop_profile.h:46
float * lut_in[3]
Definition iop_profile.h:48
dt_colormatrix_t matrix_in
Definition iop_profile.h:45
Definition imageop.h:32
double scale
Definition imageop.h:34
int width
Definition imageop.h:33
int height
Definition imageop.h:33
#define E
Definition test_filmicrgb.c:48
#define MIN(a, b)
Definition thinplate.c:23
#define MAX(a, b)
Definition thinplate.c:20
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition togglebutton.c:166
#define DTGTK_TOGGLEBUTTON(obj)
Definition togglebutton.h:24