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 the Ansel project.
3 Copyright (C) 2019-2020, 2022 Aldric Renaudin.
4 Copyright (C) 2019-2026 Aurélien PIERRE.
5 Copyright (C) 2019 Diederik ter Rahe.
6 Copyright (C) 2019-2022 Pascal Obry.
7 Copyright (C) 2019 Tobias Ellinghaus.
8 Copyright (C) 2020-2021 Chris Elston.
9 Copyright (C) 2020-2022 Diederik Ter Rahe.
10 Copyright (C) 2020 Heiko Bauke.
11 Copyright (C) 2020-2021 Hubert Kowalski.
12 Copyright (C) 2020 Jeronimo Pellegrini.
13 Copyright (C) 2020 Marco Carrarini.
14 Copyright (C) 2020 Mark-64.
15 Copyright (C) 2020 Martin Burri.
16 Copyright (C) 2020-2021 Ralf Brown.
17 Copyright (C) 2020-2021 rawfiner.
18 Copyright (C) 2021 Dan Torop.
19 Copyright (C) 2021 Fabio Heer.
20 Copyright (C) 2021 lhietal.
21 Copyright (C) 2021 luzpaz.
22 Copyright (C) 2021 paolodepetrillo.
23 Copyright (C) 2021-2022 Sakari Kapanen.
24 Copyright (C) 2021 Victor Forsiuk.
25 Copyright (C) 2022 Hanno Schwalm.
26 Copyright (C) 2022 Martin Bařinka.
27 Copyright (C) 2022 Nicolas Auffray.
28 Copyright (C) 2022 Philipp Lutz.
29 Copyright (C) 2023 Alban Gruin.
30 Copyright (C) 2023-2024 Alynx Zhou.
31 Copyright (C) 2023 Luca Zulberti.
32 Copyright (C) 2025 Guillaume Stutin.
33
34 Ansel is free software: you can redistribute it and/or modify
35 it under the terms of the GNU General Public License as published by
36 the Free Software Foundation, either version 3 of the License, or
37 (at your option) any later version.
38
39 Ansel is distributed in the hope that it will be useful,
40 but WITHOUT ANY WARRANTY; without even the implied warranty of
41 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42 GNU General Public License for more details.
43
44 You should have received a copy of the GNU General Public License
45 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
46*/
47#ifdef HAVE_CONFIG_H
48#include "config.h"
49#endif
50#include "bauhaus/bauhaus.h"
53#include "common/darktable.h"
54#include "common/bspline.h"
55#include "common/dwt.h"
56#include "common/image.h"
57#include "common/iop_profile.h"
58#include "common/opencl.h"
59#include "control/control.h"
60#include "develop/develop.h"
61#include "develop/imageop_gui.h"
65#include "develop/tiling.h"
66#include "dtgtk/button.h"
67#include "dtgtk/drawingarea.h"
68#include "dtgtk/expander.h"
69#include "dtgtk/paint.h"
70
72#include "gui/gtk.h"
73#include "gui/presets.h"
75#include "iop/iop_api.h"
76
77
78#include "develop/imageop.h"
79#include "gui/draw.h"
80
81#include <assert.h>
82#include <math.h>
83#include <stdlib.h>
84#include <string.h>
85#include <time.h>
86
87#define INVERSE_SQRT_3 0.5773502691896258f
88#define SAFETY_MARGIN 0.01f
89
90#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(1)
91
92
94
95
125{
126 DT_FILMIC_METHOD_NONE = 0, // $DESCRIPTION: "no"
127 DT_FILMIC_METHOD_MAX_RGB = 1, // $DESCRIPTION: "max RGB"
128 DT_FILMIC_METHOD_LUMINANCE = 2, // $DESCRIPTION: "luminance Y"
129 DT_FILMIC_METHOD_POWER_NORM = 3, // $DESCRIPTION: "RGB power norm"
130 DT_FILMIC_METHOD_EUCLIDEAN_NORM_V2 = 5, // $DESCRIPTION: "RGB euclidean norm"
131 DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1 = 4, // $DESCRIPTION: "RGB euclidean norm (legacy)"
133
134
136{
137 DT_FILMIC_CURVE_POLY_4 = 0, // $DESCRIPTION: "hard"
138 DT_FILMIC_CURVE_POLY_3 = 1, // $DESCRIPTION: "soft"
139 DT_FILMIC_CURVE_RATIONAL = 2, // $DESCRIPTION: "safe"
140 // Generalized-sigmoid toe/shoulder : monotone for any setting, C1 at the
141 // transitions, exact endpoints, slope-matched power fallback on a degenerate
142 // S. Its single power per side is the CIECAM16-J appearance match (see
143 // dt_iop_filmic_rgb_compute_spline). Selectable per side like the others.
144 DT_FILMIC_CURVE_SIGMOID = 3, // $DESCRIPTION: "perceptual"
146
147
149{
150 DT_FILMIC_COLORSCIENCE_V1 = 0, // $DESCRIPTION: "v3 (2019)"
151 DT_FILMIC_COLORSCIENCE_V2 = 1, // $DESCRIPTION: "v4 (2020)"
152 DT_FILMIC_COLORSCIENCE_V3 = 2, // $DESCRIPTION: "v5 (2021)"
153 DT_FILMIC_COLORSCIENCE_V4 = 3, // $DESCRIPTION: "v6 (2022)"
154 DT_FILMIC_COLORSCIENCE_V5 = 4, // $DESCRIPTION: "v7 (2023)"
155 DT_FILMIC_COLORSCIENCE_V6 = 5, // $DESCRIPTION: "v8 (AgX, no bleach)"
156 DT_FILMIC_COLORSCIENCE_V7 = 6, // $DESCRIPTION: "v8 (AgX, low bleach)"
157 DT_FILMIC_COLORSCIENCE_V8 = 7, // $DESCRIPTION: "v8 (AgX, medium bleach)"
158 DT_FILMIC_COLORSCIENCE_V9 = 8, // $DESCRIPTION: "v8 (AgX, high bleach)"
159 DT_FILMIC_COLORSCIENCE_V10 = 9, // $DESCRIPTION: "v8 (AgX, extra bleach)"
161
162// The three v8 "AgX" variants share the whole pixel path and differ ONLY by the
163// inset/outset bracket constants (see filmic_agx_prepare_bracket) : how much
164// bright-color desaturation ("bleach") they trade for in-bracket hue accuracy.
165// no bleach : max saturation, hue leans on the Ych recovery ; high bleach : best
166// in-bracket hue, strongest wash-out. All dispatch identically here.
173
175{
176 DT_FILMIC_SPLINE_VERSION_V1 = 0, // $DESCRIPTION: "v1 (2019)"
177 DT_FILMIC_SPLINE_VERSION_V2 = 1, // $DESCRIPTION: "v2 (2020)"
178 DT_FILMIC_SPLINE_VERSION_V3 = 2, // $DESCRIPTION: "v3 (2021)"
179 // NB : the enclosed enum only sets the node GEOMETRY (how latitude/balance/
180 // contrast place the toe/shoulder nodes). The segment SHAPE between the nodes
181 // is dt_iop_filmicrgb_curve_type_t (shadows/highlights), sigmoid included.
182 // A short-lived v4 (2026) conflated the two ; a history that stored it (enum
183 // value 3, ~24h in production) falls back to v3 geometry silently — no
184 // migration, see dt_iop_filmic_rgb_compute_spline.
186
192
193
195{
196 dt_aligned_pixel_t M1, M2, M3, M4, M5; // factors for the interpolation polynom
197 float latitude_min, latitude_max; // bounds of the latitude == linear part by design
198 float y[5]; // controls nodes
199 float x[5]; // controls nodes
202
203
205{
206 DT_FILMIC_GUI_LOOK = 0, // default GUI, showing only the contrast curve in a log/gamma space
207 DT_FILMIC_GUI_BASECURVE = 1, // basecurve-like GUI, showing the contrast and brightness curves, in lin/lin space
208 DT_FILMIC_GUI_BASECURVE_LOG = 2, // same as previous, but log-scaled
209 DT_FILMIC_GUI_RANGES = 3, // zone-system-like GUI, showing the range to range mapping
212
213// copy enum definition for introspection
220
221// clang-format off
223{
224 float grey_point_source; // $MIN: 0 $MAX: 100 $DEFAULT: 18.45 $DESCRIPTION: "middle gray luminance"
225 float black_point_source; // $MIN: -16 $MAX: -0.1 $DEFAULT: -8.0 $DESCRIPTION: "black relative exposure"
226 float white_point_source; // $MIN: 0.1 $MAX: 16 $DEFAULT: 4.0 $DESCRIPTION: "white relative exposure"
227 float reconstruct_threshold; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "threshold"
228 float reconstruct_feather; // $MIN: 0.25 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "transition"
229 float reconstruct_bloom_vs_details; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "bloom \342\206\224 reconstruct"
230 float reconstruct_grey_vs_color; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "gray \342\206\224 colorful details"
231 float reconstruct_structure_vs_texture; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "structure \342\206\224 texture"
232 float security_factor; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "dynamic range scaling"
233 float grey_point_target; // $MIN: 1 $MAX: 50 $DEFAULT: 18.45 $DESCRIPTION: "target middle gray"
234 float black_point_target; // $MIN: 0.000 $MAX: 20.000 $DEFAULT: 0.01517634 $DESCRIPTION: "target black luminance"
235 float white_point_target; // $MIN: 0 $MAX: 1600 $DEFAULT: 100 $DESCRIPTION: "target white luminance"
236 float output_power; // $MIN: 1 $MAX: 10 $DEFAULT: 4.0 $DESCRIPTION: "hardness"
237 float latitude; // $MIN: 0.01 $MAX: 99 $DEFAULT: 10.0
238 float contrast; // $MIN: 0 $MAX: 5 $DEFAULT: 1.18
239 float saturation; // $MIN: -200 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "extreme luminance saturation"
240 float balance; // $MIN: -50 $MAX: 50 $DEFAULT: 0.0 $DESCRIPTION: "shadows \342\206\224 highlights balance"
241 float noise_level; // $MIN: 0.0 $MAX: 6.0 $DEFAULT: 0.05f $DESCRIPTION: "add noise in highlights"
242 dt_iop_filmicrgb_methods_type_t preserve_color; // $DEFAULT: DT_FILMIC_METHOD_MAX_RGB $DESCRIPTION: "preserve chrominance"
243 dt_iop_filmicrgb_colorscience_type_t version; // $DEFAULT: DT_FILMIC_COLORSCIENCE_V8 $DESCRIPTION: "color science"
244 gboolean auto_hardness; // $DEFAULT: TRUE $DESCRIPTION: "auto adjust hardness"
245 gboolean custom_grey; // $DEFAULT: FALSE $DESCRIPTION: "use custom middle-gray values"
246 int high_quality_reconstruction; // $MIN: 0 $MAX: 10 $DEFAULT: 1 $DESCRIPTION: "iterations of color inpainting"
247 dt_iop_filmic_noise_distribution_t noise_distribution; // $DEFAULT: DT_NOISE_POISSONIAN $DESCRIPTION: "type of noise"
248 dt_iop_filmicrgb_curve_type_t shadows; // $DEFAULT: DT_FILMIC_CURVE_SIGMOID $DESCRIPTION: "contrast in shadows"
249 dt_iop_filmicrgb_curve_type_t highlights; // $DEFAULT: DT_FILMIC_CURVE_SIGMOID $DESCRIPTION: "contrast in highlights"
250 gboolean compensate_icc_black; // $DEFAULT: FALSE $DESCRIPTION: "compensate output ICC profile black point"
251 dt_iop_filmicrgb_spline_version_type_t spline_version; // $DEFAULT: DT_FILMIC_SPLINE_VERSION_V3 $DESCRIPTION: "spline handling"
253// clang-format on
254
255
256// custom buttons in graph views
263
264// custom buttons in graph views - data
266{
267 // coordinates in GUI - compute them only in the drawing function
268 float left;
269 float right;
270 float top;
271 float bottom;
272 float w;
273 float h;
274
275 // properties
276 gint mouse_hover; // whether it should be acted on / mouse is over it
277 GtkStateFlags state;
278
279 // icon drawing, function as set in dtgtk/paint.h
281
283
284
286{
313 GtkNotebook *notebook;
314 GtkDrawingArea *area;
321 dt_iop_filmicrgb_gui_button_t active_button; // ID of the button under cursor
323
324 // Cache Pango and Cairo stuff for the equalizer drawing
330 int inset;
331
332 GtkAllocation allocation;
333 PangoRectangle ink;
334 GtkStyleContext *context;
336
338{
339 float max_grad;
352 float contrast;
359 float agx_beta_hue; // AgX: hue recovery mix [0, 1] — 0 at -100% (full AgX drift),
360 // 1 at +100% (original hue). Chroma is NOT user-controlled : it
361 // follows the bracket's own outset recovery + clamp only, because
362 // mixing any original chroma back kinks highlight gradients.
365 // Soft-proof state actually used for gamut mapping (see _filmic_get_output_profile()).
366 // Tracked here, not just read at process() time, so runtime_data_hash() picks up
367 // toggling/switching the soft-proof target and re-keys the pipeline cache accordingly.
372
373
391
392
393const char *name()
394{
395 return _("fil_mic");
396}
397
398const char *aliases()
399{
400 return _("tone mapping|curve|view transform|contrast|saturation|highlights");
401}
402
403const char **description(struct dt_iop_module_t *self)
404{
405 return dt_iop_set_description(self, _("apply a view transform to prepare the scene-referred pipeline\n"
406 "for display on SDR screens and paper prints\n"
407 "while preventing clipping in non-destructive ways"),
408 _("corrective and creative"),
409 _("linear or non-linear, RGB, scene-referred"),
410 _("non-linear, RGB"),
411 _("non-linear, RGB, display-referred"));
412}
413
415{
416 return IOP_GROUP_TONES;
417}
418
423
425{
426 return IOP_CS_RGB;
427}
428
431{
432 default_input_format(self, pipe, piece, dsc);
433 dsc->channels = 4;
434 dsc->datatype = TYPE_FLOAT;
435}
436
437inline static gboolean dt_iop_filmic_rgb_compute_spline(const dt_iop_filmicrgb_params_t *const p,
438 struct dt_iop_filmic_rgb_spline_t *const spline);
439
452
460
468static inline gboolean filmic_v3_compute_geometry(const dt_iop_filmicrgb_params_t *const p,
469 dt_iop_filmicrgb_v3_geometry_t *const geometry)
470{
471 if(p->spline_version < DT_FILMIC_SPLINE_VERSION_V3) return FALSE;
472
473 if(p->custom_grey)
474 geometry->grey_display = powf(CLAMP(p->grey_point_target, p->black_point_target, p->white_point_target) / 100.0f,
475 1.0f / p->output_power);
476 else
477 geometry->grey_display = powf(0.1845f, 1.0f / p->output_power);
478
479 const float dynamic_range = p->white_point_source - p->black_point_source;
480 geometry->grey_log = fabsf(p->black_point_source) / dynamic_range;
481 geometry->black_display = powf(CLAMP(p->black_point_target, 0.0f, p->grey_point_target) / 100.0f,
482 1.0f / p->output_power);
483 geometry->white_display = powf(fmaxf(p->white_point_target, p->grey_point_target) / 100.0f,
484 1.0f / p->output_power);
485
486 const float slope = p->contrast * dynamic_range / 8.0f;
487 float min_contrast = 1.0f;
488 min_contrast = fmaxf(min_contrast,
489 (geometry->white_display - geometry->grey_display) / (1.0f - geometry->grey_log));
490 min_contrast = fmaxf(min_contrast,
491 (geometry->grey_display - geometry->black_display) / geometry->grey_log);
492 min_contrast += SAFETY_MARGIN;
493
494 geometry->contrast = slope / (p->output_power * powf(geometry->grey_display, p->output_power - 1.0f));
495 const float clamped_contrast = CLAMP(geometry->contrast, min_contrast, 100.0f);
496 geometry->contrast_clamped = (clamped_contrast != geometry->contrast);
497 geometry->contrast = clamped_contrast;
498
499 geometry->linear_intercept = geometry->grey_display - geometry->contrast * geometry->grey_log;
500 const float safety_margin = SAFETY_MARGIN * (geometry->white_display - geometry->black_display);
501 geometry->xmin = (geometry->black_display + safety_margin - geometry->linear_intercept) / geometry->contrast;
502 geometry->xmax = (geometry->white_display - safety_margin - geometry->linear_intercept) / geometry->contrast;
503 return TRUE;
504}
505
514 dt_iop_filmicrgb_v3_geometry_t *const geometry,
515 dt_iop_filmicrgb_v3_nodes_t *const nodes)
516{
517 if(!filmic_v3_compute_geometry(p, geometry)) return FALSE;
518
519 const float latitude = CLAMP(p->latitude, 0.0f, 100.0f) / 100.0f;
520 const float balance = CLAMP(p->balance, -50.0f, 50.0f) / 100.0f;
521
522 // Latitude positions toe and shoulder symmetrically between middle grey and the
523 // points where the current affine slope would meet output black and white.
524 nodes->toe_log = (1.0f - latitude) * geometry->grey_log + latitude * geometry->xmin;
525 nodes->shoulder_log = (1.0f - latitude) * geometry->grey_log + latitude * geometry->xmax;
526
527 // Balance is a signed translation of the latitude segment along the slope.
528 // Positive values protect highlights, negative values protect shadows.
529 const float balance_correction = (balance > 0.0f)
530 ? 2.0f * balance * (nodes->shoulder_log - geometry->grey_log)
531 : 2.0f * balance * (geometry->grey_log - nodes->toe_log);
532 nodes->toe_log -= balance_correction;
533 nodes->shoulder_log -= balance_correction;
534 nodes->toe_log = fmaxf(nodes->toe_log, geometry->xmin);
535 nodes->shoulder_log = fminf(nodes->shoulder_log, geometry->xmax);
536
537 nodes->toe_display = nodes->toe_log * geometry->contrast + geometry->linear_intercept;
538 nodes->shoulder_display = nodes->shoulder_log * geometry->contrast + geometry->linear_intercept;
539 return TRUE;
540}
541
543 float *const toe, float *const shoulder)
544{
547 if(!filmic_v3_compute_nodes_from_legacy(p, &geometry, &nodes))
548 {
549 *toe = CLAMP(p->latitude, 0.0f, 100.0f);
550 *shoulder = CLAMP(p->latitude, 0.0f, 100.0f);
551 return;
552 }
553
554 const float toe_span = fmaxf(geometry.grey_log - geometry.xmin, 1e-6f);
555 const float shoulder_span = fmaxf(geometry.xmax - geometry.grey_log, 1e-6f);
556 *toe = CLAMP((geometry.grey_log - nodes.toe_log) / toe_span, 0.0f, 1.0f) * 100.0f;
557 *shoulder = CLAMP((nodes.shoulder_log - geometry.grey_log) / shoulder_span, 0.0f, 1.0f) * 100.0f;
558}
559
561 const float toe, const float shoulder,
562 float *const latitude, float *const balance)
563{
565 if(!filmic_v3_compute_geometry(p, &geometry))
566 {
567 *latitude = p->latitude;
568 *balance = p->balance;
569 return;
570 }
571
572 const float toe_value = CLAMP(toe, 0.0f, 100.0f) / 100.0f;
573 const float shoulder_value = CLAMP(shoulder, 0.0f, 100.0f) / 100.0f;
574 const float toe_span = fmaxf(geometry.grey_log - geometry.xmin, 1e-6f);
575 const float shoulder_span = fmaxf(geometry.xmax - geometry.grey_log, 1e-6f);
576 const float latitude_value = CLAMP((toe_span * toe_value + shoulder_span * shoulder_value)
577 / (toe_span + shoulder_span),
578 0.0f, 1.0f);
579
580 float balance_value = 0.0f;
581 if(latitude_value > 1e-6f)
582 {
583 if(toe_value > shoulder_value)
584 balance_value = 0.5f * (1.0f - shoulder_value / latitude_value);
585 else if(shoulder_value > toe_value)
586 balance_value = 0.5f * (toe_value / latitude_value - 1.0f);
587 }
588
589 *latitude = latitude_value * 100.0f;
590 *balance = CLAMP(balance_value, -0.5f, 0.5f) * 100.0f;
591}
592
593// convert parameters from spline v1 or v2 to spline v3
595{
596 if(n->spline_version == DT_FILMIC_SPLINE_VERSION_V3)
597 return;
598
601
602 // from the spline, compute new values for contrast, balance, and latitude to update spline_version to v3
603 float grey_log = spline.x[2];
604 float toe_log = fminf(spline.x[1], grey_log);
605 float shoulder_log = fmaxf(spline.x[3], grey_log);
606 float black_display = spline.y[0];
607 float grey_display = spline.y[2];
608 float white_display = spline.y[4];
609 const float scaled_safety_margin = SAFETY_MARGIN * (white_display - black_display);
610 float toe_display = fminf(spline.y[1], grey_display);
611 float shoulder_display = fmaxf(spline.y[3], grey_display);
612
613 float hardness = n->output_power;
614 float contrast = (shoulder_display - toe_display) / (shoulder_log - toe_log);
615 // sanitize toe and shoulder, for min and max values, while keeping the same contrast
616 float linear_intercept = grey_display - (contrast * grey_log);
617 if(toe_display < black_display + scaled_safety_margin)
618 {
619 toe_display = black_display + scaled_safety_margin;
620 // compute toe_log to keep same slope
621 toe_log = (toe_display - linear_intercept) / contrast;
622 }
623 if(shoulder_display > white_display - scaled_safety_margin)
624 {
625 shoulder_display = white_display - scaled_safety_margin;
626 // compute shoulder_log to keep same slope
627 shoulder_log = (shoulder_display - linear_intercept) / contrast;
628 }
629 // revert contrast adaptation that will be performed in dt_iop_filmic_rgb_compute_spline
630 contrast *= 8.0f / (n->white_point_source - n->black_point_source);
631 contrast *= hardness * powf(grey_display, hardness-1.0f);
632 // 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
633 const float latitude = CLAMP((shoulder_display - toe_display) / ((white_display - black_display) - 2.0f * scaled_safety_margin), 0.0f, 0.99f);
634 // find balance
635 float toe_display_ref = latitude * (black_display + scaled_safety_margin) + (1.0f - latitude) * grey_display;
636 float shoulder_display_ref = latitude * (white_display - scaled_safety_margin) + (1.0f - latitude) * grey_display;
637 float balance;
638 if(shoulder_display < shoulder_display_ref)
639 balance = 0.5f * (1.0f - fmaxf(shoulder_display - grey_display, 0.0f) / fmaxf(shoulder_display_ref - grey_display, 1E-5f));
640 else
641 balance = -0.5f * (1.0f - fmaxf(grey_display - toe_display, 0.0f) / fmaxf(grey_display - toe_display_ref, 1E-5f));
642
643 if(n->spline_version == DT_FILMIC_SPLINE_VERSION_V1)
644 {
645 // black and white point need to be updated as well,
646 // as code path for v3 will raise them to power 1.0f / hardness,
647 // while code path for v1 did not.
648 n->black_point_target = powf(black_display, hardness) * 100.0f;
649 n->white_point_target = powf(white_display, hardness) * 100.0f;
650 }
651 n->latitude = latitude * 100.0f;
652 n->contrast = contrast;
653 n->balance = balance * 100.0f;
654 n->spline_version = DT_FILMIC_SPLINE_VERSION_V3;
655}
656
657int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
658 const int new_version)
659{
660 if(old_version == 1 && new_version == 5)
661 {
662 typedef struct dt_iop_filmicrgb_params_v1_t
663 {
664 float grey_point_source;
665 float black_point_source;
666 float white_point_source;
667 float security_factor;
668 float grey_point_target;
669 float black_point_target;
670 float white_point_target;
671 float output_power;
672 float latitude;
673 float contrast;
674 float saturation;
675 float balance;
676 int preserve_color;
677 } dt_iop_filmicrgb_params_v1_t;
678
679 dt_iop_filmicrgb_params_v1_t *o = (dt_iop_filmicrgb_params_v1_t *)old_params;
682
683 *n = *d; // start with a fresh copy of default parameters
684
685 n->grey_point_source = o->grey_point_source;
686 n->white_point_source = o->white_point_source;
687 n->black_point_source = o->black_point_source;
688 n->security_factor = o->security_factor;
689 n->grey_point_target = o->grey_point_target;
690 n->black_point_target = o->black_point_target;
691 n->white_point_target = o->white_point_target;
692 n->output_power = o->output_power;
693 n->latitude = o->latitude;
694 n->contrast = o->contrast;
695 n->saturation = o->saturation;
696 n->balance = o->balance;
697 n->preserve_color = o->preserve_color;
698 n->shadows = DT_FILMIC_CURVE_POLY_4;
699 n->highlights = DT_FILMIC_CURVE_POLY_3;
700 n->reconstruct_threshold
701 = 6.0f; // for old edits, this ensures clipping threshold >> white level, so it's a no-op
702 n->reconstruct_bloom_vs_details = d->reconstruct_bloom_vs_details;
703 n->reconstruct_grey_vs_color = d->reconstruct_grey_vs_color;
704 n->reconstruct_structure_vs_texture = d->reconstruct_structure_vs_texture;
705 n->reconstruct_feather = 3.0f;
706 n->version = DT_FILMIC_COLORSCIENCE_V1;
707 n->auto_hardness = TRUE;
708 n->custom_grey = TRUE;
709 n->high_quality_reconstruction = 0;
710 n->noise_distribution = d->noise_distribution;
711 n->noise_level = 0.f;
712 n->spline_version = DT_FILMIC_SPLINE_VERSION_V1;
713 n->compensate_icc_black = FALSE;
715 return 0;
716 }
717 if(old_version == 2 && new_version == 5)
718 {
719 typedef struct dt_iop_filmicrgb_params_v2_t
720 {
721 float grey_point_source;
722 float black_point_source;
723 float white_point_source;
724 float reconstruct_threshold;
725 float reconstruct_feather;
726 float reconstruct_bloom_vs_details;
727 float reconstruct_grey_vs_color;
728 float reconstruct_structure_vs_texture;
729 float security_factor;
730 float grey_point_target;
731 float black_point_target;
732 float white_point_target;
733 float output_power;
734 float latitude;
735 float contrast;
736 float saturation;
737 float balance;
738 int preserve_color;
739 int version;
740 int auto_hardness;
741 int custom_grey;
742 int high_quality_reconstruction;
745 } dt_iop_filmicrgb_params_v2_t;
746
747 dt_iop_filmicrgb_params_v2_t *o = (dt_iop_filmicrgb_params_v2_t *)old_params;
750
751 *n = *d; // start with a fresh copy of default parameters
752
753 n->grey_point_source = o->grey_point_source;
754 n->white_point_source = o->white_point_source;
755 n->black_point_source = o->black_point_source;
756 n->security_factor = o->security_factor;
757 n->grey_point_target = o->grey_point_target;
758 n->black_point_target = o->black_point_target;
759 n->white_point_target = o->white_point_target;
760 n->output_power = o->output_power;
761 n->latitude = o->latitude;
762 n->contrast = o->contrast;
763 n->saturation = o->saturation;
764 n->balance = o->balance;
765 n->preserve_color = o->preserve_color;
766 n->shadows = o->shadows;
767 n->highlights = o->highlights;
768 n->reconstruct_threshold = o->reconstruct_threshold;
769 n->reconstruct_bloom_vs_details = o->reconstruct_bloom_vs_details;
770 n->reconstruct_grey_vs_color = o->reconstruct_grey_vs_color;
771 n->reconstruct_structure_vs_texture = o->reconstruct_structure_vs_texture;
772 n->reconstruct_feather = o->reconstruct_feather;
773 n->version = o->version;
774 n->auto_hardness = o->auto_hardness;
775 n->custom_grey = o->custom_grey;
776 n->high_quality_reconstruction = o->high_quality_reconstruction;
777 n->noise_level = d->noise_level;
778 n->noise_distribution = d->noise_distribution;
779 n->noise_level = 0.f;
780 n->spline_version = DT_FILMIC_SPLINE_VERSION_V1;
781 n->compensate_icc_black = FALSE;
783 return 0;
784 }
785 if(old_version == 3 && new_version == 5)
786 {
787 typedef struct dt_iop_filmicrgb_params_v3_t
788 {
789 float grey_point_source; // $MIN: 0 $MAX: 100 $DEFAULT: 18.45 $DESCRIPTION: "middle gray luminance"
790 float black_point_source; // $MIN: -16 $MAX: -0.1 $DEFAULT: -8.0 $DESCRIPTION: "black relative exposure"
791 float white_point_source; // $MIN: 0 $MAX: 16 $DEFAULT: 4.0 $DESCRIPTION: "white relative exposure"
792 float reconstruct_threshold; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: +3.0 $DESCRIPTION: "threshold"
793 float reconstruct_feather; // $MIN: 0.25 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "transition"
794 float reconstruct_bloom_vs_details; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION:
795 // "bloom/reconstruct"
796 float reconstruct_grey_vs_color; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "gray/colorful
797 // details"
798 float reconstruct_structure_vs_texture; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION:
799 // "structure/texture"
800 float security_factor; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "dynamic range scaling"
801 float grey_point_target; // $MIN: 1 $MAX: 50 $DEFAULT: 18.45 $DESCRIPTION: "target middle gray"
802 float black_point_target; // $MIN: 0 $MAX: 20 $DEFAULT: 0 $DESCRIPTION: "target black luminance"
803 float white_point_target; // $MIN: 0 $MAX: 1600 $DEFAULT: 100 $DESCRIPTION: "target white luminance"
804 float output_power; // $MIN: 1 $MAX: 10 $DEFAULT: 4.0 $DESCRIPTION: "hardness"
805 float latitude; // $MIN: 0.01 $MAX: 100 $DEFAULT: 33.0
806 float contrast; // $MIN: 0 $MAX: 5 $DEFAULT: 1.50
807 float saturation; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "extreme luminance saturation"
808 float balance; // $MIN: -50 $MAX: 50 $DEFAULT: 0.0 $DESCRIPTION: "shadows/highlights balance"
809 float noise_level; // $MIN: 0.0 $MAX: 6.0 $DEFAULT: 0.1f $DESCRIPTION: "add noise in highlights"
810 dt_iop_filmicrgb_methods_type_t preserve_color; // $DEFAULT: DT_FILMIC_METHOD_POWER_NORM $DESCRIPTION:
811 // "preserve chrominance"
812 dt_iop_filmicrgb_colorscience_type_t version; // $DEFAULT: DT_FILMIC_COLORSCIENCE_V3 $DESCRIPTION: "color
813 // science"
814 gboolean auto_hardness; // $DEFAULT: TRUE $DESCRIPTION: "auto adjust hardness"
815 gboolean custom_grey; // $DEFAULT: FALSE $DESCRIPTION: "use custom middle-gray values"
816 int high_quality_reconstruction; // $MIN: 0 $MAX: 10 $DEFAULT: 1 $DESCRIPTION: "iterations of high-quality
817 // reconstruction"
818 int noise_distribution; // $DEFAULT: DT_NOISE_POISSONIAN $DESCRIPTION: "type of noise"
819 dt_iop_filmicrgb_curve_type_t shadows; // $DEFAULT: DT_FILMIC_CURVE_POLY_4 $DESCRIPTION: "contrast in shadows"
820 dt_iop_filmicrgb_curve_type_t highlights; // $DEFAULT: DT_FILMIC_CURVE_POLY_4 $DESCRIPTION: "contrast in
821 // highlights"
822 } dt_iop_filmicrgb_params_v3_t;
823
824 dt_iop_filmicrgb_params_v3_t *o = (dt_iop_filmicrgb_params_v3_t *)old_params;
827
828 *n = *d; // start with a fresh copy of default parameters
829
830 n->grey_point_source = o->grey_point_source;
831 n->white_point_source = o->white_point_source;
832 n->black_point_source = o->black_point_source;
833 n->security_factor = o->security_factor;
834 n->grey_point_target = o->grey_point_target;
835 n->black_point_target = o->black_point_target;
836 n->white_point_target = o->white_point_target;
837 n->output_power = o->output_power;
838 n->latitude = o->latitude;
839 n->contrast = o->contrast;
840 n->saturation = o->saturation;
841 n->balance = o->balance;
842 n->preserve_color = o->preserve_color;
843 n->shadows = o->shadows;
844 n->highlights = o->highlights;
845 n->reconstruct_threshold = o->reconstruct_threshold;
846 n->reconstruct_bloom_vs_details = o->reconstruct_bloom_vs_details;
847 n->reconstruct_grey_vs_color = o->reconstruct_grey_vs_color;
848 n->reconstruct_structure_vs_texture = o->reconstruct_structure_vs_texture;
849 n->reconstruct_feather = o->reconstruct_feather;
850 n->version = o->version;
851 n->auto_hardness = o->auto_hardness;
852 n->custom_grey = o->custom_grey;
853 n->high_quality_reconstruction = o->high_quality_reconstruction;
854 n->noise_level = d->noise_level;
855 n->noise_distribution = d->noise_distribution;
856 n->noise_level = d->noise_level;
857 n->spline_version = DT_FILMIC_SPLINE_VERSION_V1;
858 n->compensate_icc_black = FALSE;
860 return 0;
861 }
862 if(old_version == 4 && new_version == 5)
863 {
864 typedef struct dt_iop_filmicrgb_params_v4_t
865 {
866 float grey_point_source; // $MIN: 0 $MAX: 100 $DEFAULT: 18.45 $DESCRIPTION: "middle gray luminance"
867 float black_point_source; // $MIN: -16 $MAX: -0.1 $DEFAULT: -8.0 $DESCRIPTION: "black relative exposure"
868 float white_point_source; // $MIN: 0 $MAX: 16 $DEFAULT: 4.0 $DESCRIPTION: "white relative exposure"
869 float reconstruct_threshold; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: +3.0 $DESCRIPTION: "threshold"
870 float reconstruct_feather; // $MIN: 0.25 $MAX: 6.0 $DEFAULT: 3.0 $DESCRIPTION: "transition"
871 float reconstruct_bloom_vs_details; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "bloom \342\206\224 reconstruct"
872 float reconstruct_grey_vs_color; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "gray \342\206\224 colorful details"
873 float reconstruct_structure_vs_texture; // $MIN: -100.0 $MAX: 100.0 $DEFAULT: 0.0 $DESCRIPTION: "structure \342\206\224 texture"
874 float security_factor; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "dynamic range scaling"
875 float grey_point_target; // $MIN: 1 $MAX: 50 $DEFAULT: 18.45 $DESCRIPTION: "target middle gray"
876 float black_point_target; // $MIN: 0.000 $MAX: 20.000 $DEFAULT: 0.01517634 $DESCRIPTION: "target black luminance"
877 float white_point_target; // $MIN: 0 $MAX: 1600 $DEFAULT: 100 $DESCRIPTION: "target white luminance"
878 float output_power; // $MIN: 1 $MAX: 10 $DEFAULT: 4.0 $DESCRIPTION: "hardness"
879 float latitude; // $MIN: 0.01 $MAX: 99 $DEFAULT: 50.0
880 float contrast; // $MIN: 0 $MAX: 5 $DEFAULT: 1.1
881 float saturation; // $MIN: -50 $MAX: 200 $DEFAULT: 0 $DESCRIPTION: "extreme luminance saturation"
882 float balance; // $MIN: -50 $MAX: 50 $DEFAULT: 0.0 $DESCRIPTION: "shadows \342\206\224 highlights balance"
883 float noise_level; // $MIN: 0.0 $MAX: 6.0 $DEFAULT: 0.2f $DESCRIPTION: "add noise in highlights"
884 dt_iop_filmicrgb_methods_type_t preserve_color; // $DEFAULT: DT_FILMIC_METHOD_POWER_NORM $DESCRIPTION: "preserve chrominance"
885 dt_iop_filmicrgb_colorscience_type_t version; // $DEFAULT: DT_FILMIC_COLORSCIENCE_V3 $DESCRIPTION: "color science"
886 gboolean auto_hardness; // $DEFAULT: TRUE $DESCRIPTION: "auto adjust hardness"
887 gboolean custom_grey; // $DEFAULT: FALSE $DESCRIPTION: "use custom middle-gray values"
888 int high_quality_reconstruction; // $MIN: 0 $MAX: 10 $DEFAULT: 1 $DESCRIPTION: "iterations of high-quality reconstruction"
889 dt_iop_filmic_noise_distribution_t noise_distribution; // $DEFAULT: DT_NOISE_GAUSSIAN $DESCRIPTION: "type of noise"
890 dt_iop_filmicrgb_curve_type_t shadows; // $DEFAULT: DT_FILMIC_CURVE_RATIONAL $DESCRIPTION: "contrast in shadows"
891 dt_iop_filmicrgb_curve_type_t highlights; // $DEFAULT: DT_FILMIC_CURVE_RATIONAL $DESCRIPTION: "contrast in highlights"
892 gboolean compensate_icc_black; // $DEFAULT: FALSE $DESCRIPTION: "compensate output ICC profile black point"
893 gint internal_version; // $DEFAULT: 2020 $DESCRIPTION: "version of the spline generator"
894 } dt_iop_filmicrgb_params_v4_t;
895
896 dt_iop_filmicrgb_params_v4_t *o = (dt_iop_filmicrgb_params_v4_t *)old_params;
898 *n = *(dt_iop_filmicrgb_params_t*)o; // structure didn't change except the enum instead of gint for internal_version
899 // we still need to convert the internal_version (in year) to the enum
900 switch(o->internal_version)
901 {
902 case(2019):
903 n->spline_version = DT_FILMIC_SPLINE_VERSION_V1;
904 break;
905 case(2020):
906 n->spline_version = DT_FILMIC_SPLINE_VERSION_V2;
907 break;
908 case(2021):
909 n->spline_version = DT_FILMIC_SPLINE_VERSION_V3;
910 break;
911 default:
912 return 1;
913 }
915 return 0;
916 }
917 return 1;
918}
919
920static inline __attribute__((always_inline)) float pixel_rgb_norm_power_simd(const dt_aligned_pixel_simd_t pixel)
921{
922 // weird norm sort of perceptual. This is black magic really, but it looks good.
923 // the full norm is (R^3 + G^3 + B^3) / (R^2 + G^2 + B^2) and it should be in ]0; +infinity[
924
925 float numerator = 0.0f;
926 float denominator = 0.0f;
927
928 for(int c = 0; c < 3; c++)
929 {
930 const float value = fabsf(pixel[c]);
931 const float RGB_square = value * value;
932 const float RGB_cubic = RGB_square * value;
933 numerator += RGB_cubic;
934 denominator += RGB_square;
935 }
936
937 return numerator / fmaxf(denominator, 1e-12f); // prevent from division-by-0 (note: (1e-6)^2 = 1e-12
938}
939
940__OMP_DECLARE_SIMD__(aligned(pixel:16))
941static inline __attribute__((always_inline)) float pixel_rgb_norm_power(const dt_aligned_pixel_t pixel)
942{
943 return pixel_rgb_norm_power_simd(dt_load_simd_aligned(pixel));
944}
945
946
947static inline __attribute__((always_inline)) float
948get_pixel_norm_simd(const dt_aligned_pixel_simd_t pixel, const dt_iop_filmicrgb_methods_type_t variant,
949 const dt_iop_order_iccprofile_info_t *const work_profile)
950{
951 // a newly added norm should satisfy the condition that it is linear with respect to grey pixels:
952 // norm(R, G, B) = norm(x, x, x) = x
953 // the desaturation code in chroma preservation mode relies on this assumption.
954 // DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1 is an exception to this and is marked as legacy.
955 // DT_FILMIC_METHOD_EUCLIDEAN_NORM_V2 takes the Euclidean norm and scales it such that
956 // norm(1, 1, 1) = 1.
957 switch(variant)
958 {
960 return fmaxf(fmaxf(pixel[0], pixel[1]), pixel[2]);
961
963 if(!IS_NULL_PTR(work_profile))
964 {
965 if(work_profile->nonlinearlut)
966 {
969 return dt_ioppr_get_rgb_matrix_luminance(rgb, work_profile->matrix_in, work_profile->lut_in,
970 work_profile->unbounded_coeffs_in, work_profile->lutsize,
971 work_profile->nonlinearlut);
972 }
973
974 return work_profile->matrix_in[1][0] * pixel[0] + work_profile->matrix_in[1][1] * pixel[1]
975 + work_profile->matrix_in[1][2] * pixel[2];
976 }
977
978 return pixel[0] * 0.2225045f + pixel[1] * 0.7168786f + pixel[2] * 0.0606169f;
979
981 return pixel_rgb_norm_power_simd(pixel);
982
984 return sqrtf(sqf(pixel[0]) + sqf(pixel[1]) + sqf(pixel[2]));
985
987 return sqrtf(sqf(pixel[0]) + sqf(pixel[1]) + sqf(pixel[2])) * INVERSE_SQRT_3;
988
989 default:
990 if(!IS_NULL_PTR(work_profile))
991 {
992 if(work_profile->nonlinearlut)
993 {
996 return dt_ioppr_get_rgb_matrix_luminance(rgb, work_profile->matrix_in, work_profile->lut_in,
997 work_profile->unbounded_coeffs_in, work_profile->lutsize,
998 work_profile->nonlinearlut);
999 }
1000
1001 return work_profile->matrix_in[1][0] * pixel[0] + work_profile->matrix_in[1][1] * pixel[1]
1002 + work_profile->matrix_in[1][2] * pixel[2];
1003 }
1004
1005 return pixel[0] * 0.2225045f + pixel[1] * 0.7168786f + pixel[2] * 0.0606169f;
1006 }
1007}
1008
1009__OMP_DECLARE_SIMD__(aligned(pixel:16) uniform(variant, work_profile))
1010static inline __attribute__((always_inline)) float
1011get_pixel_norm(const dt_aligned_pixel_t pixel, const dt_iop_filmicrgb_methods_type_t variant,
1012 const dt_iop_order_iccprofile_info_t *const work_profile)
1013{
1014 return get_pixel_norm_simd(dt_load_simd_aligned(pixel), variant, work_profile);
1015}
1016
1017__OMP_DECLARE_SIMD__(uniform(grey, black, dynamic_range))
1018static inline float log_tonemapping(const float x, const float grey, const float black,
1019 const float dynamic_range)
1020{
1021 return clamp_simd((log2f(x / grey) - black) / dynamic_range);
1022}
1023
1024__OMP_DECLARE_SIMD__(uniform(grey, black, dynamic_range))
1025static inline float exp_tonemapping_v2(const float x, const float grey, const float black,
1026 const float dynamic_range)
1027{
1028 // inverse of log_tonemapping
1029 return grey * exp2f(dynamic_range * x + black);
1030}
1031
1032
1033__OMP_DECLARE_SIMD__(aligned(M1, M2, M3, M4 : 16) uniform(M1, M2, M3, M4, M5, latitude_min, latitude_max))
1034static inline __attribute__((always_inline)) float
1035filmic_spline(const float x, const dt_aligned_pixel_t M1, const dt_aligned_pixel_t M2,
1036 const dt_aligned_pixel_t M3, const dt_aligned_pixel_t M4,
1037 const dt_aligned_pixel_t M5, const float latitude_min,
1038 const float latitude_max, const dt_iop_filmicrgb_curve_type_t type[2])
1039{
1040 // if type polynomial :
1041 // y = M5 * x⁴ + M4 * x³ + M3 * x² + M2 * x¹ + M1 * x⁰
1042 // but we rewrite it using Horner factorisation, to spare ops and enable FMA in available
1043 // else if type rational :
1044 // y = M1 * (M2 * (x - x_0)² + (x - x_0)) / (M2 * (x - x_0)² + (x - x_0) + M3)
1045
1046 float result;
1047
1048 if(x < latitude_min)
1049 {
1050 // toe
1052 {
1053 // sigmoid packing : M1 = scale (negative), M2 = power,
1054 // M3/M4 = slope-matched power-curve fallback coeff/power, M5 = fallback flag,
1055 // M3[2]/M4[2] = target black/white.
1056 if(M5[0] != 0.f)
1057 {
1058 // the S shape is degenerate (chord to black steeper than the latitude slope) :
1059 // use a convex, slope-matched power curve down to target black
1060 result = M3[2] + fmaxf(0.f, M3[0] * powf(fmaxf(x, 0.f), M4[0]));
1061 }
1062 else
1063 {
1064 // generalized sigmoid u/(1 + u^p)^(1/p), C1 at the transition, exact at (0, target black)
1065 const float ty = latitude_min * M2[2] + M1[2];
1066 const float u = M2[2] * (x - latitude_min) / M1[0]; // M1[0] < 0 so u >= 0
1067 result = M1[0] * (u / powf(1.f + powf(u, M2[0]), 1.f / M2[0])) + ty;
1068 }
1069 }
1070 else if(type[0] == DT_FILMIC_CURVE_POLY_4)
1071 {
1072 // polynomial toe, 4th order
1073 result = M1[0] + x * (M2[0] + x * (M3[0] + x * (M4[0] + x * M5[0])));
1074 }
1075 else if(type[0] == DT_FILMIC_CURVE_POLY_3)
1076 {
1077 // polynomial toe, 3rd order
1078 result = M1[0] + x * (M2[0] + x * (M3[0] + x * M4[0]));
1079 }
1080 else
1081 {
1082 // rational toe
1083 const float xi = latitude_min - x;
1084 const float rat = xi * (xi * M2[0] + 1.f);
1085 result = M4[0] - M1[0] * rat / (rat + M3[0]);
1086 }
1087 }
1088 else if(x > latitude_max)
1089 {
1090 // shoulder
1092 {
1093 if(M5[1] != 0.f)
1094 {
1095 // degenerate S shape : concave, slope-matched power curve up to target white
1096 result = M4[2] - fmaxf(0.f, M3[1] * powf(fmaxf(1.f - x, 0.f), M4[1]));
1097 }
1098 else
1099 {
1100 // generalized sigmoid, C1 at the transition, exact at (1, target white)
1101 const float ty = latitude_max * M2[2] + M1[2];
1102 const float u = M2[2] * (x - latitude_max) / M1[1];
1103 result = M1[1] * (u / powf(1.f + powf(u, M2[1]), 1.f / M2[1])) + ty;
1104 }
1105 }
1106 else if(type[1] == DT_FILMIC_CURVE_POLY_4)
1107 {
1108 // polynomial shoulder, 4th order
1109 result = M1[1] + x * (M2[1] + x * (M3[1] + x * (M4[1] + x * M5[1])));
1110 }
1111 else if(type[1] == DT_FILMIC_CURVE_POLY_3)
1112 {
1113 // polynomial shoulder, 3rd order
1114 result = M1[1] + x * (M2[1] + x * (M3[1] + x * M4[1]));
1115 }
1116 else
1117 {
1118 // rational toe
1119 const float xi = x - latitude_max;
1120 const float rat = xi * (xi * M2[1] + 1.f);
1121 result = M4[1] + M1[1] * rat / (rat + M3[1]);
1122 }
1123 }
1124 else
1125 {
1126 // latitude
1127 result = M1[2] + x * M2[2];
1128 }
1129
1130 return result;
1131}
1132
1133__OMP_DECLARE_SIMD__(uniform(sigma_toe, sigma_shoulder))
1134static inline __attribute__((always_inline)) float
1135filmic_desaturate_v1(const float x, const float sigma_toe, const float sigma_shoulder,
1136 const float saturation)
1137{
1138 const float radius_toe = x;
1139 const float radius_shoulder = 1.0f - x;
1140
1141 const float key_toe = expf(-0.5f * radius_toe * radius_toe / sigma_toe);
1142 const float key_shoulder = expf(-0.5f * radius_shoulder * radius_shoulder / sigma_shoulder);
1143
1144 return 1.0f - clamp_simd((key_toe + key_shoulder) / saturation);
1145}
1146
1147
1148__OMP_DECLARE_SIMD__(uniform(sigma_toe, sigma_shoulder))
1149static inline __attribute__((always_inline)) float
1150filmic_desaturate_v2(const float x, const float sigma_toe, const float sigma_shoulder,
1151 const float saturation)
1152{
1153 const float radius_toe = x;
1154 const float radius_shoulder = 1.0f - x;
1155 const float sat2 = 0.5f / sqrtf(saturation);
1156 const float key_toe = expf(-radius_toe * radius_toe / sigma_toe * sat2);
1157 const float key_shoulder = expf(-radius_shoulder * radius_shoulder / sigma_shoulder * sat2);
1158
1159 return (saturation - (key_toe + key_shoulder) * (saturation));
1160}
1161
1162
1164static inline float linear_saturation(const float x, const float luminance, const float saturation)
1165{
1166 return luminance + saturation * (x - luminance);
1167}
1168
1169
1170#define MAX_NUM_SCALES 10
1172static inline gint mask_clipped_pixels(const float *const restrict in, float *const restrict mask,
1173 const float normalize, const float feathering, const size_t width,
1174 const size_t height, const size_t ch)
1175{
1176 /* 1. Detect if pixels are clipped and count them,
1177 * 2. assign them a weight in [0. ; 1.] depending on how close from clipping they are. The weights are defined
1178 * by a sigmoid centered in `reconstruct_threshold` so the transition is soft and symmetrical
1179 */
1180
1181 int clipped = 0;
1182
1183 __OMP_PARALLEL_FOR_SIMD__(aligned(mask, in:64) reduction(+:clipped))
1184 for(size_t k = 0; k < height * width * ch; k += ch)
1185 {
1186 const float pix_max = fmaxf(sqrtf(sqf(in[k]) + sqf(in[k + 1]) + sqf(in[k + 2])), 0.f);
1187 const float argument = -pix_max * normalize + feathering;
1188 const float weight = clamp_simd(1.0f / (1.0f + exp2f(argument)));
1189 mask[k / ch] = weight;
1190
1191 // at x = 4, the sigmoid produces opacity = 5.882 %.
1192 // any x > 4 will produce negligible changes over the image,
1193 // especially since we have reduced visual sensitivity in highlights.
1194 // so we discard pixels for argument > 4. for they are not worth computing.
1195 clipped += (4.f > argument);
1196 }
1197
1198 // If clipped area is < 9 pixels, recovery is not worth the computational cost, so skip it.
1199 return (clipped > 9);
1200}
1201inline static void inpaint_noise(const float *const in, const float *const mask,
1202 float *const inpainted, const float noise_level, const float threshold,
1203 const dt_noise_distribution_t noise_distribution,
1204 const size_t width, const size_t height)
1205{
1206 // add statistical noise in highlights to fill-in texture
1207 // this creates "particules" in highlights, that will help the implicit partial derivative equation
1208 // solver used in wavelets reconstruction to generate texture
1209 __OMP_PARALLEL_FOR__(collapse(2))
1210 for(size_t i = 0; i < height; i++)
1211 for(size_t j = 0; j < width; j++)
1212 {
1213 // Init random number generator
1214 uint32_t DT_ALIGNED_ARRAY state[4] = { splitmix32(j + 1), splitmix32((j + 1) * (i + 3)), splitmix32(1337), splitmix32(666) };
1219
1220 // get the mask value in [0 ; 1]
1221 const size_t idx = i * width + j;
1222 const size_t index = idx * 4;
1223 const float weight = mask[idx];
1224 const float *const restrict pix_in = __builtin_assume_aligned(in + index, 16);
1225 dt_aligned_pixel_t noise = { 0.f };
1226 dt_aligned_pixel_t sigma = { 0.f };
1227 const int DT_ALIGNED_ARRAY flip[4] = { TRUE, FALSE, TRUE, FALSE };
1228
1229 for_each_channel(c,aligned(pix_in))
1230 sigma[c] = pix_in[c] * noise_level / threshold;
1231
1232 // create statistical noise
1233 dt_noise_generator_simd(noise_distribution, pix_in, sigma, flip, state, noise);
1234
1235 // add noise to input
1236 float *const restrict pix_out = __builtin_assume_aligned(inpainted + index, 16);
1237 for_each_channel(c,aligned(pix_in,pix_out))
1238 pix_out[c] = fmaxf(pix_in[c] * (1.0f - weight) + weight * noise[c], 0.f);
1239 }
1240}
1241
1243inline static void wavelets_reconstruct_RGB(const float *const restrict HF, const float *const restrict LF,
1244 const float *const restrict texture, const float *const restrict mask,
1245 float *const restrict reconstructed, const size_t width,
1246 const size_t height, const size_t ch, const float gamma,
1247 const float gamma_comp, const float beta, const float beta_comp,
1248 const float delta, const size_t s, const size_t scales)
1249{
1251 for(size_t k = 0; k < height * width * ch; k += 4)
1252 {
1253 const float alpha = mask[k / ch];
1254
1255 // cache RGB wavelets scales just to be sure the compiler doesn't reload them
1256 const float *const restrict HF_c = __builtin_assume_aligned(HF + k, 16);
1257 const float *const restrict LF_c = __builtin_assume_aligned(LF + k, 16);
1258 const float *const restrict TT_c = __builtin_assume_aligned(texture + k, 16);
1259
1260 // synthesize the max of all RGB channels texture as a flat texture term for the whole pixel
1261 // this is useful if only 1 or 2 channels are clipped, so we transfer the valid/sharpest texture on the other
1262 // channels
1263 const float grey_texture = fmaxabsf(fmaxabsf(TT_c[0], TT_c[1]), TT_c[2]);
1264
1265 // synthesize the max of all interpolated/inpainted RGB channels as a flat details term for the whole pixel
1266 // this is smoother than grey_texture and will fill holes smoothly in details layers if grey_texture ~= 0.f
1267 const float grey_details = (HF_c[0] + HF_c[1] + HF_c[2]) / 3.f;
1268
1269 // synthesize both terms with weighting
1270 // when beta_comp ~= 1.0, we force the reconstruction to be achromatic, which may help with gamut issues or
1271 // magenta highlights.
1272 const float grey_HF = beta_comp * (gamma_comp * grey_details + gamma * grey_texture);
1273
1274 // synthesize the min of all low-frequency RGB channels as a flat structure term for the whole pixel
1275 // when beta_comp ~= 1.0, we force the reconstruction to be achromatic, which may help with gamut issues or magenta highlights.
1276 const float grey_residual = beta_comp * (LF_c[0] + LF_c[1] + LF_c[2]) / 3.f;
1277 __OMP_SIMD__(aligned(reconstructed:64) aligned(HF_c, LF_c, TT_c:16))
1278 for(size_t c = 0; c < 4; c++)
1279 {
1280 // synthesize interpolated/inpainted RGB channels color details residuals and weigh them
1281 // this brings back some color on top of the grey_residual
1282
1283 // synthesize interpolated/inpainted RGB channels color details and weigh them
1284 // this brings back some color on top of the grey_details
1285 const float details = (gamma_comp * HF_c[c] + gamma * TT_c[c]) * beta + grey_HF;
1286
1287 // reconstruction
1288 const float residual = (s == scales - 1) ? (grey_residual + LF_c[c] * beta) : 0.f;
1289 reconstructed[k + c] += alpha * (delta * details + residual);
1290 }
1291 }
1292}
1293
1295inline static void wavelets_reconstruct_ratios(const float *const restrict HF, const float *const restrict LF,
1296 const float *const restrict texture,
1297 const float *const restrict mask,
1298 float *const restrict reconstructed, const size_t width,
1299 const size_t height, const size_t ch, const float gamma,
1300 const float gamma_comp, const float beta, const float beta_comp,
1301 const float delta, const size_t s, const size_t scales)
1302{
1303/*
1304 * This is the adapted version of the RGB reconstruction
1305 * RGB contain high frequencies that we try to recover, so we favor them in the reconstruction.
1306 * The ratios represent the chromaticity in image and contain low frequencies in the absence of noise or
1307 * aberrations, so, here, we favor them instead.
1308 *
1309 * Consequences :
1310 * 1. use min of interpolated channels details instead of max, to get smoother details
1311 * 4. use the max of low frequency channels instead of min, to favor achromatic solution.
1312 *
1313 * Note : ratios close to 1 mean higher spectral purity (more white). Ratios close to 0 mean lower spectral purity
1314 * (more colorful)
1315 */
1317 for(size_t k = 0; k < height * width * ch; k += 4)
1318 {
1319 const float alpha = mask[k / ch];
1320
1321 // cache RGB wavelets scales just to be sure the compiler doesn't reload them
1322 const float *const restrict HF_c = __builtin_assume_aligned(HF + k, 16);
1323 const float *const restrict LF_c = __builtin_assume_aligned(LF + k, 16);
1324 const float *const restrict TT_c = __builtin_assume_aligned(texture + k, 16);
1325
1326 // synthesize the max of all RGB channels texture as a flat texture term for the whole pixel
1327 // this is useful if only 1 or 2 channels are clipped, so we transfer the valid/sharpest texture on the other
1328 // channels
1329 const float grey_texture = fmaxabsf(fmaxabsf(TT_c[0], TT_c[1]), TT_c[2]);
1330
1331 // synthesize the max of all interpolated/inpainted RGB channels as a flat details term for the whole pixel
1332 // this is smoother than grey_texture and will fill holes smoothly in details layers if grey_texture ~= 0.f
1333 const float grey_details = (HF_c[0] + HF_c[1] + HF_c[2]) / 3.f;
1334
1335 // synthesize both terms with weighting
1336 // when beta_comp ~= 1.0, we force the reconstruction to be achromatic, which may help with gamut issues or
1337 // magenta highlights.
1338 const float grey_HF = (gamma_comp * grey_details + gamma * grey_texture);
1339 __OMP_SIMD__(aligned(reconstructed:64) aligned(HF_c, TT_c, LF_c:16) linear(k:4))
1340 for(size_t c = 0; c < 4; c++)
1341 {
1342 // synthesize interpolated/inpainted RGB channels color details residuals and weigh them
1343 // this brings back some color on top of the grey_residual
1344 const float details = 0.5f * ((gamma_comp * HF_c[c] + gamma * TT_c[c]) + grey_HF);
1345
1346 // reconstruction
1347 const float residual = (s == scales - 1) ? LF_c[c] : 0.f;
1348 reconstructed[k + c] += alpha * (delta * details + residual);
1349 }
1350 }
1351}
1352
1353
1355static inline void init_reconstruct(const float *const restrict in, const float *const restrict mask,
1356 float *const restrict reconstructed, const size_t width,
1357 const size_t height)
1358{
1359// init the reconstructed buffer with non-clipped and partially clipped pixels
1360// Note : it's a simple multiplied alpha blending where mask = alpha weight
1362 for(size_t k = 0; k < height * width; k++)
1363 {
1364 for_each_channel(c,aligned(in,mask,reconstructed))
1365 reconstructed[4*k + c] = fmaxf(in[4*k + c] * (1.f - mask[k]), 0.f);
1366 }
1367}
1368
1369
1371static inline void wavelets_detail_level(const float *const restrict detail, const float *const restrict LF,
1372 float *const restrict HF, float *const restrict texture,
1373 const size_t width, const size_t height, const size_t ch)
1374{
1375 __OMP_PARALLEL_FOR_SIMD__(aligned(HF, LF, detail, texture : 64) collapse(2))
1376 for(size_t k = 0; k < height * width; k++)
1377 for(size_t c = 0; c < 4; ++c) HF[4*k + c] = texture[4*k + c] = detail[4*k + c] - LF[4*k + c];
1378}
1379
1381static int get_scales(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *roi_in,
1382 const dt_dev_pixelpipe_iop_t *const piece)
1383{
1384 /* How many wavelets scales do we need to compute at current zoom level ?
1385 * 0. To get the same preview no matter the zoom scale, the relative image coverage ratio of the filter at
1386 * the coarsest wavelet level should always stay constant.
1387 * 1. The image coverage of each B spline filter of size `BSPLINE_FSIZE` is `2^(level) * (BSPLINE_FSIZE - 1) / 2 + 1` pixels
1388 * 2. The coarsest level filter at full resolution should cover `1/BSPLINE_FSIZE` of the largest image dimension.
1389 * 3. The coarsest level filter at current zoom level should cover `scale/BSPLINE_FSIZE` of the largest image dimension.
1390 *
1391 * So we compute the level that solves 1. subject to 3. Of course, integer rounding doesn't make that 1:1
1392 * accurate.
1393 */
1394 const float scale = 1.0f / dt_dev_get_module_scale(pipe, roi_in);
1395 const size_t size = MAX(piece->buf_in.height * pipe->iscale, piece->buf_in.width * pipe->iscale);
1396 const int scales = floorf(log2f((2.0f * size * scale / ((BSPLINE_FSIZE - 1) * BSPLINE_FSIZE)) - 1.0f));
1397 return CLAMP(scales, 1, MAX_NUM_SCALES);
1398}
1399
1400
1401static inline int reconstruct_highlights(const dt_dev_pixelpipe_t *const pipe,
1402 const float *const restrict in, const float *const restrict mask,
1403 float *const restrict reconstructed,
1404 const dt_iop_filmicrgb_reconstruction_type_t variant, const size_t ch,
1405 const dt_iop_filmicrgb_data_t *const data, const dt_dev_pixelpipe_iop_t *piece,
1406 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
1407{
1408 int err = 0;
1409
1410 // wavelets scales
1411 const int scales = get_scales(pipe, roi_in, piece);
1412
1413 // wavelets scales buffers
1414 float *const restrict LF_even = dt_pixelpipe_cache_alloc_align_float_cache(ch * roi_out->width * roi_out->height, 0); // low-frequencies RGB
1415 float *const restrict LF_odd = dt_pixelpipe_cache_alloc_align_float_cache(ch * roi_out->width * roi_out->height, 0); // low-frequencies RGB
1416 float *const restrict HF_RGB = dt_pixelpipe_cache_alloc_align_float_cache(ch * roi_out->width * roi_out->height, 0); // high-frequencies RGB
1417 float *const restrict HF_grey = dt_pixelpipe_cache_alloc_align_float_cache(ch * roi_out->width * roi_out->height, 0); // high-frequencies RGB backup
1418
1419 // alloc a permanent reusable buffer for intermediate computations - avoid multiple alloc/free
1420 float *const restrict temp = dt_pixelpipe_cache_alloc_align_float_cache(darktable.num_openmp_threads * ch * roi_out->width, 0);
1421
1422 if(IS_NULL_PTR(LF_even) || IS_NULL_PTR(LF_odd) || IS_NULL_PTR(HF_RGB) || IS_NULL_PTR(HF_grey) || IS_NULL_PTR(temp))
1423 {
1424 err = 1;
1425 goto error;
1426 }
1427
1428 // Init reconstructed with valid parts of image
1429 init_reconstruct(in, mask, reconstructed, roi_out->width, roi_out->height);
1430
1431 // structure inpainting vs. texture duplicating weight
1432 const float gamma = (data->reconstruct_structure_vs_texture);
1433 const float gamma_comp = 1.0f - data->reconstruct_structure_vs_texture;
1434
1435 // colorful vs. grey weight
1436 const float beta = data->reconstruct_grey_vs_color;
1437 const float beta_comp = 1.f - data->reconstruct_grey_vs_color;
1438
1439 // bloom vs reconstruct weight
1440 const float delta = data->reconstruct_bloom_vs_details;
1441
1442 // À trous wavelet decompose
1443 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
1444 // the wavelets decomposition here is the same as the equalizer/atrous module,
1445 // but simplified because we don't need the edge-aware term, so we can separate the convolution kernel
1446 // with a vertical and horizontal blur, which is 10 multiply-add instead of 25 by pixel.
1447 for(int s = 0; s < scales; ++s)
1448 {
1449 const float *restrict detail; // buffer containing this scale's input
1450 float *restrict LF; // output buffer for the current scale
1451 float *restrict HF_RGB_temp; // temp buffer for HF_RBG terms before blurring
1452
1453 // swap buffers so we only need 2 LF buffers : the LF at scale (s-1) and the one at current scale (s)
1454 if(s == 0)
1455 {
1456 detail = in;
1457 LF = LF_odd;
1458 HF_RGB_temp = LF_even;
1459 }
1460 else if(s % 2 != 0)
1461 {
1462 detail = LF_odd;
1463 LF = LF_even;
1464 HF_RGB_temp = LF_odd;
1465 }
1466 else
1467 {
1468 detail = LF_even;
1469 LF = LF_odd;
1470 HF_RGB_temp = LF_even;
1471 }
1472
1473 const int mult = 1 << s; // fancy-pants C notation for 2^s with integer type, don't be afraid
1474
1475 // Compute wavelets low-frequency scales
1476 blur_2D_Bspline(detail, LF, temp, roi_out->width, roi_out->height, mult, TRUE); // clip negatives
1477
1478 // Compute wavelets high-frequency scales and save the minimum of texture over the RGB channels
1479 // Note : HF_RGB = detail - LF, HF_grey = max(HF_RGB)
1480 wavelets_detail_level(detail, LF, HF_RGB_temp, HF_grey, roi_out->width, roi_out->height, ch);
1481
1482 // interpolate/blur/inpaint (same thing) the RGB high-frequency to fill holes
1483 blur_2D_Bspline(HF_RGB_temp, HF_RGB, temp, roi_out->width, roi_out->height, 1, FALSE);
1484
1485 // Reconstruct clipped parts
1486 if(variant == DT_FILMIC_RECONSTRUCT_RGB)
1487 wavelets_reconstruct_RGB(HF_RGB, LF, HF_grey, mask, reconstructed, roi_out->width, roi_out->height, ch,
1488 gamma, gamma_comp, beta, beta_comp, delta, s, scales);
1489 else if(variant == DT_FILMIC_RECONSTRUCT_RATIOS)
1490 wavelets_reconstruct_ratios(HF_RGB, LF, HF_grey, mask, reconstructed, roi_out->width, roi_out->height, ch,
1491 gamma, gamma_comp, beta, beta_comp, delta, s, scales);
1492 }
1493
1494error:
1500 return err;
1501}
1502
1503
1505static inline void filmic_split_v1(const float *const restrict in, float *const restrict out,
1506 const dt_iop_order_iccprofile_info_t *const work_profile,
1507 const dt_iop_filmicrgb_data_t *const data,
1508 const dt_iop_filmic_rgb_spline_t spline, const size_t width,
1509 const size_t height)
1510{
1512 for(size_t k = 0; k < height * width * 4; k += 4)
1513 {
1514 const float *const restrict pix_in = in + k;
1515 float *const restrict pix_out = out + k;
1516 dt_aligned_pixel_t temp;
1517
1518 // Log tone-mapping
1519 for(int c = 0; c < 3; c++)
1520 temp[c] = log_tonemapping(fmaxf(pix_in[c], NORM_MIN), data->grey_source, data->black_source,
1521 data->dynamic_range);
1522
1523 // Get the desaturation coeff based on the log value
1524 const float lum = (work_profile)
1525 ? dt_ioppr_get_rgb_matrix_luminance(temp, work_profile->matrix_in, work_profile->lut_in,
1526 work_profile->unbounded_coeffs_in,
1527 work_profile->lutsize, work_profile->nonlinearlut)
1529 const float desaturation = filmic_desaturate_v1(lum, data->sigma_toe, data->sigma_shoulder, data->saturation);
1530
1531 // Desaturate on the non-linear parts of the curve
1532 // Filmic S curve on the max RGB
1533 // Apply the transfer function of the display
1534 for(int c = 0; c < 3; c++)
1535 pix_out[c] = powf(
1536 CLAMPF(filmic_spline(linear_saturation(temp[c], lum, desaturation),
1537 spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1538 spline.latitude_min, spline.latitude_max, spline.type),
1539 spline.y[0], spline.y[4]),
1540 data->output_power);
1541 }
1542}
1543
1544
1546static inline void filmic_split_v2_v3(const float *const restrict in, float *const restrict out,
1547 const dt_iop_order_iccprofile_info_t *const work_profile,
1548 const dt_iop_filmicrgb_data_t *const data,
1549 const dt_iop_filmic_rgb_spline_t spline, const size_t width,
1550 const size_t height)
1551{
1553 for(size_t k = 0; k < height * width * 4; k += 4)
1554 {
1555 const float *const restrict pix_in = in + k;
1556 float *const restrict pix_out = out + k;
1557 dt_aligned_pixel_t temp;
1558
1559 // Log tone-mapping
1560 for(int c = 0; c < 3; c++)
1561 temp[c] = log_tonemapping(fmaxf(pix_in[c], NORM_MIN), data->grey_source, data->black_source,
1562 data->dynamic_range);
1563
1564 // Get the desaturation coeff based on the log value
1565 const float lum = (work_profile)
1566 ? dt_ioppr_get_rgb_matrix_luminance(temp, work_profile->matrix_in, work_profile->lut_in,
1567 work_profile->unbounded_coeffs_in,
1568 work_profile->lutsize, work_profile->nonlinearlut)
1570 const float desaturation = filmic_desaturate_v2(lum, data->sigma_toe, data->sigma_shoulder, data->saturation);
1571
1572 // Desaturate on the non-linear parts of the curve
1573 // Filmic S curve on the max RGB
1574 // Apply the transfer function of the display
1575 for(int c = 0; c < 3; c++)
1576 pix_out[c] = powf(
1577 CLAMPF(filmic_spline(linear_saturation(temp[c], lum, desaturation),
1578 spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1579 spline.latitude_min, spline.latitude_max, spline.type),
1580 spline.y[0], spline.y[4]),
1581 data->output_power);
1582 }
1583}
1584
1585
1587static inline void filmic_chroma_v1(const float *const restrict in, float *const restrict out,
1588 const dt_iop_order_iccprofile_info_t *const work_profile,
1589 const dt_iop_filmicrgb_data_t *const data,
1590 const dt_iop_filmic_rgb_spline_t spline, const int variant,
1591 const size_t width, const size_t height)
1592{
1594 for(size_t k = 0; k < height * width * 4; k += 4)
1595 {
1596 const float *const restrict pix_in = in + k;
1597 float *const restrict pix_out = out + k;
1598
1599 dt_aligned_pixel_t ratios = { 0.0f };
1600 float norm = fmaxf(get_pixel_norm(pix_in, variant, work_profile), NORM_MIN);
1601
1602 // Save the ratios
1603 for_each_channel(c,aligned(pix_in))
1604 ratios[c] = pix_in[c] / norm;
1605
1606 // Sanitize the ratios
1607 const float min_ratios = fminf(fminf(ratios[0], ratios[1]), ratios[2]);
1608 if(min_ratios < 0.0f)
1609 for_each_channel(c) ratios[c] -= min_ratios;
1610
1611 // Log tone-mapping
1612 norm = log_tonemapping(norm, data->grey_source, data->black_source, data->dynamic_range);
1613
1614 // Get the desaturation value based on the log value
1615 const float desaturation = filmic_desaturate_v1(norm, data->sigma_toe, data->sigma_shoulder, data->saturation);
1616
1617 for_each_channel(c) ratios[c] *= norm;
1618
1619 const float lum = (work_profile) ? dt_ioppr_get_rgb_matrix_luminance(
1620 ratios, work_profile->matrix_in, work_profile->lut_in, work_profile->unbounded_coeffs_in,
1621 work_profile->lutsize, work_profile->nonlinearlut)
1622 : dt_camera_rgb_luminance(ratios);
1623
1624 // Desaturate on the non-linear parts of the curve and save ratios
1625 for(int c = 0; c < 3; c++) ratios[c] = linear_saturation(ratios[c], lum, desaturation) / norm;
1626
1627 // Filmic S curve on the max RGB
1628 // Apply the transfer function of the display
1629 norm = powf(CLAMPF(filmic_spline(norm, spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1630 spline.latitude_min, spline.latitude_max, spline.type),
1631 spline.y[0], spline.y[4]),
1632 data->output_power);
1633
1634 // Re-apply ratios
1635 for_each_channel(c,aligned(pix_out)) pix_out[c] = ratios[c] * norm;
1636 }
1637}
1638
1639
1641static inline void filmic_chroma_v2_v3(const float *const restrict in, float *const restrict out,
1642 const dt_iop_order_iccprofile_info_t *const work_profile,
1643 const dt_iop_filmicrgb_data_t *const data,
1644 const dt_iop_filmic_rgb_spline_t spline, const int variant,
1645 const size_t width, const size_t height, const size_t ch,
1646 const dt_iop_filmicrgb_colorscience_type_t colorscience_version)
1647{
1649 for(size_t k = 0; k < height * width * ch; k += ch)
1650 {
1651 const float *const restrict pix_in = in + k;
1652 float *const restrict pix_out = out + k;
1653
1654 float norm = fmaxf(get_pixel_norm(pix_in, variant, work_profile), NORM_MIN);
1655
1656 // Save the ratios
1657 dt_aligned_pixel_t ratios = { 0.0f };
1658
1659 for_each_channel(c,aligned(pix_in))
1660 ratios[c] = pix_in[c] / norm;
1661
1662 // Sanitize the ratios
1663 const float min_ratios = fminf(fminf(ratios[0], ratios[1]), ratios[2]);
1664 const int sanitize = (min_ratios < 0.0f);
1665
1666 if(sanitize)
1668 ratios[c] -= min_ratios;
1669
1670 // Log tone-mapping
1671 norm = log_tonemapping(norm, data->grey_source, data->black_source, data->dynamic_range);
1672
1673 // Get the desaturation value based on the log value
1674 const float desaturation = filmic_desaturate_v2(norm, data->sigma_toe, data->sigma_shoulder, data->saturation);
1675
1676 // Filmic S curve on the max RGB
1677 // Apply the transfer function of the display
1678 norm = powf(CLAMPF(filmic_spline(norm, spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
1679 spline.latitude_min, spline.latitude_max, spline.type),
1680 spline.y[0], spline.y[4]),
1681 data->output_power);
1682
1683 // Re-apply ratios with saturation change
1684 for(int c = 0; c < 3; c++) ratios[c] = fmaxf(ratios[c] + (1.0f - ratios[c]) * (1.0f - desaturation), 0.0f);
1685
1686 // color science v3: normalize again after desaturation - the norm might have changed by the desaturation
1687 // operation.
1688 if(colorscience_version == DT_FILMIC_COLORSCIENCE_V3)
1689 norm /= fmaxf(get_pixel_norm(ratios, variant, work_profile), NORM_MIN);
1690
1691 for_each_channel(c,aligned(pix_out))
1692 pix_out[c] = ratios[c] * norm;
1693
1694 // Gamut mapping
1695 const float max_pix = fmaxf(fmaxf(pix_out[0], pix_out[1]), pix_out[2]);
1696 const int penalize = (max_pix > 1.0f);
1697
1698 // Penalize the ratios by the amount of clipping
1699 if(penalize)
1700 {
1701 for_each_channel(c,aligned(pix_out))
1702 {
1703 ratios[c] = fmaxf(ratios[c] + (1.0f - max_pix), 0.0f);
1704 pix_out[c] = ratios[c] * norm;
1705 }
1706 }
1707 }
1708}
1709
1710
1711static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
1712pipe_RGB_to_Ych_simd(const dt_aligned_pixel_simd_t in, const dt_aligned_pixel_simd_t matrix0,
1713 const dt_aligned_pixel_simd_t matrix1, const dt_aligned_pixel_simd_t matrix2)
1714{
1715 // go from pipeline RGB to CIE 2006 LMS D65
1716 // go from CIE LMS 2006 to Kirk/Filmlight Yrg
1717 // rewrite in polar coordinates
1718 //
1719 // Note that we don't explicitly store the hue angle
1720 // but rather just the cosine and sine of the angle.
1721 // This is because we don't need the hue angle anywhere
1722 // and this way we can avoid calculating expensive
1723 // trigonometric functions.
1724 const dt_aligned_pixel_simd_t Yrg = LMS_to_Yrg_simd(dt_mat3x4_mul_vec4(in, matrix0, matrix1, matrix2));
1725 const float r = Yrg[1] - 0.21902143f;
1726 const float g = Yrg[2] - 0.54371398f;
1727 const float c = dt_fast_hypotf(g, r);
1728 const float cos_h = c != 0.f ? r / c : 1.f;
1729 const float sin_h = c != 0.f ? g / c : 0.f;
1730 return (dt_aligned_pixel_simd_t){ Yrg[0], c, cos_h, sin_h };
1731}
1732
1733
1734static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
1735Ych_to_pipe_RGB_simd(const dt_aligned_pixel_simd_t in, const dt_aligned_pixel_simd_t matrix0,
1736 const dt_aligned_pixel_simd_t matrix1, const dt_aligned_pixel_simd_t matrix2)
1737{
1738 // rewrite in cartesian coordinates
1739 // go from Kirk/Filmlight Yrg to CIE LMS 2006
1740 // go from CIE LMS 2006 to pipeline RGB
1741 const dt_aligned_pixel_simd_t Yrg = {
1742 in[0],
1743 in[1] * in[2] + 0.21902143f,
1744 in[1] * in[3] + 0.54371398f,
1745 0.f
1746 };
1747 return dt_mat3x4_mul_vec4(Yrg_to_LMS_simd(Yrg), matrix0, matrix1, matrix2);
1748}
1749
1750static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
1751filmic_desaturate_v4(const dt_aligned_pixel_simd_t Ych_original, dt_aligned_pixel_simd_t Ych_final,
1752 const float saturation)
1753{
1754 // Note : Ych is normalized trough the LMS conversion,
1755 // meaning c is actually a saturation (saturation ~= chroma / brightness).
1756 // So copy-pasting c and h from a different Y is equivalent to
1757 // tonemapping with a norm, which is equivalent to doing exposure compensation :
1758 // it's saturation-invariant, aka chroma will get increased
1759 // if Y is increased, and the other way around.
1760 const float chroma_original = Ych_original[1] * Ych_original[0]; // c2
1761 float chroma_final = Ych_final[1] * Ych_final[0]; // c1
1762
1763 // fit a linear model `chroma = f(y)`:
1764 // `chroma = c1 + (yc - y1) * (c2 - c1) / (y2 - y1)`
1765 // where `(yc - y1)` is user-defined as `saturation * (y2 - y1)`
1766 // so `chroma = c1 + saturation * (c2 - c1)`
1767 // when saturation = 0, we stay at the saturation-invariant final chroma
1768 // when saturation > 0, we go back towards the initial chroma before tone-mapping
1769 // when saturation < 0, we amplify the initial -> final chroma change
1770 const float delta_chroma = saturation * (chroma_original - chroma_final);
1771
1772 const int filmic_brightens = (Ych_final[0] > Ych_original[0]);
1773 const int filmic_resat = (chroma_original < chroma_final);
1774 const int filmic_desat = (chroma_original > chroma_final);
1775 const int user_resat = (saturation > 0.f);
1776 const int user_desat = (saturation < 0.f);
1777
1778 chroma_final = (filmic_brightens && filmic_resat)
1779 ? (chroma_original + chroma_final) / 2.f // force original lower sat if brightening
1780 : ((user_resat && filmic_desat) || user_desat)
1781 ? chroma_final + delta_chroma // allow resaturation only if filmic desaturated, allow desat anytime
1782 : chroma_final;
1783
1784 Ych_final[1] = fmaxf(chroma_final / Ych_final[0], 0.f);
1785 return Ych_final;
1786}
1787
1788// Pipeline and ICC luminance is CIE Y 1931
1789// Kirk Ych/Yrg uses CIE Y 2006
1790// 1 CIE Y 1931 = 1.05785528 CIE Y 2006, so we need to adjust that.
1791// This also accounts for the CAT16 D50->D65 adaptation that has to be done
1792// to go from RGB to CIE LMS 2006.
1793// Warning: only applies to achromatic pixels.
1794#define CIE_Y_1931_to_CIE_Y_2006(x) (1.05785528f * (x))
1795
1796
1797static inline __attribute__((always_inline)) float
1798clip_chroma_white_raw(const float coeffs[3], const float target_white, const float Y,
1799 const float cos_h, const float sin_h)
1800{
1801 const float denominator_Y_coeff = coeffs[0] * (0.979381443298969f * cos_h + 0.391752577319588f * sin_h)
1802 + coeffs[1] * (0.0206185567010309f * cos_h + 0.608247422680412f * sin_h)
1803 - coeffs[2] * (cos_h + sin_h);
1804 const float denominator_target_term = target_white * (0.68285981628866f * cos_h + 0.482137060515464f * sin_h);
1805
1806 // this channel won't limit the chroma
1807 if(denominator_Y_coeff == 0.f) return FLT_MAX;
1808
1809 // The equation for max chroma has an asymptote at this point (zero of denominator).
1810 // Any Y below that value won't give us sensible results for the upper bound
1811 // and we should consider the lower bound instead.
1812 const float Y_asymptote = denominator_target_term / denominator_Y_coeff;
1813 if(Y <= Y_asymptote) return FLT_MAX;
1814
1815 // Get chroma that brings one component of target RGB to the given target_rgb value.
1816 // coeffs are the transformation coeffs to get one components (R, G or B) from input LMS.
1817 // i.e. it is a row of the LMS -> RGB transformation matrix.
1818 // See tools/derive_filmic_v6_gamut_mapping.py for derivation of these equations.
1819 const float denominator = Y * denominator_Y_coeff - denominator_target_term;
1820 const float numerator = -0.427506877216495f
1821 * (Y * (coeffs[0] + 0.856492345150334f * coeffs[1] + 0.554995960637719f * coeffs[2])
1822 - 0.988237752433297f * target_white);
1823
1824 return numerator / denominator;
1825}
1826
1827
1828static inline __attribute__((always_inline)) float
1829clip_chroma_white(const float coeffs[3], const float target_white, const float Y,
1830 const float cos_h, const float sin_h)
1831{
1832 // Due to slight numerical inaccuracies in color matrices,
1833 // the chroma clipping curves for each RGB channel may be
1834 // slightly at the max luminance. Thus we linearly interpolate
1835 // each clipping line to zero chroma near max luminance.
1836 const float eps = 1e-3f;
1837 const float max_Y = CIE_Y_1931_to_CIE_Y_2006(target_white);
1838 const float delta_Y = MAX(max_Y - Y, 0.f);
1839 float max_chroma;
1840 if(delta_Y < eps)
1841 {
1842 max_chroma = delta_Y / (eps * max_Y) * clip_chroma_white_raw(coeffs, target_white, (1.f - eps) * max_Y, cos_h, sin_h);
1843 }
1844 else
1845 {
1846 max_chroma = clip_chroma_white_raw(coeffs, target_white, Y, cos_h, sin_h);
1847 }
1848 return max_chroma >= 0.f ? max_chroma : FLT_MAX;
1849}
1850
1851
1852static inline __attribute__((always_inline)) float
1853clip_chroma_black(const float coeffs[3], const float cos_h, const float sin_h)
1854{
1855 // N.B. this is the same as clip_chroma_white_raw() but with target value = 0.
1856 // This allows eliminating some computation.
1857
1858 // Get chroma that brings one component of target RGB to zero.
1859 // coeffs are the transformation coeffs to get one components (R, G or B) from input LMS.
1860 // i.e. it is a row of the LMS -> RGB transformation matrix.
1861 // See tools/derive_filmic_v6_gamut_mapping.py for derivation of these equations.
1862 const float denominator = coeffs[0] * (0.979381443298969f * cos_h + 0.391752577319588f * sin_h)
1863 + coeffs[1] * (0.0206185567010309f * cos_h + 0.608247422680412f * sin_h)
1864 - coeffs[2] * (cos_h + sin_h);
1865
1866 // this channel won't limit the chroma
1867 if(denominator == 0.f) return FLT_MAX;
1868
1869 const float numerator = -0.427506877216495f * (coeffs[0] + 0.856492345150334f * coeffs[1] + 0.554995960637719f * coeffs[2]);
1870 const float max_chroma = numerator / denominator;
1871 return max_chroma >= 0.f ? max_chroma : FLT_MAX;
1872}
1873
1874
1875static inline __attribute__((always_inline)) float
1876clip_chroma(const dt_colormatrix_t matrix_out, const float target_white, const float Y,
1877 const float cos_h, const float sin_h, const float chroma)
1878{
1879 // Note: ideally we should figure out in advance which channel is going to clip first
1880 // (either go negative or over maximum allowed value) and calculate chroma clipping
1881 // curves only for those channels. That would avoid some ambiguities
1882 // (what do negative chroma values mean etc.) and reduce computation. However this
1883 // "brute-force" approach seems to work fine for now.
1884
1885 const float chroma_R_white = clip_chroma_white(matrix_out[0], target_white, Y, cos_h, sin_h);
1886 const float chroma_G_white = clip_chroma_white(matrix_out[1], target_white, Y, cos_h, sin_h);
1887 const float chroma_B_white = clip_chroma_white(matrix_out[2], target_white, Y, cos_h, sin_h);
1888 const float max_chroma_white = MIN(MIN(chroma_R_white, chroma_G_white), chroma_B_white);
1889
1890 const float chroma_R_black = clip_chroma_black(matrix_out[0], cos_h, sin_h);
1891 const float chroma_G_black = clip_chroma_black(matrix_out[1], cos_h, sin_h);
1892 const float chroma_B_black = clip_chroma_black(matrix_out[2], cos_h, sin_h);
1893 const float max_chroma_black = MIN(MIN(chroma_R_black, chroma_G_black), chroma_B_black);
1894
1895 return MIN(MIN(chroma, max_chroma_black), max_chroma_white);
1896}
1897
1898
1899static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
1900gamut_check_Yrg_filmic_simd(const dt_aligned_pixel_simd_t Ych)
1901{
1902 // Check if the color fits in Yrg and LMS cone space
1903 // clip chroma at constant hue and luminance otherwise
1904 const dt_aligned_pixel_simd_t Yrg = {
1905 Ych[0],
1906 Ych[1] * Ych[2] + 0.21902143f,
1907 Ych[1] * Ych[3] + 0.54371398f,
1908 0.f
1909 };
1910 float max_c = Ych[1];
1911
1912 if(Yrg[1] < 0.f) max_c = fminf(-0.21902143f / Ych[2], max_c);
1913 if(Yrg[2] < 0.f) max_c = fminf(-0.54371398f / Ych[3], max_c);
1914 if(Yrg[1] + Yrg[2] > 1.f) max_c = fminf((1.f - 0.21902143f - 0.54371398f) / (Ych[2] + Ych[3]), max_c);
1915
1916 return (dt_aligned_pixel_simd_t){ Ych[0], max_c, Ych[2], Ych[3] };
1917}
1918
1919
1920static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
1921gamut_check_RGB_simd(const dt_colormatrix_t matrix_out, const dt_aligned_pixel_simd_t matrix_in0,
1922 const dt_aligned_pixel_simd_t matrix_in1, const dt_aligned_pixel_simd_t matrix_in2,
1923 const dt_aligned_pixel_simd_t matrix_out0, const dt_aligned_pixel_simd_t matrix_out1,
1924 const dt_aligned_pixel_simd_t matrix_out2, const float display_black,
1925 const float display_white, const dt_aligned_pixel_simd_t Ych_in)
1926{
1927 // Heuristic: if there are negatives, calculate the amount (luminance) of white light that
1928 // would need to be mixed in to bring the pixel back in gamut.
1929 dt_aligned_pixel_simd_t RGB_brightened = Ych_to_pipe_RGB_simd(Ych_in, matrix_out0, matrix_out1, matrix_out2);
1930 const float min_pix = MIN(MIN(RGB_brightened[0], RGB_brightened[1]), RGB_brightened[2]);
1931 const float black_offset = MAX(-min_pix, 0.f);
1932 RGB_brightened += dt_simd_set1(black_offset);
1933
1934 const dt_aligned_pixel_simd_t Ych_brightened
1935 = pipe_RGB_to_Ych_simd(RGB_brightened, matrix_in0, matrix_in1, matrix_in2);
1936
1937 // Increase the input luminance a little by the value we calculated above.
1938 // Note, however, that this doesn't actually desaturate the color like mixing
1939 // white would do. We will next find the chroma change needed to bring the pixel
1940 // into gamut.
1941 const float Y = CLAMP((Ych_in[0] + Ych_brightened[0]) / 2.f,
1942 CIE_Y_1931_to_CIE_Y_2006(display_black),
1943 CIE_Y_1931_to_CIE_Y_2006(display_white));
1944 const float new_chroma = clip_chroma(matrix_out, display_white, Y, Ych_in[2], Ych_in[3], Ych_in[1]);
1945
1946 // Go to RGB, using existing luminance and hue and the new chroma
1947 dt_aligned_pixel_simd_t RGB_out
1948 = Ych_to_pipe_RGB_simd((dt_aligned_pixel_simd_t){ Y, new_chroma, Ych_in[2], Ych_in[3] },
1949 matrix_out0, matrix_out1, matrix_out2);
1950
1951 // Clamp in target RGB as a final catch-all
1952 for_each_channel(c) RGB_out[c] = CLAMP(RGB_out[c], 0.f, display_white);
1953 return RGB_out;
1954}
1955
1956
1957static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
1958gamut_mapping_simd(dt_aligned_pixel_simd_t Ych_final, const dt_aligned_pixel_simd_t Ych_original,
1959 const dt_colormatrix_t output_matrix, const dt_aligned_pixel_simd_t input_matrix0,
1960 const dt_aligned_pixel_simd_t input_matrix1, const dt_aligned_pixel_simd_t input_matrix2,
1961 const dt_aligned_pixel_simd_t output_matrix0, const dt_aligned_pixel_simd_t output_matrix1,
1962 const dt_aligned_pixel_simd_t output_matrix2,
1963 const dt_colormatrix_t export_output_matrix, const dt_aligned_pixel_simd_t export_input_matrix0,
1964 const dt_aligned_pixel_simd_t export_input_matrix1, const dt_aligned_pixel_simd_t export_input_matrix2,
1965 const dt_aligned_pixel_simd_t export_output_matrix0, const dt_aligned_pixel_simd_t export_output_matrix1,
1966 const dt_aligned_pixel_simd_t export_output_matrix2, const float display_black,
1967 const float display_white, const float saturation, const int use_output_profile)
1968{
1969 // Force final hue to original
1970 Ych_final[2] = Ych_original[2];
1971 Ych_final[3] = Ych_original[3];
1972 // Clip luminance
1973 Ych_final[0] = CLAMP(Ych_final[0], CIE_Y_1931_to_CIE_Y_2006(display_black),
1974 CIE_Y_1931_to_CIE_Y_2006(display_white));
1975
1976 // Massage chroma
1977 Ych_final = filmic_desaturate_v4(Ych_original, Ych_final, saturation);
1978 Ych_final = gamut_check_Yrg_filmic_simd(Ych_final);
1979
1980 if(!use_output_profile)
1981 {
1982 // Now, it is still possible that one channel > display white because of saturation.
1983 // We have already clipped Y, so we know that any problem now is caused by c
1984 return gamut_check_RGB_simd(output_matrix, input_matrix0, input_matrix1, input_matrix2,
1985 output_matrix0, output_matrix1, output_matrix2,
1986 display_black, display_white, Ych_final);
1987 }
1988
1989 // Now, it is still possible that one channel > display white because of saturation.
1990 // We have already clipped Y, so we know that any problem now is caused by c
1991 dt_aligned_pixel_simd_t pix_out
1992 = gamut_check_RGB_simd(export_output_matrix, export_input_matrix0, export_input_matrix1, export_input_matrix2,
1993 export_output_matrix0, export_output_matrix1, export_output_matrix2,
1994 display_black, display_white, Ych_final);
1995
1996 // Go from export RGB to CIE LMS 2006 D65
1997 const dt_aligned_pixel_simd_t LMS
1998 = dt_mat3x4_mul_vec4(pix_out, export_input_matrix0, export_input_matrix1, export_input_matrix2);
1999 // Go from CIE LMS 2006 D65 to pipeline RGB D50
2000 return dt_mat3x4_mul_vec4(LMS, output_matrix0, output_matrix1, output_matrix2);
2001}
2002
2003
2004static inline __attribute__((always_inline)) int filmic_v4_prepare_matrices(dt_colormatrix_t input_matrix, dt_colormatrix_t output_matrix,
2005 dt_colormatrix_t export_input_matrix, dt_colormatrix_t export_output_matrix,
2006 const dt_iop_order_iccprofile_info_t *const work_profile,
2007 const dt_iop_order_iccprofile_info_t *const export_profile)
2008
2009{
2010 dt_colormatrix_t temp_matrix;
2011
2012 // Prepare the pipeline RGB (D50) -> XYZ D50 -> XYZ D65 -> LMS 2006 matrix
2013 dt_colormatrix_mul(temp_matrix, XYZ_D50_to_D65_CAT16, work_profile->matrix_in);
2014 dt_colormatrix_mul(input_matrix, XYZ_D65_to_LMS_2006_D65, temp_matrix);
2015
2016 // Prepare the LMS 2006 -> XYZ D65 -> XYZ D50 -> pipeline RGB matrix (D50)
2017 dt_colormatrix_mul(temp_matrix, XYZ_D65_to_D50_CAT16, LMS_2006_D65_to_XYZ_D65);
2018 dt_colormatrix_mul(output_matrix, work_profile->matrix_out, temp_matrix);
2019
2020 // If the pipeline output profile is supported (matrix profile), we gamut map against it
2021 const int use_output_profile = (!IS_NULL_PTR(export_profile));
2022 if(use_output_profile)
2023 {
2024 // Prepare the LMS 2006 -> XYZ D65 -> XYZ D50 -> output RGB (D50) matrix
2025 dt_colormatrix_mul(temp_matrix, XYZ_D65_to_D50_CAT16, LMS_2006_D65_to_XYZ_D65);
2026 dt_colormatrix_mul(export_output_matrix, export_profile->matrix_out, temp_matrix);
2027
2028 // Prepare the output RGB (D50) -> XYZ D50 -> XYZ D65 -> LMS 2006 matrix
2029 dt_colormatrix_mul(temp_matrix, XYZ_D50_to_D65_CAT16, export_profile->matrix_in);
2030 dt_colormatrix_mul(export_input_matrix, XYZ_D65_to_LMS_2006_D65, temp_matrix);
2031 }
2032
2033 return use_output_profile;
2034}
2035
2037{
2038 dt_aligned_pixel_simd_t input[3];
2039 dt_aligned_pixel_simd_t output[3];
2040 dt_aligned_pixel_simd_t export_input[3];
2041 dt_aligned_pixel_simd_t export_output[3];
2043
2051static inline void filmic_prepare_simd_matrices(const dt_colormatrix_t input_matrix,
2052 const dt_colormatrix_t output_matrix,
2053 const dt_colormatrix_t export_input_matrix,
2054 const dt_colormatrix_t export_output_matrix,
2055 dt_iop_filmicrgb_simd_matrices_t *const simd_matrices)
2056{
2057 dt_colormatrix_t input_matrix_t;
2058 dt_colormatrix_t output_matrix_t;
2059 dt_colormatrix_t export_input_matrix_t;
2060 dt_colormatrix_t export_output_matrix_t;
2061
2062 transpose_3xSSE(input_matrix, input_matrix_t);
2063 transpose_3xSSE(output_matrix, output_matrix_t);
2064 transpose_3xSSE(export_input_matrix, export_input_matrix_t);
2065 transpose_3xSSE(export_output_matrix, export_output_matrix_t);
2066
2067 // Convert each transposed row into a vec4 once, because every pixel reuses the same rows.
2068 for(size_t row = 0; row < 3; row++)
2069 {
2070 simd_matrices->input[row] = dt_colormatrix_row_to_simd(input_matrix_t, row);
2071 simd_matrices->output[row] = dt_colormatrix_row_to_simd(output_matrix_t, row);
2072 simd_matrices->export_input[row] = dt_colormatrix_row_to_simd(export_input_matrix_t, row);
2073 simd_matrices->export_output[row] = dt_colormatrix_row_to_simd(export_output_matrix_t, row);
2074 }
2075}
2076
2077static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
2078norm_tone_mapping_v4_simd(const dt_aligned_pixel_simd_t pix_in,
2080 const dt_iop_order_iccprofile_info_t *const work_profile,
2081 const dt_iop_filmicrgb_data_t *const data,
2082 const dt_iop_filmic_rgb_spline_t spline,
2083 const float norm_min, const float norm_max)
2084{
2085 // Norm must be clamped before ratios are extracted, otherwise clipped highlights
2086 // would inherit a wrong chroma when the scalar norm is later saturated.
2087 float norm = CLAMPF(get_pixel_norm_simd(pix_in, type, work_profile), norm_min, norm_max);
2088 // Save the ratios
2089 const dt_aligned_pixel_simd_t ratios = pix_in / dt_simd_set1(norm);
2090
2091 // Log tone-mapping
2092 norm = log_tonemapping(norm, data->grey_source, data->black_source, data->dynamic_range);
2093 // Filmic S curve on the max RGB
2094 // Apply the transfer function of the display
2095 norm = powf(CLAMPF(filmic_spline(norm, spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
2096 spline.latitude_min, spline.latitude_max, spline.type),
2097 spline.y[0], spline.y[4]),
2098 data->output_power);
2099
2100 // Restore RGB
2101 return ratios * dt_simd_set1(norm);
2102}
2103
2104static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
2105RGB_tone_mapping_v4_simd(const dt_aligned_pixel_simd_t pix_in, const dt_iop_filmicrgb_data_t *const data,
2106 const dt_iop_filmic_rgb_spline_t spline)
2107{
2108 // Filmic S curve on RGB
2109 // Apply the transfer function of the display
2110 dt_aligned_pixel_simd_t pix_out = pix_in;
2111 for(size_t c = 0; c < 3; c++)
2112 {
2113 const float mapped = log_tonemapping(pix_in[c], data->grey_source, data->black_source, data->dynamic_range);
2114 pix_out[c] = powf(CLAMPF(filmic_spline(mapped, spline.M1, spline.M2, spline.M3, spline.M4, spline.M5,
2115 spline.latitude_min, spline.latitude_max, spline.type),
2116 0.f, spline.y[4]),
2117 data->output_power);
2118 }
2119
2120 return pix_out;
2121}
2122
2124static inline void filmic_chroma_v4(const float *const restrict in, float *const restrict out,
2125 const dt_iop_order_iccprofile_info_t *const work_profile,
2126 const dt_iop_order_iccprofile_info_t *const export_profile,
2127 const dt_iop_filmicrgb_data_t *const data,
2128 const dt_iop_filmic_rgb_spline_t spline, const int variant,
2129 const size_t width, const size_t height, const size_t ch,
2130 const dt_iop_filmicrgb_colorscience_type_t colorscience_version,
2131 const float display_black, const float display_white)
2132{
2133 // See colorbalancergb.c for details
2134 dt_colormatrix_t input_matrix; // pipeline RGB -> LMS 2006
2135 dt_colormatrix_t output_matrix; // LMS 2006 -> pipeline RGB
2136 dt_colormatrix_t export_input_matrix = { { 0.f } }; // output RGB -> LMS 2006
2137 dt_colormatrix_t export_output_matrix = { { 0.f } }; // LMS 2006 -> output RGB
2138
2139 const int use_output_profile = filmic_v4_prepare_matrices(input_matrix, output_matrix, export_input_matrix,
2140 export_output_matrix, work_profile, export_profile);
2142 filmic_prepare_simd_matrices(input_matrix, output_matrix, export_input_matrix, export_output_matrix, &simd_matrices);
2143
2144 const float norm_min = exp_tonemapping_v2(0.f, data->grey_source, data->black_source, data->dynamic_range);
2145 const float norm_max = exp_tonemapping_v2(1.f, data->grey_source, data->black_source, data->dynamic_range);
2147 for(size_t k = 0; k < height * width * ch; k += ch)
2148 {
2149 const dt_aligned_pixel_simd_t pix_in = dt_load_simd_aligned(in + k);
2150 const dt_aligned_pixel_simd_t pix_out
2151 = norm_tone_mapping_v4_simd(pix_in, variant, work_profile, data, spline, norm_min, norm_max);
2152
2153 // Keep the expensive RGB <-> LMS <-> Ych path in vector form for the whole pixel.
2154 const dt_aligned_pixel_simd_t Ych_original = pipe_RGB_to_Ych_simd(pix_in, simd_matrices.input[0],
2155 simd_matrices.input[1], simd_matrices.input[2]);
2156 const dt_aligned_pixel_simd_t Ych_final = pipe_RGB_to_Ych_simd(pix_out, simd_matrices.input[0],
2157 simd_matrices.input[1], simd_matrices.input[2]);
2158
2159 dt_store_simd_nontemporal(out + k,
2160 gamut_mapping_simd(Ych_final, Ych_original, output_matrix,
2161 simd_matrices.input[0], simd_matrices.input[1], simd_matrices.input[2],
2162 simd_matrices.output[0], simd_matrices.output[1], simd_matrices.output[2],
2163 export_output_matrix,
2164 simd_matrices.export_input[0], simd_matrices.export_input[1], simd_matrices.export_input[2],
2165 simd_matrices.export_output[0], simd_matrices.export_output[1], simd_matrices.export_output[2],
2166 display_black, display_white, data->saturation, use_output_profile));
2167 }
2168 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
2169}
2170
2172static inline void filmic_split_v4(const float *const restrict in, float *const restrict out,
2173 const dt_iop_order_iccprofile_info_t *const work_profile,
2174 const dt_iop_order_iccprofile_info_t *const export_profile,
2175 const dt_iop_filmicrgb_data_t *const data,
2176 const dt_iop_filmic_rgb_spline_t spline, const int variant,
2177 const size_t width, const size_t height, const size_t ch,
2178 const dt_iop_filmicrgb_colorscience_type_t colorscience_version,
2179 const float display_black, const float display_white)
2180
2181{
2182 // See colorbalancergb.c for details
2183 dt_colormatrix_t input_matrix; // pipeline RGB -> LMS 2006
2184 dt_colormatrix_t output_matrix; // LMS 2006 -> pipeline RGB
2185 dt_colormatrix_t export_input_matrix = { { 0.f } }; // output RGB -> LMS 2006
2186 dt_colormatrix_t export_output_matrix = { { 0.f } }; // LMS 2006 -> output RGB
2187
2188 const int use_output_profile = filmic_v4_prepare_matrices(input_matrix, output_matrix, export_input_matrix,
2189 export_output_matrix, work_profile, export_profile);
2191 filmic_prepare_simd_matrices(input_matrix, output_matrix, export_input_matrix, export_output_matrix, &simd_matrices);
2193 for(size_t k = 0; k < height * width * ch; k += ch)
2194 {
2195 const dt_aligned_pixel_simd_t pix_in = dt_load_simd_aligned(in + k);
2196 const dt_aligned_pixel_simd_t pix_out = RGB_tone_mapping_v4_simd(pix_in, data, spline);
2197 const dt_aligned_pixel_simd_t Ych_original = pipe_RGB_to_Ych_simd(pix_in, simd_matrices.input[0],
2198 simd_matrices.input[1], simd_matrices.input[2]);
2199 dt_aligned_pixel_simd_t Ych_final = pipe_RGB_to_Ych_simd(pix_out, simd_matrices.input[0],
2200 simd_matrices.input[1], simd_matrices.input[2]);
2201
2202 Ych_final[1] = fminf(Ych_original[1], Ych_final[1]);
2203
2204 dt_store_simd_nontemporal(out + k,
2205 gamut_mapping_simd(Ych_final, Ych_original, output_matrix,
2206 simd_matrices.input[0], simd_matrices.input[1], simd_matrices.input[2],
2207 simd_matrices.output[0], simd_matrices.output[1], simd_matrices.output[2],
2208 export_output_matrix,
2209 simd_matrices.export_input[0], simd_matrices.export_input[1], simd_matrices.export_input[2],
2210 simd_matrices.export_output[0], simd_matrices.export_output[1], simd_matrices.export_output[2],
2211 display_black, display_white, data->saturation, use_output_profile));
2212 }
2213 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
2214}
2215
2216
2218static inline void filmic_v5(const float *const restrict in, float *const restrict out,
2219 const dt_iop_order_iccprofile_info_t *const work_profile,
2220 const dt_iop_order_iccprofile_info_t *const export_profile,
2221 const dt_iop_filmicrgb_data_t *const data,
2222 const dt_iop_filmic_rgb_spline_t spline, const size_t width,
2223 const size_t height, const size_t ch, const float display_black,
2224 const float display_white)
2225
2226{
2227 // See colorbalancergb.c for details
2228 dt_colormatrix_t input_matrix; // pipeline RGB -> LMS 2006
2229 dt_colormatrix_t output_matrix; // LMS 2006 -> pipeline RGB
2230 dt_colormatrix_t export_input_matrix = { { 0.f } }; // output RGB -> LMS 2006
2231 dt_colormatrix_t export_output_matrix = { { 0.f } }; // LMS 2006 -> output RGB
2232
2233 const int use_output_profile = filmic_v4_prepare_matrices(input_matrix, output_matrix, export_input_matrix,
2234 export_output_matrix, work_profile, export_profile);
2236 filmic_prepare_simd_matrices(input_matrix, output_matrix, export_input_matrix, export_output_matrix, &simd_matrices);
2237
2238 const float norm_min = exp_tonemapping_v2(0.f, data->grey_source, data->black_source, data->dynamic_range);
2239 const float norm_max = exp_tonemapping_v2(1.f, data->grey_source, data->black_source, data->dynamic_range);
2241 for(size_t k = 0; k < height * width * ch; k += ch)
2242 {
2243 const dt_aligned_pixel_simd_t pix_in = dt_load_simd_aligned(in + k);
2244 const dt_aligned_pixel_simd_t naive_rgb = RGB_tone_mapping_v4_simd(pix_in, data, spline);
2245 const dt_aligned_pixel_simd_t max_rgb
2246 = norm_tone_mapping_v4_simd(pix_in, DT_FILMIC_METHOD_MAX_RGB, work_profile, data, spline, norm_min, norm_max);
2247 // Mix max RGB with naive RGB
2248 dt_aligned_pixel_simd_t pix_out = dt_simd_set1(0.5f + data->saturation) * max_rgb;
2249 pix_out = dt_simd_set1(0.5f - data->saturation) * naive_rgb + pix_out;
2250
2251 // Save Ych in Kirk/Filmlight Yrg
2252 const dt_aligned_pixel_simd_t Ych_original = pipe_RGB_to_Ych_simd(pix_in, simd_matrices.input[0],
2253 simd_matrices.input[1], simd_matrices.input[2]);
2254 // Get final Ych in Kirk/Filmlight Yrg
2255 dt_aligned_pixel_simd_t Ych_final = pipe_RGB_to_Ych_simd(pix_out, simd_matrices.input[0],
2256 simd_matrices.input[1], simd_matrices.input[2]);
2257
2258 Ych_final[1] = fminf(Ych_original[1], Ych_final[1]);
2259
2260 dt_store_simd_nontemporal(out + k,
2261 gamut_mapping_simd(Ych_final, Ych_original, output_matrix,
2262 simd_matrices.input[0], simd_matrices.input[1], simd_matrices.input[2],
2263 simd_matrices.output[0], simd_matrices.output[1], simd_matrices.output[2],
2264 export_output_matrix,
2265 simd_matrices.export_input[0], simd_matrices.export_input[1], simd_matrices.export_input[2],
2266 simd_matrices.export_output[0], simd_matrices.export_output[1], simd_matrices.export_output[2],
2267 display_black, display_white, 0.f, use_output_profile));
2268 }
2269 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
2270}
2271
2272
2273/* AgX rendering : per-channel tone mapping in an inset rendering space.
2274 *
2275 * The working-space primaries are compressed toward the white point ("inset") and
2276 * rotated in the Kirk/Filmlight Yrg chromaticity plane, the filmic curve is applied
2277 * per channel in that space, then the exact inverse matrix re-expands the result.
2278 * The bracket couples desaturation to tonal compression (path-to-white in the
2279 * shoulder, path-to-black in the toe) while being exactly transparent for pixels
2280 * whose channels stay in the latitude. Hue fidelity is recovered parametrically
2281 * in Ych afterwards. See doc/filmic-agx.md for the design rationale and the
2282 * documented deviations from Blender/darktable AgX. */
2283
2284
2286{
2287 dt_aligned_pixel_t xyz_D65 = { 0.f };
2288 dt_aligned_pixel_t lms = { 0.f };
2289 dot_product(xyz_D50, XYZ_D50_to_D65_CAT16, xyz_D65);
2290 XYZ_to_LMS(xyz_D65, lms);
2291 LMS_to_Yrg(lms, Yrg);
2292}
2293
2295{
2296 dt_aligned_pixel_t lms = { 0.f };
2297 dt_aligned_pixel_t xyz_D65 = { 0.f };
2298 Yrg_to_LMS(Yrg, lms);
2299 LMS_to_XYZ(lms, xyz_D65);
2300 dot_product(xyz_D65, XYZ_D65_to_D50_CAT16, xyz_D50);
2301}
2302
2304{
2305 for(size_t r = 0; r < 4; r++)
2306 for(size_t c = 0; c < 4; c++) M[r][c] = (r == c && r < 3) ? 1.f : 0.f;
2307}
2308
2309/* Build M = work RGB -> displaced space : per-primary chroma compression toward
2310 * the white point ("inset") and hue rotation, both in the Kirk/Filmlight Yrg
2311 * chromaticity plane so one degree of rotation means the same perceptual hue
2312 * shift for every primary. The columns of M are the displaced primaries in work
2313 * RGB, white-point normalized (rows sum to 1 : a conservative channel mixer).
2314 * Returns FALSE on degenerate primaries. */
2315static gboolean _filmic_agx_build_displaced(const dt_iop_order_iccprofile_info_t *const work_profile,
2316 const float inset[3], const float rotation[3],
2318{
2319 // working-space primaries and white point in XYZ D50 : columns of the RGB->XYZ matrix
2320 dt_aligned_pixel_t white_xyz = { 0.f };
2321 dt_aligned_pixel_t white_Yrg = { 0.f };
2322 for(size_t r = 0; r < 3; r++)
2323 for(size_t c = 0; c < 3; c++) white_xyz[r] += work_profile->matrix_in[r][c];
2324 _filmic_agx_xyz_D50_to_Yrg(white_xyz, white_Yrg);
2325
2326 dt_colormatrix_t P_prime = { { 0.f } };
2327 for(size_t i = 0; i < 3; i++)
2328 {
2329 const dt_aligned_pixel_t primary_xyz = { work_profile->matrix_in[0][i], work_profile->matrix_in[1][i],
2330 work_profile->matrix_in[2][i], 0.f };
2331 dt_aligned_pixel_t primary_Yrg = { 0.f };
2332 _filmic_agx_xyz_D50_to_Yrg(primary_xyz, primary_Yrg);
2333
2334 // compress chroma toward the white point and rotate hue, at constant luminance
2335 const float dr = primary_Yrg[1] - white_Yrg[1];
2336 const float dg = primary_Yrg[2] - white_Yrg[2];
2337 const float scale = 1.f - CLAMPF(inset[i], 0.f, 0.9f);
2338 const float cos_a = cosf(rotation[i]);
2339 const float sin_a = sinf(rotation[i]);
2340 const dt_aligned_pixel_t displaced_Yrg = { primary_Yrg[0],
2341 white_Yrg[1] + scale * (cos_a * dr - sin_a * dg),
2342 white_Yrg[2] + scale * (sin_a * dr + cos_a * dg), 0.f };
2343 dt_aligned_pixel_t displaced_xyz = { 0.f };
2344 _filmic_agx_Yrg_to_xyz_D50(displaced_Yrg, displaced_xyz);
2345 for(size_t r = 0; r < 3; r++) P_prime[r][i] = displaced_xyz[r];
2346 }
2347
2348 // Rescale the displaced primaries so they share the working white point :
2349 // solve P_prime · s = white_xyz then scale the columns by s. Gray stays gray.
2350 dt_colormatrix_t P_prime_inv = { { 0.f } };
2351 if(mat3SSEinv(P_prime_inv, P_prime)) return FALSE;
2352 dt_aligned_pixel_t s = { 0.f };
2353 dot_product(white_xyz, P_prime_inv, s);
2354 for(size_t r = 0; r < 3; r++)
2355 for(size_t c = 0; c < 3; c++) P_prime[r][c] *= s[c];
2356
2357 dt_colormatrix_mul(M, work_profile->matrix_out, P_prime);
2358 return TRUE;
2359}
2360
2363 dt_colormatrix_t inset, dt_colormatrix_t outset)
2364{
2365 // All constants from tools/derive_filmic_agx_primaries.py, fitted against the
2366 // appearance-matched default curve (contrast 1.18, latitude 10%, toe 1.5 /
2367 // slope-matched shoulder). The three v8 variants are three points on ONE axis :
2368 // the inset strength, which trades bright-color desaturation ("bleach") for
2369 // in-bracket hue accuracy. The inset is UNIFORM (per-primary insets let the fit
2370 // game the metric via a lopsided green channel) ; the per-primary action lives
2371 // in the outset, which OVER-expands (ratios > 1) so priority colors REACH the
2372 // output-chroma <= input-chroma clamp of the pixel path and are trimmed to
2373 // exactly 1.0 per pixel, tone-adaptively (portable across dynamic ranges).
2374 //
2375 // Fits are hue-accuracy-optimal at a chosen average-desaturation budget over the
2376 // priority set (skin database + diffuse reflectances), skin red-ward drift always
2377 // vetoed (<= -1.5°, a racial-bias concern — see doc/filmic-agx.md). Metrics below
2378 // are on that set. sRGB blue at very high EV is a structural DoF limit of any
2379 // linear bracket, left to the per-pixel Ych hue recovery (exact at full strength).
2380 float inset_anchor[3], rotation_anchor[3], outset_anchor[3], outset_rotation[3];
2381 switch(variant)
2382 {
2383 case DT_FILMIC_COLORSCIENCE_V7: // low bleach
2384 // fitted by tools/derive_filmic_agx_primaries.py --fit-bisect no-bleach medium-bleach
2385 inset_anchor[0] = +0.6410825f; inset_anchor[1] = +0.6898110f; inset_anchor[2] = +0.3194529f;
2386 rotation_anchor[0] = +0.0405734f; rotation_anchor[1] = +0.1631286f; rotation_anchor[2] = +0.0350584f;
2387 outset_anchor[0] = 0.784757f; outset_anchor[1] = 0.789387f; outset_anchor[2] = 0.445403f;
2388 outset_rotation[0] = -0.0057845f; outset_rotation[1] = +0.1593207f; outset_rotation[2] = -0.0592955f;
2389 break;
2390 case DT_FILMIC_COLORSCIENCE_V8: // medium bleach
2391 // fitted by tools/derive_filmic_agx_primaries.py --fit-bisect no-bleach extra-bleach
2392 inset_anchor[0] = +0.6509540f; inset_anchor[1] = +0.7488775f; inset_anchor[2] = +0.3517703f;
2393 rotation_anchor[0] = +0.0278602f; rotation_anchor[1] = +0.1214671f; rotation_anchor[2] = -0.0228829f;
2394 outset_anchor[0] = 0.793082f; outset_anchor[1] = 0.815169f; outset_anchor[2] = 0.460318f;
2395 outset_rotation[0] = -0.0053781f; outset_rotation[1] = +0.1187604f; outset_rotation[2] = -0.0794801f;
2396 break;
2397 case DT_FILMIC_COLORSCIENCE_V9: // high bleach
2398 // fitted by tools/derive_filmic_agx_primaries.py --fit-bisect medium-bleach extra-bleach
2399 inset_anchor[0] = +0.6379749f; inset_anchor[1] = +0.7878689f; inset_anchor[2] = +0.3753822f;
2400 rotation_anchor[0] = +0.0106096f; rotation_anchor[1] = +0.0582598f; rotation_anchor[2] = -0.0696729f;
2401 outset_anchor[0] = 0.790237f; outset_anchor[1] = 0.831376f; outset_anchor[2] = 0.465406f;
2402 outset_rotation[0] = -0.0080070f; outset_rotation[1] = +0.0571100f; outset_rotation[2] = -0.0912220f;
2403 break;
2404 case DT_FILMIC_COLORSCIENCE_V10: // extra bleach
2405 // fitted by tools/derive_filmic_agx_primaries.py --fit-extra-bleach --bleach-nudge 0.5
2406 inset_anchor[0] = +0.5770235f; inset_anchor[1] = +0.8102094f; inset_anchor[2] = +0.4000390f;
2407 rotation_anchor[0] = -0.0081060f; rotation_anchor[1] = -0.0034008f; rotation_anchor[2] = -0.1035236f;
2408 outset_anchor[0] = 0.766420f; outset_anchor[1] = 0.838020f; outset_anchor[2] = 0.465130f;
2409 outset_rotation[0] = -0.0122011f; outset_rotation[1] = -0.0021732f; outset_rotation[2] = -0.0971215f;
2410 break;
2411 case DT_FILMIC_COLORSCIENCE_V6: // no bleach
2412 default:
2413 // fitted by tools/derive_filmic_agx_primaries.py --min-bleach --ab-pull 200
2414 inset_anchor[0] = +0.5991055f; inset_anchor[1] = +0.6000000f; inset_anchor[2] = +0.3300009f;
2415 rotation_anchor[0] = +0.0571015f; rotation_anchor[1] = +0.1999891f; rotation_anchor[2] = +0.0886110f;
2416 outset_anchor[0] = 0.761433f; outset_anchor[1] = 0.752267f; outset_anchor[2] = 0.465293f;
2417 outset_rotation[0] = -0.0034297f; outset_rotation[1] = +0.1952448f; outset_rotation[2] = -0.0480109f;
2418 break;
2419 }
2420
2421 dt_colormatrix_t M_recovery = { { 0.f } };
2422 if(!_filmic_agx_build_displaced(work_profile, inset_anchor, rotation_anchor, inset)
2423 || !_filmic_agx_build_displaced(work_profile, outset_anchor, outset_rotation, M_recovery)
2424 || mat3SSEinv(outset, M_recovery))
2425 {
2426 // degenerate primaries : neutral bracket
2427 _mat3_identity(inset);
2428 _mat3_identity(outset);
2429 }
2430}
2431
2432static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t
2433filmic_agx_compress_negatives(const dt_aligned_pixel_simd_t pix, const dt_aligned_pixel_simd_t luma_coeffs)
2434{
2435 // Out-of-gamut or clipped input can carry negative channels that the per-channel
2436 // log encoding cannot represent. Offset them to zero and rescale to preserve the
2437 // working-profile luminance, compensated with the opponent color's luminance.
2438 // Port of the Blender AgX luminance compensation, generalized to the working
2439 // profile coefficients instead of hardcoded Rec2020.
2440 const float input_y = pix[0] * luma_coeffs[0] + pix[1] * luma_coeffs[1] + pix[2] * luma_coeffs[2];
2441 const float max_rgb = fmaxf(fmaxf(pix[0], pix[1]), pix[2]);
2442 const float min_rgb = fminf(fminf(pix[0], pix[1]), pix[2]);
2443
2444 const dt_aligned_pixel_simd_t opponent = dt_simd_set1(max_rgb) - pix;
2445 const float opponent_y = opponent[0] * luma_coeffs[0] + opponent[1] * luma_coeffs[1] + opponent[2] * luma_coeffs[2];
2446 const float max_opponent = fmaxf(fmaxf(opponent[0], opponent[1]), opponent[2]);
2447 const float y_compensated = max_opponent - opponent_y + input_y;
2448
2449 const float offset = fmaxf(-min_rgb, 0.f);
2450 const dt_aligned_pixel_simd_t shifted = pix + dt_simd_set1(offset);
2451 const float max_shifted = fmaxf(fmaxf(shifted[0], shifted[1]), shifted[2]);
2452 const dt_aligned_pixel_simd_t opponent_shifted = dt_simd_set1(max_shifted) - shifted;
2453 const float max_opponent_shifted
2454 = fmaxf(fmaxf(opponent_shifted[0], opponent_shifted[1]), opponent_shifted[2]);
2455 const float y_opponent_shifted = opponent_shifted[0] * luma_coeffs[0] + opponent_shifted[1] * luma_coeffs[1]
2456 + opponent_shifted[2] * luma_coeffs[2];
2457 float y_new
2458 = shifted[0] * luma_coeffs[0] + shifted[1] * luma_coeffs[1] + shifted[2] * luma_coeffs[2];
2459 y_new += max_opponent_shifted - y_opponent_shifted;
2460
2461 const float ratio = (y_new > y_compensated && y_new > 1e-6f) ? y_compensated / y_new : 1.f;
2462 return shifted * dt_simd_set1(ratio);
2463}
2464
2466static inline void filmic_agx(const float *const restrict in, float *const restrict out,
2467 const dt_iop_order_iccprofile_info_t *const work_profile,
2468 const dt_iop_order_iccprofile_info_t *const export_profile,
2469 const dt_iop_filmicrgb_data_t *const data,
2470 const dt_iop_filmic_rgb_spline_t spline, const size_t width,
2471 const size_t height, const size_t ch, const float display_black,
2472 const float display_white)
2473{
2474 // See colorbalancergb.c for details
2475 dt_colormatrix_t input_matrix; // pipeline RGB -> LMS 2006
2476 dt_colormatrix_t output_matrix; // LMS 2006 -> pipeline RGB
2477 dt_colormatrix_t export_input_matrix = { { 0.f } }; // output RGB -> LMS 2006
2478 dt_colormatrix_t export_output_matrix = { { 0.f } }; // LMS 2006 -> output RGB
2479
2480 const int use_output_profile = filmic_v4_prepare_matrices(input_matrix, output_matrix, export_input_matrix,
2481 export_output_matrix, work_profile, export_profile);
2483 filmic_prepare_simd_matrices(input_matrix, output_matrix, export_input_matrix, export_output_matrix, &simd_matrices);
2484
2485 // rendering-space bracket : work RGB -> inset rendering space -> work RGB
2486 dt_colormatrix_t inset = { { 0.f } };
2487 dt_colormatrix_t outset = { { 0.f } };
2488 filmic_agx_prepare_bracket(work_profile, data->version, inset, outset);
2489 dt_colormatrix_t inset_t, outset_t;
2490 transpose_3xSSE(inset, inset_t);
2491 transpose_3xSSE(outset, outset_t);
2492 const dt_aligned_pixel_simd_t inset0 = dt_colormatrix_row_to_simd(inset_t, 0);
2493 const dt_aligned_pixel_simd_t inset1 = dt_colormatrix_row_to_simd(inset_t, 1);
2494 const dt_aligned_pixel_simd_t inset2 = dt_colormatrix_row_to_simd(inset_t, 2);
2495 const dt_aligned_pixel_simd_t outset0 = dt_colormatrix_row_to_simd(outset_t, 0);
2496 const dt_aligned_pixel_simd_t outset1 = dt_colormatrix_row_to_simd(outset_t, 1);
2497 const dt_aligned_pixel_simd_t outset2 = dt_colormatrix_row_to_simd(outset_t, 2);
2498
2499 const dt_aligned_pixel_simd_t luma_coeffs = { work_profile->matrix_in[1][0], work_profile->matrix_in[1][1],
2500 work_profile->matrix_in[1][2], 0.f };
2501 const float beta_hue = data->agx_beta_hue;
2502
2504 for(size_t k = 0; k < height * width * ch; k += ch)
2505 {
2506 dt_aligned_pixel_simd_t pix_in = dt_load_simd_aligned(in + k);
2507 for(size_t c = 0; c < 3; c++) pix_in[c] = isnan(pix_in[c]) ? 0.f : CLAMPF(pix_in[c], -1e6f, 1e6f);
2508 const dt_aligned_pixel_simd_t compressed = filmic_agx_compress_negatives(pix_in, luma_coeffs);
2509
2510 // the hue reference is measured after the negatives compression : before it,
2511 // out-of-gamut pixels have no meaningful chromaticity
2512 const dt_aligned_pixel_simd_t Ych_original = pipe_RGB_to_Ych_simd(compressed, simd_matrices.input[0],
2513 simd_matrices.input[1], simd_matrices.input[2]);
2514
2515 dt_aligned_pixel_simd_t rendering = dt_mat3x4_mul_vec4(compressed, inset0, inset1, inset2);
2516 rendering = RGB_tone_mapping_v4_simd(rendering, data, spline);
2517 const dt_aligned_pixel_simd_t pix_out = dt_mat3x4_mul_vec4(rendering, outset0, outset1, outset2);
2518
2519 dt_aligned_pixel_simd_t Ych_final = pipe_RGB_to_Ych_simd(pix_out, simd_matrices.input[0],
2520 simd_matrices.input[1], simd_matrices.input[2]);
2521 // bleaching is allowed, spontaneous chroma boosts are not
2522 const float chroma_final = fminf(Ych_original[1], Ych_final[1]);
2523
2524 // Chroma is bracket-driven ONLY : chroma_final is the outset's over-expansion
2525 // recovery (valid diffuse colors reach the clamp = original chroma) then bleached where
2526 // the curve converges. The user slider does NOT recover chroma — mixing any
2527 // original chroma back in kinks highlight gradients (the recovered value fights
2528 // the bracket's smooth bleach roll-off at the min() clamp), so it was removed.
2529 //
2530 // The slider recovers HUE only. The mix MUST blend the chromaticity VECTORS
2531 // (chroma-weighted), not the hue angles : heavily bleached or clipped pixels
2532 // leave the curve with near-zero chroma and a meaningless hue (exactly
2533 // achromatic ones get the red-axis placeholder from pipe_RGB_to_Ych), and a
2534 // unit-vector hue mix weights that garbage as much as the real original hue —
2535 // mid-slider, a bright blue gradient swung through magenta this way. Weighted
2536 // by chroma, an achromatic result contributes no direction at all.
2537 // beta_hue : 0 at -100% (keep the AgX drift), 1 at +100% (original hue).
2538 const float r_mix = beta_hue * Ych_original[1] * Ych_original[2]
2539 + (1.f - beta_hue) * chroma_final * Ych_final[2];
2540 const float g_mix = beta_hue * Ych_original[1] * Ych_original[3]
2541 + (1.f - beta_hue) * chroma_final * Ych_final[3];
2542 const float norm_mix = dt_fast_hypotf(g_mix, r_mix);
2543 dt_aligned_pixel_simd_t Ych_reference = Ych_original;
2544 Ych_reference[2] = (norm_mix > 1e-9f) ? r_mix / norm_mix : Ych_original[2];
2545 Ych_reference[3] = (norm_mix > 1e-9f) ? g_mix / norm_mix : Ych_original[3];
2546 Ych_final[1] = chroma_final;
2547
2548 dt_store_simd_nontemporal(out + k,
2549 gamut_mapping_simd(Ych_final, Ych_reference, output_matrix,
2550 simd_matrices.input[0], simd_matrices.input[1], simd_matrices.input[2],
2551 simd_matrices.output[0], simd_matrices.output[1], simd_matrices.output[2],
2552 export_output_matrix,
2553 simd_matrices.export_input[0], simd_matrices.export_input[1], simd_matrices.export_input[2],
2554 simd_matrices.export_output[0], simd_matrices.export_output[1], simd_matrices.export_output[2],
2555 display_black, display_white, 0.f, use_output_profile));
2556 }
2557 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
2558}
2559
2560
2562static inline void display_mask(const float *const restrict mask, float *const restrict out,
2563 const size_t width, const size_t height)
2564{
2566 for(size_t k = 0; k < height * width; k++)
2567 {
2568 dt_store_simd_nontemporal(out + 4 * k, dt_simd_set1(mask[k]));
2569 }
2570 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
2571}
2572
2573
2575static inline void compute_ratios(const float *const restrict in, float *const restrict norms,
2576 float *const restrict ratios,
2577 const dt_iop_order_iccprofile_info_t *const work_profile,
2578 const int variant, const size_t width, const size_t height)
2579{
2581 for(size_t k = 0; k < height * width * 4; k += 4)
2582 {
2583 const dt_aligned_pixel_simd_t pix_in = dt_load_simd_aligned(in + k);
2584 const float norm = fmaxf(get_pixel_norm_simd(pix_in, variant, work_profile), NORM_MIN);
2585 norms[k / 4] = norm;
2586 dt_store_simd_nontemporal(ratios + k, pix_in / dt_simd_set1(norm));
2587 }
2588 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read the ratios
2589}
2590
2591
2593static inline void restore_ratios(float *const restrict ratios, const float *const restrict norms,
2594 const size_t width, const size_t height)
2595{
2597 for(size_t k = 0; k < height * width; k++)
2598 {
2599 dt_aligned_pixel_simd_t ratio = dt_load_simd_aligned(ratios + 4 * k);
2600 const float norm = norms[k];
2601
2602 for_each_channel(c,aligned(norms,ratios))
2603 ratio[c] = clamp_simd(ratio[c]) * norm;
2604
2605 dt_store_simd_nontemporal(ratios + 4 * k, ratio);
2606 }
2607 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read the ratios
2608}
2609
2611{
2613 {
2617 // LUT-only (non matrix-shaper) profiles - typical of printer/inkjet ICC profiles - are
2618 // flagged by NAN matrices. The gamut-mapping code below only knows how to use 3x3
2619 // matrices, so using a NAN one silently poisons the whole image with NaN pixels. Fall
2620 // back to the pipe output profile in that case; the actual soft-proof color conversion
2621 // still happens correctly downstream, in colorout, which supports full lcms2 transforms.
2622 if(!IS_NULL_PTR(softproof_profile) && !isnan(softproof_profile->matrix_in[0][0])
2623 && !isnan(softproof_profile->matrix_out[0][0]))
2624 return softproof_profile;
2625 }
2627}
2628
2629void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
2630{
2631 const dt_iop_roi_t *const roi_in = &piece->roi_in;
2632 const int scales = get_scales(pipe, roi_in, piece);
2633 const int max_filter_radius = (1 << scales);
2634
2635 // in + out + 2 * tmp + 2 * LF + 2 * temp + ratios
2636 tiling->factor = 9.0f;
2637 tiling->factor_cl = 9.0f;
2638
2639 tiling->maxbuf = 1.0f;
2640 tiling->maxbuf_cl = 1.0f;
2641 tiling->overhead = 0;
2642 tiling->overlap = max_filter_radius;
2643 tiling->xalign = 1;
2644 tiling->yalign = 1;
2645 return;
2646}
2647
2650 const void *const restrict ivoid,
2651 void *const restrict ovoid)
2652{
2653 const dt_iop_roi_t *const roi_in = &piece->roi_in;
2654 const dt_iop_roi_t *const roi_out = &piece->roi_out;
2655 const dt_iop_filmicrgb_data_t *const data = (dt_iop_filmicrgb_data_t *)piece->data;
2657 const dt_iop_order_iccprofile_info_t *const export_profile = _filmic_get_output_profile(pipe);
2658
2659 const size_t ch = 4;
2660
2670 float *restrict in = (float *)ivoid;
2671 float *const restrict out = (float *)ovoid;
2672 float *const restrict mask = dt_pixelpipe_cache_alloc_align_float((size_t)roi_out->width * roi_out->height, pipe);
2673 if(IS_NULL_PTR(mask)) return 1;
2674
2675 // used to adjuste noise level depending on size. Don't amplify noise if magnified > 100%
2676 const float scale = fmaxf(dt_dev_get_module_scale(pipe, roi_in), 1.f);
2677
2678 // build a mask of clipped pixels
2679 const int recover_highlights = mask_clipped_pixels(in, mask, data->normalize, data->reconstruct_feather, roi_out->width, roi_out->height, 4);
2680
2681 // display mask and exit
2682 if(self->dev->gui_attached && pipe->type == DT_DEV_PIXELPIPE_FULL && !IS_NULL_PTR(mask))
2683 {
2685
2686 if(!IS_NULL_PTR(g) && g->show_mask)
2687 {
2688 display_mask(mask, out, roi_out->width, roi_out->height);
2690 return 0;
2691 }
2692 }
2693
2694 float *const restrict reconstructed = dt_pixelpipe_cache_alloc_align_float((size_t)roi_out->width * roi_out->height * 4, pipe);
2695 if(recover_highlights && IS_NULL_PTR(reconstructed))
2696 {
2698 return 1;
2699 }
2700
2701 // if fast mode is not in use
2702 if(recover_highlights && !IS_NULL_PTR(mask) && !IS_NULL_PTR(reconstructed))
2703 {
2704 // init the blown areas with noise to create particles
2705 float *const restrict inpainted = dt_pixelpipe_cache_alloc_align_float((size_t)roi_out->width * roi_out->height * 4, pipe);
2706 if(IS_NULL_PTR(inpainted))
2707 {
2709 dt_pixelpipe_cache_free_align(reconstructed);
2710 return 1;
2711 }
2712 inpaint_noise(in, mask, inpainted, data->noise_level / scale, data->reconstruct_threshold, data->noise_distribution,
2713 roi_out->width, roi_out->height);
2714
2715 // diffuse particles with wavelets reconstruction
2716 // PASS 1 on RGB channels
2717 const int err_1 = reconstruct_highlights(pipe, inpainted, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RGB, ch, data, piece, roi_in, roi_out);
2718 int err_2 = 0;
2719
2721
2722 if(err_1)
2723 {
2724 dt_pixelpipe_cache_free_align(reconstructed);
2726 return 1;
2727 }
2728
2729 if(data->high_quality_reconstruction > 0)
2730 {
2731 float *const restrict norms = dt_pixelpipe_cache_alloc_align_float((size_t)roi_out->width * roi_out->height, pipe);
2732 float *const restrict ratios = dt_pixelpipe_cache_alloc_align_float((size_t)roi_out->width * roi_out->height * 4, pipe);
2733 if(IS_NULL_PTR(norms) || IS_NULL_PTR(ratios))
2734 {
2737 dt_pixelpipe_cache_free_align(reconstructed);
2739 return 1;
2740 }
2741
2742 // reconstruct highlights PASS 2 on ratios
2743 if(!IS_NULL_PTR(norms) && ratios)
2744 {
2745 for(int i = 0; i < data->high_quality_reconstruction; i++)
2746 {
2747 compute_ratios(reconstructed, norms, ratios, work_profile, DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1,
2748 roi_out->width, roi_out->height);
2749 if(reconstruct_highlights(pipe, ratios, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RATIOS, ch,
2750 data, piece, roi_in, roi_out))
2751 {
2752 err_2 = 1;
2753 break;
2754 }
2755 restore_ratios(reconstructed, norms, roi_out->width, roi_out->height);
2756 }
2757 }
2758
2761 }
2762
2763 if(err_2)
2764 {
2765 dt_pixelpipe_cache_free_align(reconstructed);
2767 return 1;
2768 }
2769
2770 in = reconstructed; // use reconstructed buffer as tonemapping input
2771 }
2772
2774
2775 const float white_display = powf(data->spline.y[4], data->output_power);
2776 const float black_display = powf(data->spline.y[0], data->output_power);
2777
2778 if(_filmic_is_agx(data->version))
2779 {
2780 // AgX color science : per-channel curve in an inset rendering space with
2781 // parametric Ych hue recovery. Ignores preserve_color, like v7.
2782 filmic_agx(in, out, work_profile, export_profile, data, data->spline, roi_out->width,
2783 roi_out->height, ch, black_display, white_display);
2784 }
2785 else if(data->version == DT_FILMIC_COLORSCIENCE_V5)
2786 {
2787 filmic_v5(in, out, work_profile, export_profile, data, data->spline, roi_out->width,
2788 roi_out->height, ch, black_display, white_display);
2789 }
2790 else
2791 {
2793 {
2794 // no chroma preservation
2796 filmic_split_v1(in, out, work_profile, data, data->spline, roi_out->width, roi_in->height);
2798 filmic_split_v2_v3(in, out, work_profile, data, data->spline, roi_out->width, roi_in->height);
2799 else if(data->version == DT_FILMIC_COLORSCIENCE_V4)
2800 filmic_split_v4(in, out, work_profile, export_profile, data, data->spline, data->preserve_color, roi_out->width,
2801 roi_out->height, ch, data->version, black_display, white_display);
2802 }
2803 else
2804 {
2805 // chroma preservation
2807 filmic_chroma_v1(in, out, work_profile, data, data->spline, data->preserve_color, roi_out->width,
2808 roi_out->height);
2810 filmic_chroma_v2_v3(in, out, work_profile, data, data->spline, data->preserve_color, roi_out->width,
2811 roi_out->height, ch, data->version);
2812 else if(data->version == DT_FILMIC_COLORSCIENCE_V4)
2813 filmic_chroma_v4(in, out, work_profile, export_profile, data, data->spline, data->preserve_color, roi_out->width,
2814 roi_out->height, ch, data->version, black_display, white_display);
2815 }
2816 }
2817
2818 dt_pixelpipe_cache_free_align(reconstructed);
2819
2821 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
2822 return 0;
2823}
2824
2825#ifdef HAVE_OPENCL
2826static inline cl_int reconstruct_highlights_cl(const dt_dev_pixelpipe_t *pipe, cl_mem in, cl_mem mask, cl_mem reconstructed,
2828 const dt_iop_filmicrgb_data_t *const data, const dt_dev_pixelpipe_iop_t *piece,
2829 const dt_iop_roi_t *const roi_in)
2830{
2831 cl_int err = -999;
2832 const int devid = pipe->devid;
2833 const int width = roi_in->width;
2834 const int height = roi_in->height;
2835 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
2836
2837 // wavelets scales
2838 const int scales = get_scales(pipe, roi_in, piece);
2839
2840 // wavelets scales buffers
2841 cl_mem LF_even = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // low-frequencies RGB
2842 cl_mem LF_odd = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // low-frequencies RGB
2843 cl_mem HF_RGB = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // high-frequencies RGB
2844 cl_mem HF_grey = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4); // high-frequencies RGB backup
2845
2846 // alloc a permanent reusable buffer for intermediate computations - avoid multiple alloc/free
2847 cl_mem temp = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);;
2848
2849 if(IS_NULL_PTR(LF_even) || IS_NULL_PTR(LF_odd) || IS_NULL_PTR(HF_RGB) || IS_NULL_PTR(HF_grey) || IS_NULL_PTR(temp))
2850 {
2851 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
2852 goto error;
2853 }
2854
2855 // Init reconstructed with valid parts of image
2856 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 0, sizeof(cl_mem), (void *)&in);
2857 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 1, sizeof(cl_mem), (void *)&mask);
2858 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 2, sizeof(cl_mem), (void *)&reconstructed);
2859 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 3, sizeof(int), (void *)&width);
2860 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_init_reconstruct, 4, sizeof(int), (void *)&height);
2862 if(err != CL_SUCCESS) goto error;
2863
2864 // structure inpainting vs. texture duplicating weight
2865 const float gamma = (data->reconstruct_structure_vs_texture);
2866 const float gamma_comp = 1.0f - data->reconstruct_structure_vs_texture;
2867
2868 // colorful vs. grey weight
2869 const float beta = data->reconstruct_grey_vs_color;
2870 const float beta_comp = 1.f - data->reconstruct_grey_vs_color;
2871
2872 // bloom vs reconstruct weight
2873 const float delta = data->reconstruct_bloom_vs_details;
2874
2875 // À trous wavelet decompose
2876 // there is a paper from a guy we know that explains it : https://jo.dreggn.org/home/2010_atrous.pdf
2877 // the wavelets decomposition here is the same as the equalizer/atrous module,
2878 // but simplified because we don't need the edge-aware term, so we can separate the convolution kernel
2879 // with a vertical and horizontal blur, which is 10 multiply-add instead of 25 by pixel.
2880 for(int s = 0; s < scales; ++s)
2881 {
2882 cl_mem detail;
2883 cl_mem LF;
2884
2885 // swap buffers so we only need 2 LF buffers : the LF at scale (s-1) and the one at current scale (s)
2886 if(s == 0)
2887 {
2888 detail = in;
2889 LF = LF_odd;
2890 }
2891 else if(s % 2 != 0)
2892 {
2893 detail = LF_odd;
2894 LF = LF_even;
2895 }
2896 else
2897 {
2898 detail = LF_even;
2899 LF = LF_odd;
2900 }
2901
2902 const int mult = 1 << s; // fancy-pants C notation for 2^s with integer type, don't be afraid
2903
2904 // Compute wavelets low-frequency scales
2905 const int clamp_lf = 1;
2906 int hblocksize;
2907 dt_opencl_local_buffer_t hlocopt = (dt_opencl_local_buffer_t){ .xoffset = 2 * mult, .xfactor = 1,
2908 .yoffset = 0, .yfactor = 1,
2909 .cellsize = 4 * sizeof(float), .overhead = 0,
2910 .sizex = 1 << 16, .sizey = 1 };
2912 hblocksize = hlocopt.sizex;
2913 else
2914 hblocksize = 1;
2915
2916 if(hblocksize > 1)
2917 {
2918 const size_t horizontal_sizes[3] = { ROUNDUP(width, hblocksize), ROUNDUPDHT(height, devid), 1 };
2919 const size_t horizontal_local[3] = { hblocksize, 1, 1 };
2920 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 0, sizeof(cl_mem), (void *)&detail);
2921 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 1, sizeof(cl_mem), (void *)&temp);
2922 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 2, sizeof(int), (void *)&width);
2923 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 3, sizeof(int), (void *)&height);
2924 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 4, sizeof(int), (void *)&mult);
2925 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal_local, 5, sizeof(int), (void *)&clamp_lf);
2927 (hblocksize + 4 * mult) * 4 * sizeof(float), NULL);
2929 horizontal_sizes, horizontal_local);
2930 }
2931 else
2932 {
2933 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 0, sizeof(cl_mem), (void *)&detail);
2934 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 1, sizeof(cl_mem), (void *)&temp);
2935 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 2, sizeof(int), (void *)&width);
2936 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 3, sizeof(int), (void *)&height);
2937 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 4, sizeof(int), (void *)&mult);
2938 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 5, sizeof(int), (void *)&clamp_lf);
2940 }
2941 if(err != CL_SUCCESS) goto error;
2942
2943 int vblocksize;
2944 dt_opencl_local_buffer_t vlocopt = (dt_opencl_local_buffer_t){ .xoffset = 0, .xfactor = 1,
2945 .yoffset = 2 * mult, .yfactor = 1,
2946 .cellsize = 4 * sizeof(float), .overhead = 0,
2947 .sizex = 1, .sizey = 1 << 16 };
2949 vblocksize = vlocopt.sizey;
2950 else
2951 vblocksize = 1;
2952
2953 if(vblocksize > 1)
2954 {
2955 const size_t vertical_sizes[3] = { ROUNDUPDWD(width, devid), ROUNDUP(height, vblocksize), 1 };
2956 const size_t vertical_local[3] = { 1, vblocksize, 1 };
2957 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 0, sizeof(cl_mem), (void *)&temp);
2958 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 1, sizeof(cl_mem), (void *)&LF);
2959 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 2, sizeof(int), (void *)&width);
2960 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 3, sizeof(int), (void *)&height);
2961 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 4, sizeof(int), (void *)&mult);
2962 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical_local, 5, sizeof(int), (void *)&clamp_lf);
2964 (vblocksize + 4 * mult) * 4 * sizeof(float), NULL);
2966 vertical_sizes, vertical_local);
2967 }
2968 else
2969 {
2970 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 0, sizeof(cl_mem), (void *)&temp);
2971 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 1, sizeof(cl_mem), (void *)&LF);
2972 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 2, sizeof(int), (void *)&width);
2973 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 3, sizeof(int), (void *)&height);
2974 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 4, sizeof(int), (void *)&mult);
2975 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 5, sizeof(int), (void *)&clamp_lf);
2977 }
2978 if(err != CL_SUCCESS) goto error;
2979
2980 // Compute wavelets high-frequency scales and backup the maximum of texture over the RGB channels
2981 // Note : HF_RGB = detail - LF
2982 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 0, sizeof(cl_mem), (void *)&detail);
2983 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 1, sizeof(cl_mem), (void *)&LF);
2984 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 2, sizeof(cl_mem), (void *)&HF_RGB);
2985 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 3, sizeof(int), (void *)&width);
2986 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_detail, 4, sizeof(int), (void *)&height);
2988 if(err != CL_SUCCESS) goto error;
2989
2990 // Take a backup copy of HF_RGB in HF_grey - only HF_RGB will be blurred
2991 size_t origin[] = { 0, 0, 0 };
2992 err = dt_opencl_enqueue_copy_image(devid, HF_RGB, HF_grey, origin, origin, sizes);
2993 if(err != CL_SUCCESS) goto error;
2994
2995 // interpolate/blur/inpaint (same thing) the RGB high-frequency to fill holes
2996 const int blur_size = 1;
2997 const int clamp_hf = 0;
2998 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 0, sizeof(cl_mem), (void *)&HF_RGB);
2999 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 1, sizeof(cl_mem), (void *)&temp);
3000 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 2, sizeof(int), (void *)&width);
3001 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 3, sizeof(int), (void *)&height);
3002 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 4, sizeof(int), (void *)&blur_size);
3003 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_vertical, 5, sizeof(int), (void *)&clamp_hf);
3005 if(err != CL_SUCCESS) goto error;
3006
3007 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 0, sizeof(cl_mem), (void *)&temp);
3008 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 1, sizeof(cl_mem), (void *)&HF_RGB);
3009 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 2, sizeof(int), (void *)&width);
3010 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 3, sizeof(int), (void *)&height);
3011 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 4, sizeof(int), (void *)&blur_size);
3012 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_bspline_horizontal, 5, sizeof(int), (void *)&clamp_hf);
3014 if(err != CL_SUCCESS) goto error;
3015
3016 // Reconstruct clipped parts
3017 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 0, sizeof(cl_mem), (void *)&HF_RGB);
3018 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 1, sizeof(cl_mem), (void *)&LF);
3019 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 2, sizeof(cl_mem), (void *)&HF_grey);
3020 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 3, sizeof(cl_mem), (void *)&mask);
3021 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 4, sizeof(cl_mem), (void *)&reconstructed);
3022 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 5, sizeof(cl_mem), (void *)&reconstructed);
3023 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 6, sizeof(int), (void *)&width);
3024 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 7, sizeof(int), (void *)&height);
3025 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 8, sizeof(float), (void *)&gamma);
3026 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 9, sizeof(float), (void *)&gamma_comp);
3027 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 10, sizeof(float), (void *)&beta);
3028 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 11, sizeof(float), (void *)&beta_comp);
3029 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 12, sizeof(float), (void *)&delta);
3030 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 13, sizeof(int), (void *)&s);
3031 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 14, sizeof(int), (void *)&scales);
3032 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_wavelets_reconstruct, 15, sizeof(int), (void *)&variant);
3034 if(err != CL_SUCCESS) goto error;
3035 }
3036
3037error:
3043 return err;
3044}
3045
3046
3047int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
3048{
3049 const dt_iop_roi_t *const roi_in = &piece->roi_in;
3050 const dt_iop_filmicrgb_data_t *const d = (dt_iop_filmicrgb_data_t *)piece->data;
3052
3053 cl_int err = -999;
3054
3055 const int devid = pipe->devid;
3056 const int width = roi_in->width;
3057 const int height = roi_in->height;
3058
3059 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
3060
3061 cl_mem in = dev_in;
3062 cl_mem inpainted = NULL;
3063 cl_mem reconstructed = NULL;
3064 cl_mem mask = NULL;
3065 cl_mem ratios = NULL;
3066 cl_mem norms = NULL;
3067
3068 // fetch working color profile
3070 const dt_iop_order_iccprofile_info_t *const export_profile = _filmic_get_output_profile(pipe);
3071 const int use_work_profile = (IS_NULL_PTR(work_profile)) ? 0 : 1;
3072
3073 // See colorbalancergb.c for details
3074 dt_colormatrix_t input_matrix; // pipeline RGB -> LMS 2006
3075 dt_colormatrix_t output_matrix; // LMS 2006 -> pipeline RGB
3076 dt_colormatrix_t export_input_matrix; // output RGB -> LMS 2006
3077 dt_colormatrix_t export_output_matrix; // LMS 2006 -> output RGB
3078
3079 const int use_output_profile = filmic_v4_prepare_matrices(input_matrix, output_matrix, export_input_matrix,
3080 export_output_matrix, work_profile, export_profile);
3081
3082 const float norm_min = exp_tonemapping_v2(0.f, d->grey_source, d->black_source, d->dynamic_range);
3083 const float norm_max = exp_tonemapping_v2(1.f, d->grey_source, d->black_source, d->dynamic_range);
3084
3085 float input_matrix_3x4[12];
3086 float output_matrix_3x4[12];
3087 pack_3xSSE_to_3x4(input_matrix, input_matrix_3x4);
3088 pack_3xSSE_to_3x4(output_matrix, output_matrix_3x4);
3089
3090 cl_mem input_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(input_matrix_3x4), input_matrix_3x4);
3091 cl_mem output_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(output_matrix_3x4), output_matrix_3x4);
3092 cl_mem export_input_matrix_cl = NULL;
3093 cl_mem export_output_matrix_cl = NULL;
3094
3095 // AgX rendering-space bracket (v8 only)
3096 cl_mem inset_matrix_cl = NULL;
3097 cl_mem outset_matrix_cl = NULL;
3098 float luma_coeffs[4] = { work_profile->matrix_in[1][0], work_profile->matrix_in[1][1],
3099 work_profile->matrix_in[1][2], 0.f };
3100 if(_filmic_is_agx(d->version))
3101 {
3102 dt_colormatrix_t inset = { { 0.f } };
3103 dt_colormatrix_t outset = { { 0.f } };
3104 filmic_agx_prepare_bracket(work_profile, d->version, inset, outset);
3105 float inset_3x4[12];
3106 float outset_3x4[12];
3107 pack_3xSSE_to_3x4(inset, inset_3x4);
3108 pack_3xSSE_to_3x4(outset, outset_3x4);
3109 inset_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(inset_3x4), inset_3x4);
3110 outset_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(outset_3x4), outset_3x4);
3111 }
3112
3113 cl_mem dev_profile_info = NULL;
3114 cl_mem dev_profile_lut = NULL;
3115 dt_colorspaces_iccprofile_info_cl_t *profile_info_cl;
3116 cl_float *profile_lut_cl = NULL;
3117
3118 cl_mem clipped = NULL;
3119
3120 err = dt_ioppr_build_iccprofile_params_cl(work_profile, devid, &profile_info_cl, &profile_lut_cl,
3121 &dev_profile_info, &dev_profile_lut);
3122 if(err != CL_SUCCESS) goto error;
3123
3124 if(use_output_profile)
3125 {
3126 float export_input_matrix_3x4[12];
3127 float export_output_matrix_3x4[12];
3128 pack_3xSSE_to_3x4(export_input_matrix, export_input_matrix_3x4);
3129 pack_3xSSE_to_3x4(export_output_matrix, export_output_matrix_3x4);
3130 export_input_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(export_input_matrix_3x4), export_input_matrix_3x4);
3131 export_output_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(export_output_matrix_3x4), export_output_matrix_3x4);
3132 }
3133
3134 // used to adjust noise level depending on size. Don't amplify noise if magnified > 100%
3135 const float scale = fmaxf(dt_dev_get_module_scale(pipe, roi_in), 1.f);
3136
3137 uint32_t is_clipped = 0;
3138 clipped = dt_opencl_alloc_device_buffer(devid, sizeof(uint32_t));
3139 err = dt_opencl_write_buffer_to_device(devid, &is_clipped, clipped, 0, sizeof(uint32_t), CL_TRUE);
3140 if(err != CL_SUCCESS) goto error;
3141
3142 // build a mask of clipped pixels
3143 mask = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float));
3144 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 0, sizeof(cl_mem), (void *)&in);
3145 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 1, sizeof(cl_mem), (void *)&mask);
3146 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 2, sizeof(int), (void *)&width);
3147 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 3, sizeof(int), (void *)&height);
3148 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 4, sizeof(float), (void *)&d->normalize);
3149 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 5, sizeof(float), (void *)&d->reconstruct_feather);
3150 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_mask, 6, sizeof(cl_mem), (void *)&clipped);
3151 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_filmic_mask, sizes);
3152 if(err != CL_SUCCESS) goto error;
3153
3154 // check for clipped pixels
3155 err = dt_opencl_read_buffer_from_device(devid, &is_clipped, clipped, 0, sizeof(uint32_t), CL_TRUE);
3156 if(err != CL_SUCCESS) goto error;
3158 clipped = NULL;
3159
3160 // display mask and exit
3161 if(self->dev->gui_attached && pipe->type == DT_DEV_PIXELPIPE_FULL)
3162 {
3164
3165 if(!IS_NULL_PTR(g) && g->show_mask)
3166 {
3167 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 0, sizeof(cl_mem), (void *)&mask);
3168 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 1, sizeof(cl_mem), (void *)&dev_out);
3169 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 2, sizeof(int), (void *)&width);
3170 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_show_mask, 3, sizeof(int), (void *)&height);
3173 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
3174 dt_opencl_release_mem_object(input_matrix_cl);
3175 dt_opencl_release_mem_object(output_matrix_cl);
3176 dt_opencl_release_mem_object(export_input_matrix_cl);
3177 dt_opencl_release_mem_object(export_output_matrix_cl);
3178 dt_opencl_release_mem_object(inset_matrix_cl);
3179 dt_opencl_release_mem_object(outset_matrix_cl);
3180 return TRUE;
3181 }
3182 }
3183
3184 if(is_clipped > 0)
3185 {
3186 // Inpaint noise
3187 const float noise_level = d->noise_level / scale;
3188 inpainted = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
3189 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 0, sizeof(cl_mem), (void *)&in);
3190 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 1, sizeof(cl_mem), (void *)&mask);
3191 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 2, sizeof(cl_mem), (void *)&inpainted);
3192 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 3, sizeof(int), (void *)&width);
3193 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 4, sizeof(int), (void *)&height);
3194 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 5, sizeof(float), (void *)&noise_level);
3195 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 6, sizeof(float), (void *)&d->reconstruct_threshold);
3196 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_inpaint_noise, 7, sizeof(float), (void *)&d->noise_distribution);
3198 if(err != CL_SUCCESS) goto error;
3199
3200 // first step of highlight reconstruction in RGB
3201 reconstructed = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
3202 err = reconstruct_highlights_cl(pipe, inpainted, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RGB, gd, d, piece, roi_in);
3203 if(err != CL_SUCCESS) goto error;
3205 inpainted = NULL;
3206
3207 if(d->high_quality_reconstruction > 0)
3208 {
3209 ratios = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
3210 norms = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float));
3211
3212 if(norms && ratios)
3213 {
3214 for(int i = 0; i < d->high_quality_reconstruction; i++)
3215 {
3216 // break ratios and norms
3217 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 0, sizeof(cl_mem), (void *)&reconstructed);
3218 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 1, sizeof(cl_mem), (void *)&norms);
3219 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 2, sizeof(cl_mem), (void *)&ratios);
3220 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 3, sizeof(int), (void *)&d->preserve_color);
3221 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 4, sizeof(int), (void *)&width);
3222 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_compute_ratios, 5, sizeof(int), (void *)&height);
3224 if(err != CL_SUCCESS) goto error;
3225
3226 // second step of reconstruction over ratios
3227 err = reconstruct_highlights_cl(pipe, ratios, mask, reconstructed, DT_FILMIC_RECONSTRUCT_RATIOS, gd, d, piece, roi_in);
3228 if(err != CL_SUCCESS) goto error;
3229
3230 // restore ratios to RGB
3231 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 0, sizeof(cl_mem), (void *)&reconstructed);
3232 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 1, sizeof(cl_mem), (void *)&norms);
3233 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 2, sizeof(cl_mem), (void *)&reconstructed);
3234 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 3, sizeof(int), (void *)&width);
3235 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_restore_ratios, 4, sizeof(int), (void *)&height);
3237 if(err != CL_SUCCESS) goto error;
3238 }
3239 }
3240
3243 ratios = NULL;
3244 norms = NULL;
3245 }
3246
3247 in = reconstructed;
3248 }
3249
3250 dt_opencl_release_mem_object(mask); // mask is only used for highlights reconstruction.
3251 mask = NULL;
3252
3254
3255 const float white_display = powf(spline.y[4], d->output_power);
3256 const float black_display = powf(spline.y[0], d->output_power);
3257
3258 if(d->preserve_color == DT_FILMIC_METHOD_NONE && d->version != DT_FILMIC_COLORSCIENCE_V5
3259 && !_filmic_is_agx(d->version))
3260 {
3261 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 0, sizeof(cl_mem), (void *)&in);
3262 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 1, sizeof(cl_mem), (void *)&dev_out);
3263 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 2, sizeof(int), (void *)&width);
3264 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 3, sizeof(int), (void *)&height);
3265 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 4, sizeof(float), (void *)&d->dynamic_range);
3266 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 5, sizeof(float), (void *)&d->black_source);
3267 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 6, sizeof(float), (void *)&d->grey_source);
3268 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 7, sizeof(cl_mem), (void *)&dev_profile_info);
3269 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 8, sizeof(cl_mem), (void *)&dev_profile_lut);
3270 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 9, sizeof(int), (void *)&use_work_profile);
3271 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 10, sizeof(float), (void *)&d->sigma_toe);
3272 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 11, sizeof(float), (void *)&d->sigma_shoulder);
3273 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 12, sizeof(float), (void *)&d->saturation);
3274 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 13, 4 * sizeof(float), (void *)&spline.M1);
3275 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 14, 4 * sizeof(float), (void *)&spline.M2);
3276 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 15, 4 * sizeof(float), (void *)&spline.M3);
3277 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 16, 4 * sizeof(float), (void *)&spline.M4);
3278 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 17, 4 * sizeof(float), (void *)&spline.M5);
3279 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 18, sizeof(float), (void *)&spline.latitude_min);
3280 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 19, sizeof(float), (void *)&spline.latitude_max);
3281 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 20, sizeof(float), (void *)&d->output_power);
3282 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 21, sizeof(int), (void *)&d->version);
3283 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 22, sizeof(int), (void *)&spline.type[0]);
3284 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 23, sizeof(int), (void *)&spline.type[1]);
3285 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 24, sizeof(cl_mem), (void *)&input_matrix_cl);
3286 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 25, sizeof(cl_mem), (void *)&output_matrix_cl);
3287 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 26, sizeof(float), (void *)&black_display);
3288 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 27, sizeof(float), (void *)&white_display);
3289 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 28, sizeof(int), (void *)&use_output_profile);
3290 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 29, sizeof(cl_mem), (void *)&export_input_matrix_cl);
3291 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 30, sizeof(cl_mem), (void *)&export_output_matrix_cl);
3292 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 31, sizeof(float), (void *)&spline.y[0]);
3293 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_split, 32, sizeof(float), (void *)&spline.y[4]);
3294
3296 if(err != CL_SUCCESS) goto error;
3297 }
3298 else
3299 {
3300 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 0, sizeof(cl_mem), (void *)&in);
3301 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 1, sizeof(cl_mem), (void *)&dev_out);
3302 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 2, sizeof(int), (void *)&width);
3303 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 3, sizeof(int), (void *)&height);
3304 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 4, sizeof(float), (void *)&d->dynamic_range);
3305 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 5, sizeof(float), (void *)&d->black_source);
3306 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 6, sizeof(float), (void *)&d->grey_source);
3307 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 7, sizeof(cl_mem), (void *)&dev_profile_info);
3308 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 8, sizeof(cl_mem), (void *)&dev_profile_lut);
3309 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 9, sizeof(int), (void *)&use_work_profile);
3310 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 10, sizeof(float), (void *)&d->sigma_toe);
3311 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 11, sizeof(float), (void *)&d->sigma_shoulder);
3312 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 12, sizeof(float), (void *)&d->saturation);
3313 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 13, 4 * sizeof(float), (void *)&spline.M1);
3314 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 14, 4 * sizeof(float), (void *)&spline.M2);
3315 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 15, 4 * sizeof(float), (void *)&spline.M3);
3316 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 16, 4 * sizeof(float), (void *)&spline.M4);
3317 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 17, 4 * sizeof(float), (void *)&spline.M5);
3318 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 18, sizeof(float), (void *)&spline.latitude_min);
3319 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 19, sizeof(float), (void *)&spline.latitude_max);
3320 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 20, sizeof(float), (void *)&d->output_power);
3321 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 21, sizeof(int), (void *)&d->preserve_color);
3322 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 22, sizeof(int), (void *)&d->version);
3323 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 23, sizeof(int), (void *)&spline.type[0]);
3324 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 24, sizeof(int), (void *)&spline.type[1]);
3325 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 25, sizeof(cl_mem), (void *)&input_matrix_cl);
3326 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 26, sizeof(cl_mem), (void *)&output_matrix_cl);
3327 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 27, sizeof(float), (void *)&black_display);
3328 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 28, sizeof(float), (void *)&white_display);
3329 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 29, sizeof(int), (void *)&use_output_profile);
3330 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 30, sizeof(cl_mem), (void *)&export_input_matrix_cl);
3331 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 31, sizeof(cl_mem), (void *)&export_output_matrix_cl);
3332 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 32, sizeof(float), (void *)&norm_min);
3333 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 33, sizeof(float), (void *)&norm_max);
3334 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 34, sizeof(float), (void *)&spline.y[0]);
3335 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 35, sizeof(float), (void *)&spline.y[4]);
3336 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 36, sizeof(cl_mem), (void *)&inset_matrix_cl);
3337 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 37, sizeof(cl_mem), (void *)&outset_matrix_cl);
3338 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 38, 4 * sizeof(float), (void *)&luma_coeffs);
3339 dt_opencl_set_kernel_arg(devid, gd->kernel_filmic_rgb_chroma, 39, sizeof(float), (void *)&d->agx_beta_hue);
3340
3342 if(err != CL_SUCCESS) goto error;
3343 }
3344
3345 dt_opencl_release_mem_object(reconstructed);
3346 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
3347 dt_opencl_release_mem_object(input_matrix_cl);
3348 dt_opencl_release_mem_object(output_matrix_cl);
3349 dt_opencl_release_mem_object(export_input_matrix_cl);
3350 dt_opencl_release_mem_object(export_output_matrix_cl);
3351 dt_opencl_release_mem_object(inset_matrix_cl);
3352 dt_opencl_release_mem_object(outset_matrix_cl);
3353 return TRUE;
3354
3355error:
3356 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
3357 dt_opencl_release_mem_object(reconstructed);
3362 dt_opencl_release_mem_object(input_matrix_cl);
3363 dt_opencl_release_mem_object(output_matrix_cl);
3364 dt_opencl_release_mem_object(export_input_matrix_cl);
3365 dt_opencl_release_mem_object(export_output_matrix_cl);
3366 dt_opencl_release_mem_object(inset_matrix_cl);
3367 dt_opencl_release_mem_object(outset_matrix_cl);
3369 dt_print(DT_DEBUG_OPENCL, "[opencl_filmicrgb] couldn't enqueue kernel! %d\n", err);
3370 return FALSE;
3371}
3372#endif
3373
3374
3376 const dt_iop_order_iccprofile_info_t *const work_profile)
3377{
3378 if(dt_gui_widgets_suppressed()) return;
3381
3382 const float grey = get_pixel_norm(self->picked_color, p->preserve_color, work_profile) / 2.0f;
3383
3384 const float prev_grey = p->grey_point_source;
3385 p->grey_point_source = CLAMP(100.f * grey, 0.001f, 100.0f);
3386 const float grey_var = log2f(prev_grey / p->grey_point_source);
3387 p->black_point_source = p->black_point_source - grey_var;
3388 p->white_point_source = p->white_point_source + grey_var;
3389 p->output_power = logf(p->grey_point_target / 100.0f)
3390 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
3391
3393 dt_bauhaus_slider_set(g->grey_point_source, p->grey_point_source);
3394 dt_bauhaus_slider_set(g->black_point_source, p->black_point_source);
3395 dt_bauhaus_slider_set(g->white_point_source, p->white_point_source);
3396 dt_bauhaus_slider_set(g->output_power, p->output_power);
3398
3399 gtk_widget_queue_draw(self->widget);
3401}
3402
3404 const dt_iop_order_iccprofile_info_t *const work_profile)
3405{
3406 if(dt_gui_widgets_suppressed()) return;
3409
3410 // Black
3411 const float black = get_pixel_norm(self->picked_color_min, DT_FILMIC_METHOD_MAX_RGB, work_profile);
3412
3413 float EVmin = CLAMP(log2f(black / (p->grey_point_source / 100.0f)), -16.0f, -1.0f);
3414 EVmin *= (1.0f + p->security_factor / 100.0f);
3415
3416 p->black_point_source = fmaxf(EVmin, -16.0f);
3417 p->output_power = logf(p->grey_point_target / 100.0f)
3418 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
3419
3421 dt_bauhaus_slider_set(g->black_point_source, p->black_point_source);
3422 dt_bauhaus_slider_set(g->output_power, p->output_power);
3424
3425 gtk_widget_queue_draw(self->widget);
3427}
3428
3429
3431 const dt_iop_order_iccprofile_info_t *const work_profile)
3432{
3433 if(dt_gui_widgets_suppressed()) return;
3436
3437 // White
3438 const float white = get_pixel_norm(self->picked_color_max, DT_FILMIC_METHOD_MAX_RGB, work_profile);
3439
3440 float EVmax = CLAMP(log2f(white / (p->grey_point_source / 100.0f)), 1.0f, 16.0f);
3441 EVmax *= (1.0f + p->security_factor / 100.0f);
3442
3443 p->white_point_source = EVmax;
3444 p->output_power = logf(p->grey_point_target / 100.0f)
3445 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
3446
3448 dt_bauhaus_slider_set(g->white_point_source, p->white_point_source);
3449 dt_bauhaus_slider_set(g->output_power, p->output_power);
3451
3452 gtk_widget_queue_draw(self->widget);
3454}
3455
3457 const dt_iop_order_iccprofile_info_t *const work_profile)
3458{
3461
3462 // Grey
3463 if(p->custom_grey)
3464 {
3465 const float grey = get_pixel_norm(self->picked_color, p->preserve_color, work_profile) / 2.0f;
3466 p->grey_point_source = CLAMP(100.f * grey, 0.001f, 100.0f);
3467 }
3468
3469 // White
3470 const float white = get_pixel_norm(self->picked_color_max, DT_FILMIC_METHOD_MAX_RGB, work_profile);
3471 float EVmax = CLAMP(log2f(white / (p->grey_point_source / 100.0f)), 1.0f, 16.0f);
3472 EVmax *= (1.0f + p->security_factor / 100.0f);
3473
3474 // Black
3475 const float black = get_pixel_norm(self->picked_color_min, DT_FILMIC_METHOD_MAX_RGB, work_profile);
3476 float EVmin = CLAMP(log2f(black / (p->grey_point_source / 100.0f)), -16.0f, -1.0f);
3477 EVmin *= (1.0f + p->security_factor / 100.0f);
3478
3479 p->black_point_source = fmaxf(EVmin, -16.0f);
3480 p->white_point_source = EVmax;
3481 p->output_power = logf(p->grey_point_target / 100.0f)
3482 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
3483
3485 dt_bauhaus_slider_set(g->grey_point_source, p->grey_point_source);
3486 dt_bauhaus_slider_set(g->black_point_source, p->black_point_source);
3487 dt_bauhaus_slider_set(g->white_point_source, p->white_point_source);
3488 dt_bauhaus_slider_set(g->output_power, p->output_power);
3490
3491 gtk_widget_queue_draw(self->widget);
3493}
3494
3495void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
3496 const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
3497{
3498 const dt_iop_order_iccprofile_info_t *const work_profile
3499 = pipe ? dt_ioppr_get_pipe_current_profile_info(self, pipe)
3501 if(IS_NULL_PTR(work_profile) || piece->dsc_in.channels != 4) return;
3502
3504 const dt_iop_roi_t *const roi_out = &piece->roi_out;
3505 const float *const restrict in = (const float *)i;
3506
3507 float min_Y = INFINITY;
3508 float max_RGB = 0.0f;
3509
3510 __OMP_PARALLEL_FOR__(reduction(min:min_Y) reduction(max:max_RGB))
3511 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height * 4; k += 4)
3512 {
3513 dt_aligned_pixel_t XYZ = { 0.f };
3514 dt_ioppr_rgb_matrix_to_xyz(in + k, XYZ, work_profile->matrix_in_transposed, work_profile->lut_in,
3515 work_profile->unbounded_coeffs_in, work_profile->lutsize,
3516 work_profile->nonlinearlut);
3517
3518 if(isfinite(XYZ[1]))
3519 min_Y = fminf(min_Y, XYZ[1]);
3520
3521 const float pixel_max = fmaxf(in[k], fmaxf(in[k + 1], in[k + 2]));
3522 if(isfinite(pixel_max))
3523 max_RGB = fmaxf(max_RGB, pixel_max);
3524 }
3525
3526 if(!isfinite(min_Y) || !isfinite(max_RGB)) return;
3527
3528 const float grey = p->grey_point_source / 100.0f;
3529 const float white = fmaxf(max_RGB, NORM_MIN);
3530 const float black = fmaxf(min_Y, NORM_MIN);
3531
3532 float EVmax = CLAMP(log2f(white / grey), 1.0f, 16.0f);
3533 EVmax *= (1.0f + p->security_factor / 100.0f);
3534
3535 float EVmin = CLAMP(log2f(black / grey), -16.0f, -1.0f);
3536 EVmin *= (1.0f + p->security_factor / 100.0f);
3537
3538 p->black_point_source = fmaxf(EVmin, -16.0f);
3539 p->white_point_source = EVmax;
3540 p->output_power = logf(p->grey_point_target / 100.0f)
3541 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
3542}
3543
3545{
3546 (void)piece;
3547 dt_print(DT_DEBUG_DEV, "[picker/filmicrgb] apply picker=%p pipe=%p min=%g max=%g avg=%g\n",
3548 (void *)picker, (void *)pipe,
3549 self->picked_color_min[0], self->picked_color_max[0], self->picked_color[0]);
3551 const dt_iop_order_iccprofile_info_t *const work_profile
3552 = pipe ? dt_ioppr_get_pipe_current_profile_info(self, pipe)
3554
3555 if(picker == g->grey_point_source)
3556 apply_auto_grey(self, work_profile);
3557 else if(picker == g->black_point_source)
3558 apply_auto_black(self, work_profile);
3559 else if(picker == g->white_point_source)
3560 apply_auto_white_point_source(self, work_profile);
3561 else if(picker == g->auto_button)
3562 apply_autotune(self, work_profile);
3563}
3564
3565static void show_mask_callback(GtkToggleButton *button, GdkEventButton *event, gpointer user_data)
3566{
3567 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3568 if(dt_gui_widgets_suppressed()) return;
3569 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
3571
3572 // if blend module is displaying mask do not display it here
3575
3576 g->show_mask = !(g->show_mask);
3577
3578 if(g->show_mask)
3580
3581 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_highlight_mask), g->show_mask);
3582 dt_iop_set_cache_bypass(self, g->show_mask);
3584}
3585
3586#define ORDER_4 5
3587#define ORDER_3 4
3588
3589
3590/* Solve the scale of the generalized sigmoid u / (1 + u^p)^(1/p) so the segment
3591 * runs from the latitude transition point (value + slope continuity, since the
3592 * sigmoid derivative is 1 at 0) and passes exactly through (limit_x, limit_y).
3593 * Simplified from the AgX formulation : scale = (dy^-p - (m*dx)^-p)^(-1/p).
3594 * Monotonic for any power > 0, which is the whole point of the sigmoid spline. */
3595static inline float filmic_sigmoid_scale(const float limit_x, const float limit_y,
3596 const float transition_x, const float transition_y,
3597 const float slope, const float power)
3598{
3599 const float projected_rise = slope * fmaxf(1e-6f, limit_x - transition_x);
3600 const float actual_rise = fmaxf(1e-6f, limit_y - transition_y);
3601 const float base = fmaxf(1e-6f, powf(actual_rise, -power) - powf(projected_rise, -power));
3602 return fminf(1e9f, powf(base, -1.f / power));
3603}
3604
3605// returns true if contrast was clamped, false otherwise
3606// used in GUI, to show user when contrast clamping is happening
3608 struct dt_iop_filmic_rgb_spline_t *const spline)
3609{
3610 float grey_display = 0.4638f;
3611 gboolean clamping = FALSE;
3612
3613 if(p->custom_grey)
3614 {
3615 // user set a custom value
3616 grey_display = powf(CLAMP(p->grey_point_target, p->black_point_target, p->white_point_target) / 100.0f,
3617 1.0f / (p->output_power));
3618 }
3619 else
3620 {
3621 // use 18.45% grey and don't bother
3622 grey_display = powf(0.1845f, 1.0f / (p->output_power));
3623 }
3624
3625 const float white_source = p->white_point_source;
3626 const float black_source = p->black_point_source;
3627 const float dynamic_range = white_source - black_source;
3628
3629 // luminance after log encoding
3630 const float black_log = 0.0f; // assumes user set log as in the autotuner
3631 const float grey_log = fabsf(p->black_point_source) / dynamic_range;
3632 const float white_log = 1.0f; // assumes user set log as in the autotuner
3633
3634 // target luminance desired after filmic curve
3635 float black_display, white_display;
3636
3637 if(p->spline_version == DT_FILMIC_SPLINE_VERSION_V1)
3638 {
3639 // this is a buggy version that doesn't take the output power function into account
3640 // it was silent because black and white display were set to 0 and 1 and users were advised to not touch them.
3641 // (since 0^x = 0 and 1^x = 1). It's not silent anymore if black display > 0,
3642 // for example if compensating ICC black level for target medium
3643 black_display = CLAMP(p->black_point_target, 0.0f, p->grey_point_target) / 100.0f; // in %
3644 white_display = fmaxf(p->white_point_target, p->grey_point_target) / 100.0f; // in %
3645 }
3646 else //(p->spline_version >= DT_FILMIC_SPLINE_VERSION_V2)
3647 {
3648 // this is the fixed version
3649 black_display = powf(CLAMP(p->black_point_target, 0.0f, p->grey_point_target) / 100.0f,
3650 1.0f / (p->output_power)); // in %
3651 white_display
3652 = powf(fmaxf(p->white_point_target, p->grey_point_target) / 100.0f, 1.0f / (p->output_power)); // in %
3653 }
3654
3655 float toe_log, shoulder_log, toe_display, shoulder_display, contrast;
3656 float balance = CLAMP(p->balance, -50.0f, 50.0f) / 100.0f; // in %
3657 if(p->spline_version < DT_FILMIC_SPLINE_VERSION_V3)
3658 {
3659 float latitude = CLAMP(p->latitude, 0.0f, 100.0f) / 100.0f * dynamic_range; // in % of dynamic range
3660 contrast = CLAMP(p->contrast, 1.00001f, 6.0f);
3661
3662 // nodes for mapping from log encoding to desired target luminance
3663 // X coordinates
3664 toe_log = grey_log - latitude / dynamic_range * fabsf(black_source / dynamic_range);
3665 shoulder_log = grey_log + latitude / dynamic_range * fabsf(white_source / dynamic_range);
3666
3667 // interception
3668 float linear_intercept = grey_display - (contrast * grey_log);
3669
3670 // y coordinates
3671 toe_display = (toe_log * contrast + linear_intercept);
3672 shoulder_display = (shoulder_log * contrast + linear_intercept);
3673
3674 // Apply the highlights/shadows balance as a shift along the contrast slope
3675 const float norm = sqrtf(contrast * contrast + 1.0f);
3676
3677 // negative values drag to the left and compress the shadows, on the UI negative is the inverse
3678 const float coeff = -((2.0f * latitude) / dynamic_range) * balance;
3679
3680 toe_display += coeff * contrast / norm;
3681 shoulder_display += coeff * contrast / norm;
3682 toe_log += coeff / norm;
3683 shoulder_log += coeff / norm;
3684 }
3685 else // p->spline_version >= DT_FILMIC_SPLINE_VERSION_V3. Slope dependent on contrast only, and latitude as % of display range.
3686 {
3689 filmic_v3_compute_nodes_from_legacy(p, &geometry, &nodes);
3690 clamping = geometry.contrast_clamped;
3691 contrast = geometry.contrast;
3692 toe_log = nodes.toe_log;
3693 shoulder_log = nodes.shoulder_log;
3694 toe_display = nodes.toe_display;
3695 shoulder_display = nodes.shoulder_display;
3696 }
3697
3708 // Build the curve from the nodes
3709 spline->x[0] = black_log;
3710 spline->x[1] = toe_log;
3711 spline->x[2] = grey_log;
3712 spline->x[3] = shoulder_log;
3713 spline->x[4] = white_log;
3714
3715 spline->y[0] = black_display;
3716 spline->y[1] = toe_display;
3717 spline->y[2] = grey_display;
3718 spline->y[3] = shoulder_display;
3719 spline->y[4] = white_display;
3720
3721 spline->latitude_min = spline->x[1];
3722 spline->latitude_max = spline->x[3];
3723
3724 spline->type[0] = p->shadows;
3725 spline->type[1] = p->highlights;
3726
3732 const double Tl = spline->x[1];
3733 const double Tl2 = Tl * Tl;
3734 const double Tl3 = Tl2 * Tl;
3735 const double Tl4 = Tl3 * Tl;
3736
3737 const double Sl = spline->x[3];
3738 const double Sl2 = Sl * Sl;
3739 const double Sl3 = Sl2 * Sl;
3740 const double Sl4 = Sl3 * Sl;
3741
3742 // if type polynomial :
3743 // y = M5 * x⁴ + M4 * x³ + M3 * x² + M2 * x¹ + M1 * x⁰
3744 // else if type rational :
3745 // y = M1 * (M2 * (x - x_0)² + (x - x_0)) / (M2 * (x - x_0)² + (x - x_0) + M3)
3746 // We then compute M1 to M5 coeffs using the imposed conditions over the curve.
3747 // M1 to M5 are 3x1 vectors, where each element belongs to a part of the curve.
3748
3749 // solve the linear central part - affine function
3750 spline->M2[2] = contrast; // * x¹ (slope)
3751 spline->M1[2] = spline->y[1] - spline->M2[2] * spline->x[1]; // * x⁰ (offset)
3752 spline->M3[2] = 0.f; // * x²
3753 spline->M4[2] = 0.f; // * x³
3754 spline->M5[2] = 0.f; // * x⁴
3755
3756 // The "perceptual" toe and shoulder are grounded on DIFFERENT physical limits,
3757 // so they are shaped differently :
3758 // - SHOULDER : slope-matched power roll-off, exponent computed at RUNTIME from
3759 // the geometry (below). Highlights have no perceptual floor, and the
3760 // lightness match is indifferent to the shoulder power (its RMS is flat, so
3761 // fitting it just rails against whatever bound you give it — an earlier fixed
3762 // 7.8/9.0 sat on the JND ceiling and over-compressed the top highlight stop).
3763 // Matching the latitude slope instead is neutral and adaptive : q ~ 1 for a
3764 // low-DR studio curve (barely any roll-off), ~2.5 for a 14 EV ETTR curve
3765 // (more compression), never the "hold-then-crush" of a fixed high power.
3766 // - TOE : fixed exponent 1.5, from the CIECAM16-J appearance match with a local
3767 // JND tolerance. Shadows DO have a perceptual floor (veiling flare hides
3768 // detail below ~0.1% output), and 1.5 is the value that keeps *visible*
3769 // shadow gradients open there (the hardness power alone imposes slope 4.0 at
3770 // -6.5 EV, so the toe's job is counteracting it). Derivation :
3771 // tools/derive_filmic_default_curve.py.
3772 const float sigmoid_toe_power = 1.5f;
3773 const float sigmoid_slope = spline->M2[2];
3774 if(p->shadows == DT_FILMIC_CURVE_SIGMOID || p->highlights == DT_FILMIC_CURVE_SIGMOID)
3775 {
3776 // fallback targets, read only by the sigmoid branches of filmic_spline ; the
3777 // linear-segment evaluator ignores M3[2]/M4[2], so this is harmless when the
3778 // opposite side is a polynomial/rational curve.
3779 spline->M3[2] = spline->y[0]; // target black
3780 spline->M4[2] = spline->y[4]; // target white
3781 }
3782
3783 // solve the toe part
3784 if(p->shadows == DT_FILMIC_CURVE_SIGMOID)
3785 {
3786 // from (toe_log, toe_display) down to (0, black_display) ; mirror through
3787 // (0.5, 0.5) so the shoulder scale solver applies, then negate.
3788 const float tx = spline->x[1];
3789 const float ty = spline->y[1];
3790 const float y0 = spline->y[0];
3791 const float dx = fmaxf(1e-6f, tx);
3792 const float dy = fmaxf(1e-6f, ty - y0);
3793 spline->M1[0] = -filmic_sigmoid_scale(1.f, 1.f - y0, 1.f - tx, 1.f - ty, sigmoid_slope, sigmoid_toe_power);
3794 spline->M2[0] = sigmoid_toe_power;
3795 spline->M4[0] = sigmoid_slope * dx / dy; // fallback power, matches slope at transition
3796 spline->M3[0] = dy / powf(dx, spline->M4[0]); // fallback coefficient
3797 spline->M5[0] = (dy / dx > sigmoid_slope) ? 1.f : 0.f; // chord steeper than slope : no S shape
3798 }
3799 else if(p->shadows == DT_FILMIC_CURVE_POLY_4)
3800 {
3801 // fourth order polynom - only mode in darktable 3.0.0
3802 double A0[ORDER_4 * ORDER_4] = { 0., 0., 0., 0., 1., // position in 0
3803 0., 0., 0., 1., 0., // first derivative in 0
3804 Tl4, Tl3, Tl2, Tl, 1., // position at toe node
3805 4. * Tl3, 3. * Tl2, 2. * Tl, 1., 0., // first derivative at toe node
3806 12. * Tl2, 6. * Tl, 2., 0., 0. }; // second derivative at toe node
3807
3808 double b0[ORDER_4] = { spline->y[0], 0., spline->y[1], spline->M2[2], 0. };
3809
3810 gauss_solve(A0, b0, ORDER_4);
3811
3812 spline->M5[0] = b0[0]; // * x⁴
3813 spline->M4[0] = b0[1]; // * x³
3814 spline->M3[0] = b0[2]; // * x²
3815 spline->M2[0] = b0[3]; // * x¹
3816 spline->M1[0] = b0[4]; // * x⁰
3817 }
3818 else if(p->shadows == DT_FILMIC_CURVE_POLY_3)
3819 {
3820 // third order polynom
3821 double A0[ORDER_3 * ORDER_3] = { 0., 0., 0., 1., // position in 0
3822 Tl3, Tl2, Tl, 1., // position at toe node
3823 3. * Tl2, 2. * Tl, 1., 0., // first derivative at toe node
3824 6. * Tl, 2., 0., 0. }; // second derivative at toe node
3825
3826 double b0[ORDER_3] = { spline->y[0], spline->y[1], spline->M2[2], 0. };
3827
3828 gauss_solve(A0, b0, ORDER_3);
3829
3830 spline->M5[0] = 0.0f; // * x⁴
3831 spline->M4[0] = b0[0]; // * x³
3832 spline->M3[0] = b0[1]; // * x²
3833 spline->M2[0] = b0[2]; // * x¹
3834 spline->M1[0] = b0[3]; // * x⁰
3835 }
3836 else
3837 {
3838 const float P1[2] = { black_log, black_display };
3839 const float P0[2] = { toe_log, toe_display };
3840 const float x = P0[0] - P1[0];
3841 const float y = P0[1] - P1[1];
3842 const float g = contrast;
3843 const float b = g / (2.f * y) + (sqrtf(sqf(x * g / y + 1.f) - 4.f) - 1.f) / (2.f * x);
3844 const float c = y / g * (b * sqf(x) + x) / (b * sqf(x) + x - (y / g));
3845 const float a = c * g;
3846 spline->M1[0] = a;
3847 spline->M2[0] = b;
3848 spline->M3[0] = c;
3849 spline->M4[0] = toe_display;
3850 }
3851
3852 // solve the shoulder part
3853 if(p->highlights == DT_FILMIC_CURVE_SIGMOID)
3854 {
3855 // "perceptual" shoulder = slope-matched power roll-off from (shoulder_log,
3856 // shoulder_display) to (1, white_display). y = white - c*(1-x)^q with the
3857 // exponent q = slope*dx/dy chosen so the curve leaves the latitude node at
3858 // *exactly* the latitude slope, then glides to white (slope -> 0 there for
3859 // q > 1, which holds whenever the shoulder actually rolls off). No fixed
3860 // exponent : q is the geometry, adapting to how much range is compressed.
3861 // Evaluated by the sigmoid branch's power-curve path (M5[1] = 1).
3862 const float sx = spline->x[3];
3863 const float sy = spline->y[3];
3864 const float y4 = spline->y[4];
3865 const float dx = fmaxf(1e-6f, 1.f - sx);
3866 const float dy = fmaxf(1e-6f, y4 - sy);
3867 spline->M4[1] = sigmoid_slope * dx / dy; // exponent q, matches latitude slope at the node
3868 spline->M3[1] = dy / powf(dx, spline->M4[1]); // coefficient so it passes through white
3869 spline->M5[1] = 1.f; // always the slope-matched power curve
3870 }
3871 else if(p->highlights == DT_FILMIC_CURVE_POLY_3)
3872 {
3873 // 3rd order polynom - only mode in darktable 3.0.0
3874 double A1[ORDER_3 * ORDER_3] = { 1., 1., 1., 1., // position in 1
3875 Sl3, Sl2, Sl, 1., // position at shoulder node
3876 3. * Sl2, 2. * Sl, 1., 0., // first derivative at shoulder node
3877 6. * Sl, 2., 0., 0. }; // second derivative at shoulder node
3878
3879 double b1[ORDER_3] = { spline->y[4], spline->y[3], spline->M2[2], 0. };
3880
3881 gauss_solve(A1, b1, ORDER_3);
3882
3883 spline->M5[1] = 0.0f; // * x⁴
3884 spline->M4[1] = b1[0]; // * x³
3885 spline->M3[1] = b1[1]; // * x²
3886 spline->M2[1] = b1[2]; // * x¹
3887 spline->M1[1] = b1[3]; // * x⁰
3888 }
3889 else if(p->highlights == DT_FILMIC_CURVE_POLY_4)
3890 {
3891 // 4th order polynom
3892 double A1[ORDER_4 * ORDER_4] = { 1., 1., 1., 1., 1., // position in 1
3893 4., 3., 2., 1., 0., // first derivative in 1
3894 Sl4, Sl3, Sl2, Sl, 1., // position at shoulder node
3895 4. * Sl3, 3. * Sl2, 2. * Sl, 1., 0., // first derivative at shoulder node
3896 12. * Sl2, 6. * Sl, 2., 0., 0. }; // second derivative at shoulder node
3897
3898 double b1[ORDER_4] = { spline->y[4], 0., spline->y[3], spline->M2[2], 0. };
3899
3900 gauss_solve(A1, b1, ORDER_4);
3901
3902 spline->M5[1] = b1[0]; // * x⁴
3903 spline->M4[1] = b1[1]; // * x³
3904 spline->M3[1] = b1[2]; // * x²
3905 spline->M2[1] = b1[3]; // * x¹
3906 spline->M1[1] = b1[4]; // * x⁰
3907 }
3908 else
3909 {
3910 const float P1[2] = { white_log, white_display };
3911 const float P0[2] = { shoulder_log, shoulder_display };
3912 const float x = P1[0] - P0[0];
3913 const float y = P1[1] - P0[1];
3914 const float g = contrast;
3915 const float b = g / (2.f * y) + (sqrtf(sqf(x * g / y + 1.f) - 4.f) - 1.f) / (2.f * x);
3916 const float c = y / g * (b * sqf(x) + x) / (b * sqf(x) + x - (y / g));
3917 const float a = c * g;
3918 spline->M1[1] = a;
3919 spline->M2[1] = b;
3920 spline->M3[1] = c;
3921 spline->M4[1] = shoulder_display;
3922 }
3923 return clamping;
3924}
3925
3928{
3931
3932 // source and display greys
3933 float grey_source = 0.1845f, grey_display = 0.4638f;
3934 if(p->custom_grey)
3935 {
3936 // user set a custom value
3937 grey_source = p->grey_point_source / 100.0f; // in %
3938 grey_display = powf(p->grey_point_target / 100.0f, 1.0f / (p->output_power));
3939 }
3940 else
3941 {
3942 // use 18.45% grey and don't bother
3943 grey_source = 0.1845f; // in %
3944 grey_display = powf(0.1845f, 1.0f / (p->output_power));
3945 }
3946
3947 // source luminance - Used only in the log encoding
3948 const float white_source = p->white_point_source;
3949 const float black_source = p->black_point_source;
3950 const float dynamic_range = white_source - black_source;
3951
3952 // luminance after log encoding
3953 const float grey_log = fabsf(p->black_point_source) / dynamic_range;
3954
3955
3956 float contrast = p->contrast;
3957 if((p->spline_version < DT_FILMIC_SPLINE_VERSION_V3) && (contrast < grey_display / grey_log))
3958 {
3959 // We need grey_display - (contrast * grey_log) <= 0.0
3960 // this clamping is handled automatically for spline_version >= DT_FILMIC_SPLINE_VERSION_V3
3961 contrast = 1.0001f * grey_display / grey_log;
3962 }
3963
3964 // commit
3965 d->dynamic_range = dynamic_range;
3966 d->black_source = black_source;
3967 d->grey_source = grey_source;
3968 d->output_power = p->output_power;
3969 d->contrast = contrast;
3970 d->version = p->version;
3971 d->spline_version = p->spline_version;
3972 d->preserve_color = p->preserve_color;
3973 d->high_quality_reconstruction = p->high_quality_reconstruction;
3974 d->noise_level = p->noise_level;
3975 d->noise_distribution = (dt_noise_distribution_t)p->noise_distribution;
3976
3977 // See _filmic_get_output_profile(): soft-proofing only applies to the interactive
3978 // full pipe. Zero the profile identity when inactive so toggling other pipe types
3979 // or preferences unrelated to soft-proofing never perturbs the hash.
3980 d->softproof_mode = (pipe->type == DT_DEV_PIXELPIPE_FULL) ? darktable.color_profiles->mode : DT_PROFILE_NORMAL;
3981
3982 memset(d->softproof_filename, 0, sizeof(d->softproof_filename));
3983 if(d->softproof_mode != DT_PROFILE_NORMAL)
3984 {
3985 d->softproof_type = darktable.color_profiles->softproof_type;
3986 g_strlcpy(d->softproof_filename, darktable.color_profiles->softproof_filename,
3987 sizeof(d->softproof_filename));
3988 }
3989 else
3990 {
3991 d->softproof_type = DT_COLORSPACE_NONE;
3992 }
3993
3994 // compute the curves and their LUT
3996
3997 if(p->version >= DT_FILMIC_COLORSCIENCE_V4)
3998 d->saturation = p->saturation / 100.0f;
3999 else
4000 d->saturation = (2.0f * p->saturation / 100.0f + 1.0f);
4001
4002 // AgX color science : the saturation slider is a bipolar character/fidelity axis.
4003 // The slider recovers HUE ONLY. Chroma is never user-controlled : it is entirely
4004 // the bracket's own outset recovery + clamp (valid diffuse colors — skin tones,
4005 // product colors — reach the output <= input chroma clamp, so they keep their
4006 // saturation ; strongly compressed colors bleach smoothly). Mixing any original
4007 // chroma back on top of that kinks highlight gradients where the recovered value
4008 // meets the bracket's roll-off, so the chroma-recovery term (former d->agx_beta)
4009 // was removed (2026-07). Bleaching valid midtone colors is a racial-bias issue,
4010 // handled by the bracket, not by this slider — see doc/filmic-agx.md.
4011 //
4012 // Slider a in [-1, +1] : beta_hue = (a + 1) / 2 : 0 at -100% (full AgX drift, the
4013 // film character), 0.5 at 0% (half the drift removed), 1 at +100% (original hue
4014 // restored, chroma still bracket-bleached).
4015 const float agx_axis = CLAMPF(p->saturation / 100.0f, -1.f, 1.f);
4016 d->agx_beta_hue = 0.5f * (agx_axis + 1.f);
4017
4018 d->sigma_toe = powf(d->spline.latitude_min / 3.0f, 2.0f);
4019 d->sigma_shoulder = powf((1.0f - d->spline.latitude_max) / 3.0f, 2.0f);
4020
4021 d->reconstruct_threshold = powf(2.0f, white_source + p->reconstruct_threshold) * grey_source;
4022 d->reconstruct_feather = exp2f(12.f / p->reconstruct_feather);
4023
4024 // offset and rescale user param to alpha blending so 0 -> 50% and 1 -> 100%
4025 d->normalize = d->reconstruct_feather / d->reconstruct_threshold;
4026 d->reconstruct_structure_vs_texture = (p->reconstruct_structure_vs_texture / 100.0f + 1.f) / 2.f;
4027 d->reconstruct_bloom_vs_details = (p->reconstruct_bloom_vs_details / 100.0f + 1.f) / 2.f;
4028 d->reconstruct_grey_vs_color = (p->reconstruct_grey_vs_color / 100.0f + 1.f) / 2.f;
4029}
4030
4031void gui_focus(struct dt_iop_module_t *self, gboolean in)
4032{
4034
4035 if(!in)
4036 {
4037 // lost focus - hide the mask
4038 gint mask_was_shown = g->show_mask;
4039 g->show_mask = FALSE;
4040 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_highlight_mask), FALSE);
4041 if(mask_was_shown) dt_dev_pixelpipe_update_history_main(self->dev);
4042 }
4043}
4044
4046 const dt_dev_pixelpipe_iop_t *piece)
4047{
4048 // piece->data carries the soft-proof profile identity (see commit_params()), which
4049 // is runtime GUI/preference state, not part of module->params: fold it into the hash
4050 // so toggling/switching soft-proofing re-keys the pipeline cache instead of leaving
4051 // a stale non-proofed render behind.
4052 return TRUE;
4053}
4054
4060
4062{
4063 dt_free_align(piece->data);
4064 piece->data = NULL;
4065}
4066
4068{
4071 float toe = 0.0f;
4072 float shoulder = 0.0f;
4073 filmic_v3_legacy_to_direct(p, &toe, &shoulder);
4074
4076 dt_bauhaus_slider_set(g->toe, toe);
4077 dt_bauhaus_slider_set(g->shoulder, shoulder);
4079}
4080
4081static void toe_shoulder_callback(GtkWidget *slider, gpointer user_data)
4082{
4083 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4084 if(dt_gui_widgets_suppressed()) return;
4085
4089 &p->latitude, &p->balance);
4090 gui_changed(self, slider, NULL);
4092}
4093
4095{
4098
4100
4101 g->show_mask = FALSE;
4102 g->gui_mode = dt_conf_get_int("plugins/darkroom/filmicrgb/graph_view");
4103 g->gui_show_labels = dt_conf_get_int("plugins/darkroom/filmicrgb/graph_show_labels");
4104 g->gui_hover = FALSE;
4105 g->gui_sizes_inited = FALSE;
4106
4107 // fetch last view in dartablerc
4108
4109 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->auto_hardness), p->auto_hardness);
4110 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->custom_grey), p->custom_grey);
4112
4113 gui_changed(self, NULL, NULL);
4114}
4115
4117{
4118 dt_iop_filmicrgb_params_t *d = module->default_params;
4119
4120 d->black_point_source = module->so->get_f("black_point_source")->Float.Default;
4121 d->white_point_source = module->so->get_f("white_point_source")->Float.Default;
4122 d->output_power = module->so->get_f("output_power")->Float.Default;
4123
4124 // Scene-referred dynamic-range defaults apply to any raw-colorimetry image (mosaiced raw
4125 // OR already-demosaiced sraw/linear DNG), so gate on needs_rawprepare rather than the
4126 // mosaic-centric DT_IMAGE_RAW flag, otherwise an sraw/linear DNG silently gets the
4127 // display-referred defaults.
4129 {
4130 // For scene-referred workflow, auto-enable and adjust based on exposure
4131 // TODO: fetch actual exposure in module, don't assume 1.
4132 const float exposure = 0.7f - dt_image_get_exposure_bias(&module->dev->image_storage);
4133
4134 // As global exposure increases, white exposure increases faster than black
4135 // this is probably because raw black/white points offsets the lower bound of the dynamic range to 0
4136 // so exposure compensation actually increases the dynamic range too (stretches only white).
4137 d->white_point_source = exposure + 2.45f;
4138 d->black_point_source = d->white_point_source - 12.f; // 12 EV of dynamic range is a good default for modern cameras
4139 d->output_power = logf(d->grey_point_target / 100.0f)
4140 / logf(-d->black_point_source / (d->white_point_source - d->black_point_source));
4141
4142 module->workflow_enabled = TRUE;
4143 }
4144 dt_iop_fmt_log(module, "reload_defaults: class=%s needs_rawprepare=%d -> workflow_enabled=%d white=%.3f black=%.3f",
4147 d->white_point_source, d->black_point_source);
4148}
4149
4150
4152{
4153 const int program = 22; // filmic.cl, from programs.conf
4156
4157 module->data = gd;
4158 gd->kernel_filmic_rgb_split = dt_opencl_create_kernel(program, "filmicrgb_split");
4159 gd->kernel_filmic_rgb_chroma = dt_opencl_create_kernel(program, "filmicrgb_chroma");
4160 gd->kernel_filmic_mask = dt_opencl_create_kernel(program, "filmic_mask_clipped_pixels");
4161 gd->kernel_filmic_show_mask = dt_opencl_create_kernel(program, "filmic_show_mask");
4162 gd->kernel_filmic_inpaint_noise = dt_opencl_create_kernel(program, "filmic_inpaint_noise");
4163 gd->kernel_filmic_init_reconstruct = dt_opencl_create_kernel(program, "init_reconstruct");
4164 gd->kernel_filmic_wavelets_reconstruct = dt_opencl_create_kernel(program, "wavelets_reconstruct");
4165 gd->kernel_filmic_compute_ratios = dt_opencl_create_kernel(program, "compute_ratios");
4166 gd->kernel_filmic_restore_ratios = dt_opencl_create_kernel(program, "restore_ratios");
4167
4168 const int wavelets = 35; // bspline.cl, from programs.conf
4169 gd->kernel_filmic_bspline_horizontal = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_horizontal");
4170 gd->kernel_filmic_bspline_vertical = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_vertical");
4171 gd->kernel_filmic_bspline_horizontal_local = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_horizontal_local");
4172 gd->kernel_filmic_bspline_vertical_local = dt_opencl_create_kernel(wavelets, "blur_2D_Bspline_vertical_local");
4173 gd->kernel_filmic_wavelets_detail = dt_opencl_create_kernel(wavelets, "wavelets_detail_level");
4174}
4175
4195
4196
4198{
4200}
4201
4202#define LOGBASE 20.f
4203
4204static inline void dt_cairo_draw_arrow(cairo_t *cr, double origin_x, double origin_y, double destination_x,
4205 double destination_y, gboolean show_head)
4206{
4207 cairo_move_to(cr, origin_x, origin_y);
4208 cairo_line_to(cr, destination_x, destination_y);
4209 cairo_stroke(cr);
4210
4211 if(show_head)
4212 {
4213 // arrow head is hard set to 45° - convert to radians
4214 const float angle_arrow = 45.f / 360.f * M_PI;
4215 const float angle_trunk = atan2f((destination_y - origin_y), (destination_x - origin_x));
4216 const float radius = DT_PIXEL_APPLY_DPI(3);
4217
4218 const float x_1 = destination_x + radius / sinf(angle_arrow + angle_trunk);
4219 const float y_1 = destination_y + radius / cosf(angle_arrow + angle_trunk);
4220
4221 const float x_2 = destination_x - radius / sinf(-angle_arrow + angle_trunk);
4222 const float y_2 = destination_y - radius / cosf(-angle_arrow + angle_trunk);
4223
4224 cairo_move_to(cr, x_1, y_1);
4225 cairo_line_to(cr, destination_x, destination_y);
4226 cairo_line_to(cr, x_2, y_2);
4227 cairo_stroke(cr);
4228 }
4229}
4230
4233{
4234 if(!g->gui_sizes_inited) return;
4235
4236 cairo_save(cr);
4237
4238 GdkRGBA color;
4239
4240 // copy color
4241 color.red = darktable.bauhaus->graph_fg.red;
4242 color.green = darktable.bauhaus->graph_fg.green;
4243 color.blue = darktable.bauhaus->graph_fg.blue;
4244 color.alpha = darktable.bauhaus->graph_fg.alpha;
4245
4246 if(button->mouse_hover)
4247 {
4248 // use graph_fg color as-is if mouse hover
4249 cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha);
4250 }
4251 else
4252 {
4253 // use graph_fg color with transparency else
4254 cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha * 0.5);
4255 }
4256
4257 cairo_rectangle(cr, button->left, button->top, button->w - DT_PIXEL_APPLY_DPI(0.5),
4258 button->h - DT_PIXEL_APPLY_DPI(0.5));
4259 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
4260 cairo_stroke(cr);
4261 cairo_translate(cr, button->left + button->w / 2. - DT_PIXEL_APPLY_DPI(0.25),
4262 button->top + button->h / 2. - DT_PIXEL_APPLY_DPI(0.25));
4263
4264 const float scale = 0.85;
4265 cairo_scale(cr, scale, scale);
4266 button->icon(cr, -scale * button->w / 2., -scale * button->h / 2., scale * button->w, scale * button->h, 0, NULL);
4267 cairo_restore(cr);
4268}
4269
4270
4271static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
4272{
4273 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4276 gboolean contrast_clamped = dt_iop_filmic_rgb_compute_spline(p, &g->spline);
4277
4278 // Cache the graph objects to avoid recomputing all the view at each redraw
4279 gtk_widget_get_allocation(widget, &g->allocation);
4280
4281 cairo_surface_t *cst =
4282 dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, g->allocation.width, g->allocation.height);
4283 PangoFontDescription *desc =
4284 pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
4285 cairo_t *cr = cairo_create(cst);
4286 PangoLayout *layout = pango_cairo_create_layout(cr);
4287
4288 pango_layout_set_font_description(layout, desc);
4290 g->context = gtk_widget_get_style_context(widget);
4291
4292 char text[256];
4293
4294 // reduce a bit the font size
4295 const gint font_size = pango_font_description_get_size(desc);
4296 pango_font_description_set_size(desc, 0.95 * font_size);
4297 pango_layout_set_font_description(layout, desc);
4298
4299 // Get the text line height for spacing
4300 g_strlcpy(text, "X", sizeof(text));
4301 pango_layout_set_text(layout, text, -1);
4302 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4303 g->line_height = g->ink.height;
4304
4305 // Get the width of a minus sign for legend labels spacing
4306 g_strlcpy(text, "-", sizeof(text));
4307 pango_layout_set_text(layout, text, -1);
4308 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4309 g->sign_width = g->ink.width / 2.0;
4310
4311 // Get the width of a zero for legend labels spacing
4312 g_strlcpy(text, "0", sizeof(text));
4313 pango_layout_set_text(layout, text, -1);
4314 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4315 g->zero_width = g->ink.width;
4316
4317 // Set the sizes, margins and paddings
4318 g->inset = INNER_PADDING;
4319
4320 float margin_left;
4321 float margin_bottom;
4322 if(g->gui_show_labels)
4323 {
4324 // leave room for labels
4325 margin_left = 3. * g->zero_width + 2. * g->inset;
4326 margin_bottom = 2. * g->line_height + 4. * g->inset;
4327 }
4328 else
4329 {
4330 margin_left = g->inset;
4331 margin_bottom = g->inset;
4332 }
4333
4334 const float margin_top = 2. * g->line_height + g->inset;
4335 const float margin_right = darktable.bauhaus->quad_width + 2. * g->inset;
4336
4337 g->graph_width = g->allocation.width - margin_right - margin_left; // align the right border on sliders
4338 g->graph_height = g->allocation.height - margin_bottom - margin_top; // give room to nodes
4339
4340 gtk_render_background(g->context, cr, 0, 0, g->allocation.width, g->allocation.height);
4341
4342 // Init icons bounds and cache them for mouse events
4343 for(int i = 0; i < DT_FILMIC_GUI_BUTTON_LAST; i++)
4344 {
4345 // put the buttons in the right margin and increment vertical position
4346 g->buttons[i].right = g->allocation.width;
4347 g->buttons[i].left = g->buttons[i].right - darktable.bauhaus->quad_width;
4348 g->buttons[i].top = margin_top + i * (g->inset + darktable.bauhaus->quad_width);
4349 g->buttons[i].bottom = g->buttons[i].top + darktable.bauhaus->quad_width;
4350 g->buttons[i].w = g->buttons[i].right - g->buttons[i].left;
4351 g->buttons[i].h = g->buttons[i].bottom - g->buttons[i].top;
4352 g->buttons[i].state = GTK_STATE_FLAG_NORMAL;
4353 }
4354
4355 g->gui_sizes_inited = TRUE;
4356
4357 g->buttons[0].icon = dtgtk_cairo_paint_refresh;
4358 g->buttons[1].icon = dtgtk_cairo_paint_text_label;
4359
4360 if(g->gui_hover)
4361 {
4362 for(int i = 0; i < DT_FILMIC_GUI_BUTTON_LAST; i++) filmic_gui_draw_icon(cr, &g->buttons[i], g);
4363 }
4364
4365 const float grey = p->grey_point_source / 100.f;
4366 const float DR = p->white_point_source - p->black_point_source;
4367
4368 // set the graph as the origin of the coordinates
4369 cairo_translate(cr, margin_left, margin_top);
4370
4371 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
4372
4373 // write the graph legend at GUI default size
4374 pango_font_description_set_size(desc, font_size);
4375 pango_layout_set_font_description(layout, desc);
4376 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
4377 g_strlcpy(text, _("look only"), sizeof(text));
4378 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
4379 g_strlcpy(text, _("look + mapping (lin)"), sizeof(text));
4380 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4381 g_strlcpy(text, _("look + mapping (log)"), sizeof(text));
4382 else if(g->gui_mode == DT_FILMIC_GUI_RANGES)
4383 g_strlcpy(text, _("dynamic range mapping"), sizeof(text));
4384
4385 pango_layout_set_text(layout, text, -1);
4386 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4387
4388 // legend background
4390 cairo_rectangle(cr, g->allocation.width - margin_left - g->ink.width - g->ink.x - 2. * g->inset,
4391 -g->line_height - g->inset - 0.5 * g->ink.height - g->ink.y - g->inset,
4392 g->ink.width + 3. * g->inset, g->ink.height + 2. * g->inset);
4393 cairo_fill(cr);
4394
4395 // legend text
4397 cairo_move_to(cr, g->allocation.width - margin_left - g->ink.width - g->ink.x - g->inset,
4398 -g->line_height - g->inset - 0.5 * g->ink.height - g->ink.y);
4399 pango_cairo_show_layout(cr, layout);
4400 cairo_stroke(cr);
4401
4402 // reduce font size for the rest of the graph
4403 pango_font_description_set_size(desc, 0.95 * font_size);
4404 pango_layout_set_font_description(layout, desc);
4405
4406 if(g->gui_mode != DT_FILMIC_GUI_RANGES)
4407 {
4408 // Draw graph background then border
4409 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(0.5));
4410 cairo_rectangle(cr, 0, 0, g->graph_width, g->graph_height);
4412 cairo_fill_preserve(cr);
4414 cairo_stroke(cr);
4415
4416 // draw grid
4417 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(0.5));
4419
4420 // we need to tweak the coordinates system to match dt_draw_grid expectations
4421 cairo_save(cr);
4422 cairo_scale(cr, 1., -1.);
4423 cairo_translate(cr, 0., -g->graph_height);
4424
4425 if(g->gui_mode == DT_FILMIC_GUI_LOOK || g->gui_mode == DT_FILMIC_GUI_BASECURVE)
4426 dt_draw_grid(cr, 4, 0, 0, g->graph_width, g->graph_height);
4427 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4428 dt_draw_loglog_grid(cr, 4, 0, 0, g->graph_width, g->graph_height, LOGBASE);
4429
4430 // reset coordinates
4431 cairo_restore(cr);
4432
4433 // draw identity line
4434 cairo_move_to(cr, 0, g->graph_height);
4435 cairo_line_to(cr, g->graph_width, 0);
4436 cairo_stroke(cr);
4437
4438 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
4439
4440 // Draw the saturation curve
4441 const float saturation = (2.0f * p->saturation / 100.0f + 1.0f);
4442 const float sigma_toe = powf(g->spline.latitude_min / 3.0f, 2.0f);
4443 const float sigma_shoulder = powf((1.0f - g->spline.latitude_max) / 3.0f, 2.0f);
4444
4445 cairo_set_source_rgb(cr, .5, .5, .5);
4446
4447 // prevent graph overflowing
4448 cairo_save(cr);
4449 cairo_rectangle(cr, -DT_PIXEL_APPLY_DPI(2.), -DT_PIXEL_APPLY_DPI(2.),
4450 g->graph_width + 2. * DT_PIXEL_APPLY_DPI(2.), g->graph_height + 2. * DT_PIXEL_APPLY_DPI(2.));
4451 cairo_clip(cr);
4452
4453 if(p->version == DT_FILMIC_COLORSCIENCE_V1)
4454 {
4455 cairo_move_to(cr, 0,
4456 g->graph_height * (1.0 - filmic_desaturate_v1(0.0f, sigma_toe, sigma_shoulder, saturation)));
4457 for(int k = 1; k < 256; k++)
4458 {
4459 float x = k / 255.0;
4460 const float y = filmic_desaturate_v1(x, sigma_toe, sigma_shoulder, saturation);
4461
4462 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
4463 x = exp_tonemapping_v2(x, grey, p->black_point_source, DR);
4464 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4465 x = dt_log_scale_axis(exp_tonemapping_v2(x, grey, p->black_point_source, DR), LOGBASE);
4466
4467 cairo_line_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
4468 }
4469 }
4470 else if(p->version == DT_FILMIC_COLORSCIENCE_V2 || p->version == DT_FILMIC_COLORSCIENCE_V3)
4471 {
4472 cairo_move_to(cr, 0,
4473 g->graph_height * (1.0 - filmic_desaturate_v2(0.0f, sigma_toe, sigma_shoulder, saturation)));
4474 for(int k = 1; k < 256; k++)
4475 {
4476 float x = k / 255.0;
4477 const float y = filmic_desaturate_v2(x, sigma_toe, sigma_shoulder, saturation);
4478
4479 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
4480 x = exp_tonemapping_v2(x, grey, p->black_point_source, DR);
4481 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4482 x = dt_log_scale_axis(exp_tonemapping_v2(x, grey, p->black_point_source, DR), LOGBASE);
4483
4484 cairo_line_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
4485 }
4486 }
4487 cairo_stroke(cr);
4488
4489 // draw the tone curve
4490 float x_start = 0.f;
4491 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4492 x_start = log_tonemapping(x_start, grey, p->black_point_source, DR);
4493
4494 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG) x_start = dt_log_scale_axis(x_start, LOGBASE);
4495
4496 float y_start = clamp_simd(filmic_spline(x_start, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
4497 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type));
4498
4499 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
4500 y_start = powf(y_start, p->output_power);
4501 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4502 y_start = dt_log_scale_axis(powf(y_start, p->output_power), LOGBASE);
4503
4504 cairo_move_to(cr, 0, g->graph_height * (1.0 - y_start));
4505
4506 for(int k = 1; k < 256; k++)
4507 {
4508 // k / 255 step defines a linearly scaled space. This might produce large gaps in lowlights when using log
4509 // GUI scaling so we non-linearly rescale that step to get more points in lowlights
4510 float x = powf(k / 255.0f, 2.4f);
4511 float value = x;
4512
4513 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4514 value = log_tonemapping(x, grey, p->black_point_source, DR);
4515
4517
4518 float y = filmic_spline(value, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4, g->spline.M5,
4519 g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
4520
4521 // curve is drawn in orange when above maximum
4522 // or below minimum.
4523 // we use a small margin in the comparison
4524 // to avoid drawing curve in orange when it
4525 // is right above or right below the limit
4526 // due to floating point errors
4527 const float margin = 1E-5;
4528 if(y > g->spline.y[4] + margin)
4529 {
4530 y = fminf(y, 1.0f);
4531 cairo_set_source_rgb(cr, 0.75, .5, 0.);
4532 }
4533 else if(y < g->spline.y[0] - margin)
4534 {
4535 y = fmaxf(y, 0.f);
4536 cairo_set_source_rgb(cr, 0.75, .5, 0.);
4537 }
4538 else
4539 {
4541 }
4542
4543 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
4544 y = powf(y, p->output_power);
4545 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4546 y = dt_log_scale_axis(powf(y, p->output_power), LOGBASE);
4547
4548 cairo_line_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
4549 cairo_stroke(cr);
4550 cairo_move_to(cr, x * g->graph_width, g->graph_height * (1.0 - y));
4551 }
4552
4553 cairo_restore(cr);
4554
4555 // draw nodes
4556
4557 // special case for the grey node
4558 cairo_save(cr);
4559 cairo_rectangle(cr, -DT_PIXEL_APPLY_DPI(4.), -DT_PIXEL_APPLY_DPI(4.),
4560 g->graph_width + 2. * DT_PIXEL_APPLY_DPI(4.), g->graph_height + 2. * DT_PIXEL_APPLY_DPI(4.));
4561 cairo_clip(cr);
4562 float x_grey = g->spline.x[2];
4563 float y_grey = g->spline.y[2];
4564
4565 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
4566 {
4567 x_grey = exp_tonemapping_v2(x_grey, grey, p->black_point_source, DR);
4568 y_grey = powf(y_grey, p->output_power);
4569 }
4570 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4571 {
4572 x_grey = dt_log_scale_axis(exp_tonemapping_v2(x_grey, grey, p->black_point_source, DR), LOGBASE);
4573 y_grey = dt_log_scale_axis(powf(y_grey, p->output_power), LOGBASE);
4574 }
4575
4576 cairo_set_source_rgb(cr, 0.75, 0.5, 0.0);
4577 cairo_arc(cr, x_grey * g->graph_width, (1.0 - y_grey) * g->graph_height, DT_PIXEL_APPLY_DPI(6), 0,
4578 2. * M_PI);
4579 cairo_fill(cr);
4580 cairo_stroke(cr);
4581
4582 // latitude nodes
4583 float x_black = 0.f;
4584 float y_black = 0.f;
4585
4586 float x_white = 1.f;
4587 float y_white = 1.f;
4588
4589 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);
4590 const float central_slope_angle = atanf(central_slope) + M_PI / 2.0f;
4592 for(int k = 0; k < 5; k++)
4593 {
4594 if(k != 2) // k == 2 : grey point, already processed above
4595 {
4596 float x = g->spline.x[k];
4597 float y = g->spline.y[k];
4598 const float ymin = g->spline.y[0];
4599 const float ymax = g->spline.y[4];
4600 // we multiply SAFETY_MARGIN by 1.1f to avoid possible false negatives due to float errors
4601 const float y_margin = SAFETY_MARGIN * 1.1f * (ymax - ymin);
4602 gboolean red = (((k == 1) && (y - ymin <= y_margin))
4603 || ((k == 3) && (ymax - y <= y_margin)));
4604 float start_angle = 0.0f;
4605 float end_angle = 2.f * M_PI;
4606 // if contrast is clamped, show it on GUI with half circles
4607 // for points 1 and 3
4608 if(contrast_clamped)
4609 {
4610 if(k == 1)
4611 {
4612 start_angle = central_slope_angle + M_PI;
4613 end_angle = central_slope_angle;
4614 }
4615 if(k == 3)
4616 {
4617 start_angle = central_slope_angle;
4618 end_angle = start_angle + M_PI;
4619 }
4620 }
4621
4622 if(g->gui_mode == DT_FILMIC_GUI_BASECURVE)
4623 {
4624 x = exp_tonemapping_v2(x, grey, p->black_point_source, DR);
4625 y = powf(y, p->output_power);
4626 }
4627 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4628 {
4629 x = dt_log_scale_axis(exp_tonemapping_v2(x, grey, p->black_point_source, DR), LOGBASE);
4630 y = dt_log_scale_axis(powf(y, p->output_power), LOGBASE);
4631 }
4632
4633 // save the bounds of the curve to mark the axis graduation
4634 if(k == 0) // black point
4635 {
4636 x_black = x;
4637 y_black = y;
4638 }
4639 else if(k == 4) // white point
4640 {
4641 x_white = x;
4642 y_white = y;
4643 }
4644
4645 if(red) cairo_set_source_rgb(cr, 0.8, 0.35, 0.35);
4646
4647 // draw bullet
4648 cairo_arc(cr, x * g->graph_width, (1.0 - y) * g->graph_height, DT_PIXEL_APPLY_DPI(4), start_angle, end_angle);
4649 cairo_fill(cr);
4650 cairo_stroke(cr);
4651
4652 // reset color for next points
4653 if(red) set_color(cr, darktable.bauhaus->graph_fg);
4654 }
4655 }
4656 cairo_restore(cr);
4657
4658 if(g->gui_show_labels)
4659 {
4660 // position of the upper bound of x axis labels
4661 const float x_legend_top = g->graph_height + 0.5 * g->line_height;
4662
4663 // mark the y axis graduation at grey spot
4665 snprintf(text, sizeof(text), "%.0f", p->grey_point_target);
4666 pango_layout_set_text(layout, text, -1);
4667 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4668 cairo_move_to(cr, -2. * g->inset - g->ink.width - g->ink.x,
4669 (1.0 - y_grey) * g->graph_height - 0.5 * g->ink.height - g->ink.y);
4670 pango_cairo_show_layout(cr, layout);
4671 cairo_stroke(cr);
4672
4673 // mark the x axis graduation at grey spot
4675 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
4676 snprintf(text, sizeof(text), "%+.1f", 0.f);
4677 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4678 snprintf(text, sizeof(text), "%.0f", p->grey_point_source);
4679
4680 pango_layout_set_text(layout, text, -1);
4681 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4682 cairo_move_to(cr, x_grey * g->graph_width - 0.5 * g->ink.width - g->ink.x, x_legend_top);
4683 pango_cairo_show_layout(cr, layout);
4684 cairo_stroke(cr);
4685
4686 // mark the y axis graduation at black spot
4688 snprintf(text, sizeof(text), "%.0f", p->black_point_target);
4689 pango_layout_set_text(layout, text, -1);
4690 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4691 cairo_move_to(cr, -2. * g->inset - g->ink.width - g->ink.x,
4692 (1.0 - y_black) * g->graph_height - 0.5 * g->ink.height - g->ink.y);
4693 pango_cairo_show_layout(cr, layout);
4694 cairo_stroke(cr);
4695
4696 // mark the y axis graduation at black spot
4698 snprintf(text, sizeof(text), "%.0f", p->white_point_target);
4699 pango_layout_set_text(layout, text, -1);
4700 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4701 cairo_move_to(cr, -2. * g->inset - g->ink.width - g->ink.x,
4702 (1.0 - y_white) * g->graph_height - 0.5 * g->ink.height - g->ink.y);
4703 pango_cairo_show_layout(cr, layout);
4704 cairo_stroke(cr);
4705
4706 // mark the x axis graduation at black spot
4708 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
4709 snprintf(text, sizeof(text), "%+.1f", p->black_point_source);
4710 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4711 snprintf(text, sizeof(text), "%.0f", exp2f(p->black_point_source) * p->grey_point_source);
4712
4713 pango_layout_set_text(layout, text, -1);
4714 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4715 cairo_move_to(cr, x_black * g->graph_width - 0.5 * g->ink.width - g->ink.x, x_legend_top);
4716 pango_cairo_show_layout(cr, layout);
4717 cairo_stroke(cr);
4718
4719 // mark the x axis graduation at white spot
4721 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
4722 snprintf(text, sizeof(text), "%+.1f", p->white_point_source);
4723 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4724 {
4725 if(x_white > 1.f)
4726 snprintf(text, sizeof(text), "%.0f \342\206\222", 100.f); // this marks the bound of the graph, not the actual white
4727 else
4728 snprintf(text, sizeof(text), "%.0f", exp2f(p->white_point_source) * p->grey_point_source);
4729 }
4730
4731 pango_layout_set_text(layout, text, -1);
4732 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4733 cairo_move_to(cr,
4734 fminf(x_white, 1.f) * g->graph_width - 0.5 * g->ink.width - g->ink.x
4735 + 2. * (x_white > 1.f) * g->sign_width,
4736 x_legend_top);
4737 pango_cairo_show_layout(cr, layout);
4738 cairo_stroke(cr);
4739
4740 // handle the case where white > 100 %, so the node is out of the graph.
4741 // we still want to display the value to get a hint.
4743 if((g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG) && (x_white > 1.f))
4744 {
4745 // set to italic font
4746 PangoStyle backup = pango_font_description_get_style(desc);
4747 pango_font_description_set_style(desc, PANGO_STYLE_ITALIC);
4748 pango_layout_set_font_description(layout, desc);
4749
4750 snprintf(text, sizeof(text), _("(%.0f %%)"), exp2f(p->white_point_source) * p->grey_point_source);
4751 pango_layout_set_text(layout, text, -1);
4752 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4753 cairo_move_to(cr, g->allocation.width - g->ink.width - g->ink.x - margin_left,
4754 g->graph_height + 3. * g->inset + g->line_height - g->ink.y);
4755 pango_cairo_show_layout(cr, layout);
4756 cairo_stroke(cr);
4757
4758 // restore font
4759 pango_font_description_set_style(desc, backup);
4760 pango_layout_set_font_description(layout, desc);
4761 }
4762
4763 // mark the y axis legend
4765 /* xgettext:no-c-format */
4766 g_strlcpy(text, _("% display"), sizeof(text));
4767 pango_layout_set_text(layout, text, -1);
4768 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4769 cairo_move_to(cr, -2. * g->inset - g->zero_width - g->ink.x,
4770 -g->line_height - g->inset - 0.5 * g->ink.height - g->ink.y);
4771 pango_cairo_show_layout(cr, layout);
4772 cairo_stroke(cr);
4773
4774
4775 // mark the x axis legend
4777 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
4778 g_strlcpy(text, _("EV scene"), sizeof(text));
4779 else if(g->gui_mode == DT_FILMIC_GUI_BASECURVE || g->gui_mode == DT_FILMIC_GUI_BASECURVE_LOG)
4780 {
4781 /* xgettext:no-c-format */
4782 g_strlcpy(text, _("% camera"), sizeof(text));
4783 }
4784 pango_layout_set_text(layout, text, -1);
4785 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4786 cairo_move_to(cr, 0.5 * g->graph_width - 0.5 * g->ink.width - g->ink.x,
4787 g->graph_height + 3. * g->inset + g->line_height - g->ink.y);
4788 pango_cairo_show_layout(cr, layout);
4789 cairo_stroke(cr);
4790 }
4791 }
4792 else
4793 {
4794 // mode ranges
4795 cairo_identity_matrix(cr); // reset coordinates
4796
4797 // draw the dynamic range of display
4798 // if white = 100%, assume -11.69 EV because of uint8 output + sRGB OETF.
4799 // for uint10 output, white should be set to 400%, so anything above 100% increases DR
4800 // FIXME : if darktable becomes HDR-10bits compatible (for output), this needs to be updated
4801 const float display_DR = 12.f + log2f(p->white_point_target / 100.f);
4802
4803 const float y_display = g->allocation.height / 3.f + g->line_height;
4804 const float y_scene = 2. * g->allocation.height / 3.f + g->line_height;
4805
4806 const float display_top = y_display - g->line_height / 2;
4807 const float display_bottom = display_top + g->line_height;
4808
4809 const float scene_top = y_scene - g->line_height / 2;
4810 const float scene_bottom = scene_top + g->line_height;
4811
4812 float column_left;
4813
4814 if(g->gui_show_labels)
4815 {
4816 // labels
4818 g_strlcpy(text, _("display"), sizeof(text));
4819 pango_layout_set_text(layout, text, -1);
4820 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4821 cairo_move_to(cr, 0., y_display - 0.5 * g->ink.height - g->ink.y);
4822 pango_cairo_show_layout(cr, layout);
4823 cairo_stroke(cr);
4824 const float display_label_width = g->ink.width;
4825
4826 // axis legend
4827 g_strlcpy(text, _("(%)"), sizeof(text));
4828 pango_layout_set_text(layout, text, -1);
4829 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4830 cairo_move_to(cr, 0.5 * display_label_width - 0.5 * g->ink.width - g->ink.x,
4831 display_top - 4. * g->inset - g->ink.height - g->ink.y);
4832 pango_cairo_show_layout(cr, layout);
4833 cairo_stroke(cr);
4834
4836 g_strlcpy(text, _("scene"), sizeof(text));
4837 pango_layout_set_text(layout, text, -1);
4838 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4839 cairo_move_to(cr, 0., y_scene - 0.5 * g->ink.height - g->ink.y);
4840 pango_cairo_show_layout(cr, layout);
4841 cairo_stroke(cr);
4842 const float scene_label_width = g->ink.width;
4843
4844 // axis legend
4845 g_strlcpy(text, _("(EV)"), sizeof(text));
4846 pango_layout_set_text(layout, text, -1);
4847 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
4848 cairo_move_to(cr, 0.5 * scene_label_width - 0.5 * g->ink.width - g->ink.x,
4849 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
4850 pango_cairo_show_layout(cr, layout);
4851 cairo_stroke(cr);
4852
4853 // arrow between labels
4854 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
4855 dt_cairo_draw_arrow(cr, fminf(scene_label_width, display_label_width) / 2.f, y_scene - g->line_height,
4856 fminf(scene_label_width, display_label_width) / 2.f,
4857 y_display + g->line_height + g->inset, TRUE);
4858
4859 column_left = fmaxf(display_label_width, scene_label_width) + g->inset;
4860 }
4861 else
4862 column_left = darktable.bauhaus->quad_width;
4863
4864 const float column_right = g->allocation.width - column_left - darktable.bauhaus->quad_width;
4865
4866 // compute dynamic ranges left and right to middle grey
4867 const float display_HL_EV = -log2f(p->grey_point_target / p->white_point_target); // compared to white EV
4868 const float display_LL_EV = display_DR - display_HL_EV; // compared to black EV
4869 const float display_real_black_EV
4870 = -fmaxf(log2f(p->black_point_target / p->grey_point_target),
4871 -11.685887601778058f + display_HL_EV - log2f(p->white_point_target / 100.f));
4872 const float scene_HL_EV = p->white_point_source; // compared to white EV
4873 const float scene_LL_EV = -p->black_point_source; // compared to black EV
4874
4875 // compute the max width needed to fit both dynamic ranges and derivate the unit size of a GUI EV
4876 const float max_DR = ceilf(fmaxf(display_HL_EV, scene_HL_EV)) + ceilf(fmaxf(display_LL_EV, scene_LL_EV));
4877 const float EV = (column_right) / max_DR;
4878
4879 // all greys are aligned vertically in GUI since they are the fulcrum of the transform
4880 // so, get their coordinates
4881 const float grey_EV = fmaxf(ceilf(display_HL_EV), ceilf(scene_HL_EV));
4882 const float grey_x = g->allocation.width - (grey_EV)*EV - darktable.bauhaus->quad_width;
4883
4884 // similarly, get black/white coordinates from grey point
4885 const float display_black_x = grey_x - display_real_black_EV * EV;
4886 const float display_DR_start_x = grey_x - display_LL_EV * EV;
4887 const float display_white_x = grey_x + display_HL_EV * EV;
4888
4889 const float scene_black_x = grey_x - scene_LL_EV * EV;
4890 const float scene_white_x = grey_x + scene_HL_EV * EV;
4891 const float scene_lat_bottom = grey_x + (g->spline.x[1] - g->spline.x[2]) * EV * DR;
4892 const float scene_lat_top = grey_x + (g->spline.x[3] - g->spline.x[2]) * EV * DR;
4893
4894 // show EV zones for display - zones are aligned on 0% and 100%
4895 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
4896
4897 // latitude bounds - show contrast expansion
4898
4899 // Compute usual filmic mapping
4900 float display_lat_bottom = filmic_spline(g->spline.latitude_min, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
4901 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
4902 display_lat_bottom = powf(fmaxf(display_lat_bottom, NORM_MIN), p->output_power); // clamp at -16 EV
4903
4904 // rescale output to log scale
4905 display_lat_bottom = log2f(display_lat_bottom/ (p->grey_point_target / 100.f));
4906
4907 // take clamping into account
4908 if(display_lat_bottom < 0.f) // clamp to - 8 EV (black)
4909 display_lat_bottom = fmaxf(display_lat_bottom, -display_real_black_EV);
4910 else if(display_lat_bottom > 0.f) // clamp to 0 EV (white)
4911 display_lat_bottom = fminf(display_lat_bottom, display_HL_EV);
4912
4913 // get destination coordinate
4914 display_lat_bottom = grey_x + display_lat_bottom * EV;
4915
4916 // Compute usual filmic mapping
4917 float display_lat_top = filmic_spline(g->spline.latitude_max, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
4918 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
4919 display_lat_top = powf(fmaxf(display_lat_top, NORM_MIN), p->output_power); // clamp at -16 EV
4920
4921 // rescale output to log scale
4922 display_lat_top = log2f(display_lat_top / (p->grey_point_target / 100.f));
4923
4924 // take clamping into account
4925 if(display_lat_top < 0.f) // clamp to - 8 EV (black)
4926 display_lat_top = fmaxf(display_lat_top, -display_real_black_EV);
4927 else if(display_lat_top > 0.f) // clamp to 0 EV (white)
4928 display_lat_top = fminf(display_lat_top, display_HL_EV);
4929
4930 // get destination coordinate and draw
4931 display_lat_top = grey_x + display_lat_top * EV;
4932
4933 cairo_move_to(cr, scene_lat_bottom, scene_top);
4934 cairo_line_to(cr, scene_lat_top, scene_top);
4935 cairo_line_to(cr, display_lat_top, display_bottom);
4936 cairo_line_to(cr, display_lat_bottom, display_bottom);
4937 cairo_line_to(cr, scene_lat_bottom, scene_top);
4939 cairo_fill(cr);
4940
4941 for(int i = 0; i < (int)ceilf(display_DR); i++)
4942 {
4943 // content
4944 const float shade = powf(exp2f(-11.f + (float)i), 1.f / 2.4f);
4945 cairo_set_source_rgb(cr, shade, shade, shade);
4946 cairo_rectangle(cr, display_DR_start_x + i * EV, display_top, EV, g->line_height);
4947 cairo_fill_preserve(cr);
4948
4949 // borders
4950 cairo_set_source_rgb(cr, 0.75, .5, 0.);
4951 cairo_stroke(cr);
4952 }
4953
4954 // middle grey display
4955 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
4956 cairo_move_to(cr, grey_x, display_bottom + 2. * g->inset);
4957 cairo_line_to(cr, grey_x, display_top - 2. * g->inset);
4958 cairo_stroke(cr);
4959
4960 // show EV zones for scene - zones are aligned on grey
4961
4962 for(int i = floorf(p->black_point_source); i < ceilf(p->white_point_source); i++)
4963 {
4964 // content
4965 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
4966 const float shade = powf(0.1845f * exp2f((float)i), 1.f / 2.4f);
4967 const float x_temp = grey_x + i * EV;
4968 cairo_set_source_rgb(cr, shade, shade, shade);
4969 cairo_rectangle(cr, x_temp, scene_top, EV, g->line_height);
4970 cairo_fill_preserve(cr);
4971
4972 // borders
4973 cairo_set_source_rgb(cr, 0.75, .5, 0.);
4974 cairo_stroke(cr);
4975
4976 // arrows
4977 if(i == 0)
4978 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
4979 else
4980 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
4981
4982 if((float)i > p->black_point_source && (float)i < p->white_point_source)
4983 {
4984 // Compute usual filmic mapping
4985 const float normal_value = ((float)i - p->black_point_source) / DR;
4986 float y_temp = filmic_spline(normal_value, g->spline.M1, g->spline.M2, g->spline.M3, g->spline.M4,
4987 g->spline.M5, g->spline.latitude_min, g->spline.latitude_max, g->spline.type);
4988 y_temp = powf(fmaxf(y_temp, NORM_MIN), p->output_power); // clamp at -16 EV
4989
4990 // rescale output to log scale
4991 y_temp = log2f(y_temp / (p->grey_point_target / 100.f));
4992
4993 // take clamping into account
4994 if(y_temp < 0.f) // clamp to - 8 EV (black)
4995 y_temp = fmaxf(y_temp, -display_real_black_EV);
4996 else if(y_temp > 0.f) // clamp to 0 EV (white)
4997 y_temp = fminf(y_temp, display_HL_EV);
4998
4999 // get destination coordinate and draw
5000 y_temp = grey_x + y_temp * EV;
5001 dt_cairo_draw_arrow(cr, x_temp, scene_top, y_temp, display_bottom, FALSE);
5002 }
5003 }
5004
5005 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
5006
5007 // arrows for black and white
5008 float x_temp = grey_x + p->black_point_source * EV;
5009 float y_temp = grey_x - display_real_black_EV * EV;
5010 dt_cairo_draw_arrow(cr, x_temp, scene_top, y_temp, display_bottom, FALSE);
5011
5012 x_temp = grey_x + p->white_point_source * EV;
5013 y_temp = grey_x + display_HL_EV * EV;
5014 dt_cairo_draw_arrow(cr, x_temp, scene_top, y_temp, display_bottom, FALSE);
5015
5016 // draw white - grey - black ticks
5017
5018 // black display
5019 cairo_move_to(cr, display_black_x, display_bottom);
5020 cairo_line_to(cr, display_black_x, display_top - 2. * g->inset);
5021 cairo_stroke(cr);
5022
5023 // middle grey display
5024 cairo_move_to(cr, grey_x, display_bottom);
5025 cairo_line_to(cr, grey_x, display_top - 2. * g->inset);
5026 cairo_stroke(cr);
5027
5028 // white display
5029 cairo_move_to(cr, display_white_x, display_bottom);
5030 cairo_line_to(cr, display_white_x, display_top - 2. * g->inset);
5031 cairo_stroke(cr);
5032
5033 // black scene
5034 cairo_move_to(cr, scene_black_x, scene_bottom + 2. * g->inset);
5035 cairo_line_to(cr, scene_black_x, scene_top);
5036 cairo_stroke(cr);
5037
5038 // middle grey scene
5039 cairo_move_to(cr, grey_x, scene_bottom + 2. * g->inset);
5040 cairo_line_to(cr, grey_x, scene_top);
5041 cairo_stroke(cr);
5042
5043 // white scene
5044 cairo_move_to(cr, scene_white_x, scene_bottom + 2. * g->inset);
5045 cairo_line_to(cr, scene_white_x, scene_top);
5046 cairo_stroke(cr);
5047
5048 // legends
5050
5051 // black scene legend
5052 snprintf(text, sizeof(text), "%+.1f", p->black_point_source);
5053 pango_layout_set_text(layout, text, -1);
5054 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
5055 cairo_move_to(cr, scene_black_x - 0.5 * g->ink.width - g->ink.x,
5056 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
5057 pango_cairo_show_layout(cr, layout);
5058 cairo_stroke(cr);
5059
5060 // grey scene legend
5061 snprintf(text, sizeof(text), "%+.1f", 0.f);
5062 pango_layout_set_text(layout, text, -1);
5063 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
5064 cairo_move_to(cr, grey_x - 0.5 * g->ink.width - g->ink.x,
5065 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
5066 pango_cairo_show_layout(cr, layout);
5067 cairo_stroke(cr);
5068
5069 // white scene legend
5070 snprintf(text, sizeof(text), "%+.1f", p->white_point_source);
5071 pango_layout_set_text(layout, text, -1);
5072 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
5073 cairo_move_to(cr, scene_white_x - 0.5 * g->ink.width - g->ink.x,
5074 scene_bottom + 2. * g->inset + 0. * g->ink.height + g->ink.y);
5075 pango_cairo_show_layout(cr, layout);
5076 cairo_stroke(cr);
5077
5078 // black scene legend
5079 snprintf(text, sizeof(text), "%.0f", p->black_point_target);
5080 pango_layout_set_text(layout, text, -1);
5081 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
5082 cairo_move_to(cr, display_black_x - 0.5 * g->ink.width - g->ink.x,
5083 display_top - 4. * g->inset - g->ink.height - g->ink.y);
5084 pango_cairo_show_layout(cr, layout);
5085 cairo_stroke(cr);
5086
5087 // grey scene legend
5088 snprintf(text, sizeof(text), "%.0f", p->grey_point_target);
5089 pango_layout_set_text(layout, text, -1);
5090 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
5091 cairo_move_to(cr, grey_x - 0.5 * g->ink.width - g->ink.x,
5092 display_top - 4. * g->inset - g->ink.height - g->ink.y);
5093 pango_cairo_show_layout(cr, layout);
5094 cairo_stroke(cr);
5095
5096 // white scene legend
5097 snprintf(text, sizeof(text), "%.0f", p->white_point_target);
5098 pango_layout_set_text(layout, text, -1);
5099 pango_layout_get_pixel_extents(layout, &g->ink, NULL);
5100 cairo_move_to(cr, display_white_x - 0.5 * g->ink.width - g->ink.x,
5101 display_top - 4. * g->inset - g->ink.height - g->ink.y);
5102 pango_cairo_show_layout(cr, layout);
5103 cairo_stroke(cr);
5104 }
5105
5106 // restore font size
5107 pango_font_description_set_size(desc, font_size);
5108 pango_layout_set_font_description(layout, desc);
5109
5110 cairo_destroy(cr);
5111 cairo_set_source_surface(crf, cst, 0, 0);
5112 cairo_paint(crf);
5113 cairo_surface_destroy(cst);
5114 g_object_unref(layout);
5115 pango_font_description_free(desc);
5116 return TRUE;
5117}
5118
5119static gboolean area_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
5120{
5121 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5122 if(dt_gui_widgets_suppressed()) return TRUE;
5123
5125
5127
5128 if(g->active_button != DT_FILMIC_GUI_BUTTON_LAST)
5129 {
5130
5131 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
5132 {
5133 // double click resets view
5134 if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
5135 {
5136 g->gui_mode = DT_FILMIC_GUI_LOOK;
5137 gtk_widget_queue_draw(GTK_WIDGET(g->area));
5138 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_view", g->gui_mode);
5139 return TRUE;
5140 }
5141 else
5142 {
5143 return FALSE;
5144 }
5145 }
5146 else if(event->button == 1)
5147 {
5148 // simple left click cycles through modes in positive direction
5149 if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
5150 {
5151 // cycle type of graph
5152 if(g->gui_mode == DT_FILMIC_GUI_RANGES)
5153 g->gui_mode = DT_FILMIC_GUI_LOOK;
5154 else
5155 g->gui_mode++;
5156
5157 gtk_widget_queue_draw(GTK_WIDGET(g->area));
5158 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_view", g->gui_mode);
5159 return TRUE;
5160 }
5161 else if(g->active_button == DT_FILMIC_GUI_BUTTON_LABELS)
5162 {
5163 g->gui_show_labels = !g->gui_show_labels;
5164 gtk_widget_queue_draw(GTK_WIDGET(g->area));
5165 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_show_labels", g->gui_show_labels);
5166 return TRUE;
5167 }
5168 else
5169 {
5170 // we should never get there since (g->active_button != DT_FILMIC_GUI_BUTTON_LAST)
5171 // and any other case has been processed above.
5172 return FALSE;
5173 }
5174 }
5175 else if(event->button == 3)
5176 {
5177 // simple right click cycles through modes in negative direction
5178 if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
5179 {
5180 if(g->gui_mode == DT_FILMIC_GUI_LOOK)
5181 g->gui_mode = DT_FILMIC_GUI_RANGES;
5182 else
5183 g->gui_mode--;
5184
5185 gtk_widget_queue_draw(GTK_WIDGET(g->area));
5186 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_view", g->gui_mode);
5187 return TRUE;
5188 }
5189 else if(g->active_button == DT_FILMIC_GUI_BUTTON_LABELS)
5190 {
5191 g->gui_show_labels = !g->gui_show_labels;
5192 gtk_widget_queue_draw(GTK_WIDGET(g->area));
5193 dt_conf_set_int("plugins/darkroom/filmicrgb/graph_show_labels", g->gui_show_labels);
5194 return TRUE;
5195 }
5196 else
5197 {
5198 return FALSE;
5199 }
5200 }
5201 }
5202
5203 return FALSE;
5204}
5205
5206static gboolean area_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
5207{
5208 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5209 if(dt_gui_widgets_suppressed()) return 1;
5210 if(!self->enabled) return 0;
5211
5213 g->gui_hover = TRUE;
5214 gtk_widget_queue_draw(GTK_WIDGET(g->area));
5215 return TRUE;
5216}
5217
5218
5219static gboolean area_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
5220{
5221 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5222 if(dt_gui_widgets_suppressed()) return 1;
5223 if(!self->enabled) return 0;
5224
5226 g->gui_hover = FALSE;
5227 gtk_widget_queue_draw(GTK_WIDGET(g->area));
5228 return TRUE;
5229}
5230
5231static gboolean area_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
5232{
5233 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
5234 if(dt_gui_widgets_suppressed()) return 1;
5235
5237 if(!g->gui_sizes_inited) return FALSE;
5238
5239 // get in-widget coordinates
5240 const float y = event->y;
5241 const float x = event->x;
5242
5243 if(x > 0. && x < g->allocation.width && y > 0. && y < g->allocation.height) g->gui_hover = TRUE;
5244
5245 gint save_active_button = g->active_button;
5246
5247 if(g->gui_hover)
5248 {
5249 // find out which button is under the mouse
5250 gint found_something = FALSE;
5251 for(int i = 0; i < DT_FILMIC_GUI_BUTTON_LAST; i++)
5252 {
5253 // check if mouse in in the button's bounds
5254 if(x > g->buttons[i].left && x < g->buttons[i].right && y > g->buttons[i].top && y < g->buttons[i].bottom)
5255 {
5256 // yeah, mouse is over that button
5257 g->buttons[i].mouse_hover = TRUE;
5258 g->active_button = i;
5259 found_something = TRUE;
5260 }
5261 else
5262 {
5263 // no luck with this button
5264 g->buttons[i].mouse_hover = FALSE;
5265 }
5266 }
5267
5268 if(!found_something) g->active_button = DT_FILMIC_GUI_BUTTON_LAST; // mouse is over no known button
5269
5270 // update the tooltips
5271 if(g->active_button == DT_FILMIC_GUI_BUTTON_LAST && x < g->buttons[0].left)
5272 {
5273 // we are over the graph area
5274 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("use the parameters below to set the nodes.\n"
5275 "the bright curve is the filmic tone mapping curve\n"
5276 "the dark curve is the desaturation curve."));
5277 }
5278 else if(g->active_button == DT_FILMIC_GUI_BUTTON_LABELS)
5279 {
5280 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("toggle axis labels and values display"));
5281 }
5282 else if(g->active_button == DT_FILMIC_GUI_BUTTON_TYPE)
5283 {
5284 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("cycle through graph views.\n"
5285 "left click: cycle forward.\n"
5286 "right click: cycle backward.\n"
5287 "double-click: reset to look view."));
5288 }
5289 else
5290 {
5291 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), "");
5292 }
5293
5294 if(save_active_button != g->active_button) gtk_widget_queue_draw(GTK_WIDGET(g->area));
5295 return TRUE;
5296 }
5297 else
5298 {
5299 g->active_button = DT_FILMIC_GUI_BUTTON_LAST;
5300 if(save_active_button != g->active_button) (GTK_WIDGET(g->area));
5301 return FALSE;
5302 }
5303}
5304
5305static gboolean area_scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
5306{
5307 // let scroll events fall through (e.g. to scroll the panel); the height is set via the grip
5308 return FALSE;
5309}
5310
5312{
5314
5315 g->show_mask = FALSE;
5316 g->gui_mode = DT_FILMIC_GUI_LOOK;
5317 g->gui_show_labels = TRUE;
5318 g->gui_hover = FALSE;
5319 g->gui_sizes_inited = FALSE;
5320
5321 // the graph is not interactive; give it a modest default height, user-resizable via its grip
5322 g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
5323 gtk_widget_set_hexpand(GTK_WIDGET(g->area), TRUE);
5324 g_object_set_data(G_OBJECT(g->area), "iop-instance", self);
5325
5326 gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
5327 gtk_widget_add_events(GTK_WIDGET(g->area), GDK_BUTTON_PRESS_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
5328 | GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask);
5329 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(dt_iop_tonecurve_draw), self);
5330 g_signal_connect(G_OBJECT(g->area), "button-press-event", G_CALLBACK(area_button_press), self);
5331 g_signal_connect(G_OBJECT(g->area), "leave-notify-event", G_CALLBACK(area_leave_notify), self);
5332 g_signal_connect(G_OBJECT(g->area), "enter-notify-event", G_CALLBACK(area_enter_notify), self);
5333 g_signal_connect(G_OBJECT(g->area), "motion-notify-event", G_CALLBACK(area_motion_notify), self);
5334 g_signal_connect(G_OBJECT(g->area), "scroll-event", G_CALLBACK(area_scroll_callback), self);
5335
5336 // Init GTK notebook
5337 g->notebook = dt_ui_notebook_new();
5338 // Each page holds its own pickers (grey/white/black point on scene, auto
5339 // exposure/contrast boost on reconstruct); reset any active one when the page
5340 // it lives on is switched away from.
5341 dt_ui_notebook_set_picker_owner(g->notebook, self);
5342
5343 // Page SCENE
5344 self->widget = dt_ui_notebook_page(g->notebook, N_("scene"), NULL);
5345
5346 g->grey_point_source
5347 = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "grey_point_source"));
5348 dt_bauhaus_slider_set_soft_range(g->grey_point_source, .1, 36.0);
5349 dt_bauhaus_slider_set_format(g->grey_point_source, "%");
5350 gtk_widget_set_tooltip_text(g->grey_point_source,
5351 _("adjust to match the average luminance of the image's subject.\n"
5352 "the value entered here will then be remapped to 18.45%.\n"
5353 "decrease the value to increase the overall brightness."));
5354
5355 // White slider
5356 g->white_point_source
5357 = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "white_point_source"));
5358 dt_bauhaus_slider_set_soft_range(g->white_point_source, 2.0, 8.0);
5359 dt_bauhaus_slider_set_format(g->white_point_source, _(" EV"));
5360 gtk_widget_set_tooltip_text(g->white_point_source,
5361 _("number of stops between middle gray and pure white.\n"
5362 "this is a reading a lightmeter would give you on the scene.\n"
5363 "adjust so highlights clipping is avoided"));
5364
5365 // Black slider
5366 g->black_point_source
5367 = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "black_point_source"));
5368 dt_bauhaus_slider_set_soft_range(g->black_point_source, -14.0, -3);
5369 dt_bauhaus_slider_set_format(g->black_point_source, _(" EV"));
5370 gtk_widget_set_tooltip_text(
5371 g->black_point_source, _("number of stops between middle gray and pure black.\n"
5372 "this is a reading a lightmeter would give you on the scene.\n"
5373 "increase to get more contrast.\ndecrease to recover more details in low-lights."));
5374
5375 // Dynamic range scaling
5376 g->security_factor = dt_bauhaus_slider_from_params(self, "security_factor");
5377 dt_bauhaus_slider_set_soft_max(g->security_factor, 50);
5378 dt_bauhaus_slider_set_format(g->security_factor, "%");
5379 gtk_widget_set_tooltip_text(g->security_factor, _("symmetrically enlarge or shrink the computed dynamic range.\n"
5380 "useful to give a safety margin to extreme luminances."));
5381
5382 // Auto tune slider
5383 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
5384 gtk_box_pack_start(GTK_BOX(hbox), dt_ui_label_new(_("auto tune levels")), TRUE, TRUE, 0);
5385 g->auto_button = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, NULL);
5386 gtk_box_pack_start(GTK_BOX(hbox), g->auto_button, FALSE, FALSE, 0);
5387 dt_gui_add_class(g->auto_button, "dt_bauhaus_alignment");
5388 gtk_widget_set_tooltip_text(g->auto_button, _("try to optimize the settings with some statistical assumptions.\n"
5389 "this will fit the luminance range inside the histogram bounds.\n"
5390 "works better for landscapes and evenly-lit pictures\n"
5391 "but fails for high-keys, low-keys and high-ISO pictures.\n"
5392 "this is not an artificial intelligence, but a simple guess.\n"
5393 "ensure you understand its assumptions before using it."));
5394 gtk_box_pack_start(GTK_BOX(self->widget), hbox, FALSE, FALSE, 0);
5395
5396 GtkWidget *label = dt_ui_section_label_new(_("advanced"));
5397 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
5398
5399 g->custom_grey = dt_bauhaus_toggle_from_params(self, "custom_grey");
5400 gtk_widget_set_tooltip_text(g->custom_grey, _("enable to input custom middle-gray values.\n"
5401 "this is not recommended in general.\n"
5402 "fix the global exposure in the exposure module instead.\n"
5403 "disable to use standard 18.45 %% middle gray."));
5404
5405 // Page RECONSTRUCT
5406 self->widget = dt_ui_notebook_page(g->notebook, N_("reconstruct"), NULL);
5407
5408 label = dt_ui_section_label_new(_("highlights clipping"));
5409 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
5410
5411 g->reconstruct_threshold = dt_bauhaus_slider_from_params(self, "reconstruct_threshold");
5412 dt_bauhaus_slider_set_format(g->reconstruct_threshold, _(" EV"));
5413 gtk_widget_set_tooltip_text(g->reconstruct_threshold,
5414 _("set the exposure threshold upon which\n"
5415 "clipped highlights get reconstructed.\n"
5416 "values are relative to the scene white point.\n"
5417 "0 EV means the threshold is the same as the scene white point.\n"
5418 "decrease to include more areas,\n"
5419 "increase to exclude more areas."));
5420
5421 g->reconstruct_feather = dt_bauhaus_slider_from_params(self, "reconstruct_feather");
5422 dt_bauhaus_slider_set_format(g->reconstruct_feather, _(" EV"));
5423 gtk_widget_set_tooltip_text(g->reconstruct_feather,
5424 _("soften the transition between clipped highlights and valid pixels.\n"
5425 "decrease to make the transition harder and sharper,\n"
5426 "increase to make the transition softer and blurrier."));
5427
5428 // Highlight Reconstruction Mask
5429 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
5430 gtk_box_pack_start(GTK_BOX(hbox), dt_ui_label_new(_("display highlight reconstruction mask")), TRUE, TRUE, 0);
5431 g->show_highlight_mask = dt_iop_togglebutton_new(self, NULL, N_("display highlight reconstruction mask"), NULL, G_CALLBACK(show_mask_callback),
5432 FALSE, 0, 0, dtgtk_cairo_paint_showmask, hbox);
5434 dt_gui_add_class(g->show_highlight_mask, "dt_bauhaus_alignment");
5435
5436 gtk_box_pack_start(GTK_BOX(self->widget), hbox, FALSE, FALSE, 0);
5437
5438 label = dt_ui_section_label_new(_("balance"));
5439 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
5440
5441 g->reconstruct_structure_vs_texture = dt_bauhaus_slider_from_params(self, "reconstruct_structure_vs_texture");
5442 dt_bauhaus_slider_set_format(g->reconstruct_structure_vs_texture, "%");
5443 gtk_widget_set_tooltip_text(g->reconstruct_structure_vs_texture,
5444 /* xgettext:no-c-format */
5445 _("decide which reconstruction strategy to favor,\n"
5446 "between inpainting a smooth color gradient,\n"
5447 "or trying to recover the textured details.\n"
5448 "0% is an equal mix of both.\n"
5449 "increase if at least one RGB channel is not clipped.\n"
5450 "decrease if all RGB channels are clipped over large areas."));
5451
5452 g->reconstruct_bloom_vs_details = dt_bauhaus_slider_from_params(self, "reconstruct_bloom_vs_details");
5453 dt_bauhaus_slider_set_format(g->reconstruct_bloom_vs_details, "%");
5454 gtk_widget_set_tooltip_text(g->reconstruct_bloom_vs_details,
5455 /* xgettext:no-c-format */
5456 _("decide which reconstruction strategy to favor,\n"
5457 "between blooming highlights like film does,\n"
5458 "or trying to recover sharp details.\n"
5459 "0% is an equal mix of both.\n"
5460 "increase if you want more details.\n"
5461 "decrease if you want more blur."));
5462
5463 // Bloom threshold
5464 g->reconstruct_grey_vs_color = dt_bauhaus_slider_from_params(self, "reconstruct_grey_vs_color");
5465 dt_bauhaus_slider_set_format(g->reconstruct_grey_vs_color, "%");
5466 gtk_widget_set_tooltip_text(g->reconstruct_grey_vs_color,
5467 /* xgettext:no-c-format */
5468 _("decide which reconstruction strategy to favor,\n"
5469 "between recovering monochromatic highlights,\n"
5470 "or trying to recover colorful highlights.\n"
5471 "0% is an equal mix of both.\n"
5472 "increase if you want more color.\n"
5473 "decrease if you see magenta or out-of-gamut highlights."));
5474
5475 label = dt_ui_section_label_new(_("advanced"));
5476 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
5477
5478 // Color inpainting
5479 g->high_quality_reconstruction = dt_bauhaus_slider_from_params(self, "high_quality_reconstruction");
5480 gtk_widget_set_tooltip_text(g->high_quality_reconstruction,
5481 _("run extra passes of chromaticity reconstruction.\n"
5482 "more iterations means more color propagation from neighbourhood.\n"
5483 "this will be slower but will yield more neutral highlights.\n"
5484 "it also helps with difficult cases of magenta highlights."));
5485
5486 // Highlight noise
5487 g->noise_level = dt_bauhaus_slider_from_params(self, "noise_level");
5488 gtk_widget_set_tooltip_text(g->noise_level, _("add statistical noise in reconstructed highlights.\n"
5489 "this avoids highlights to look too smooth\n"
5490 "when the picture is noisy overall,\n"
5491 "so they blend with the rest of the picture."));
5492
5493 // Noise distribution
5494 g->noise_distribution = dt_bauhaus_combobox_from_params(self, "noise_distribution");
5495 gtk_widget_set_tooltip_text(g->noise_distribution, _("choose the statistical distribution of noise.\n"
5496 "this is useful to match natural sensor noise pattern.\n"));
5497
5498 // Page LOOK
5499 self->widget = dt_ui_notebook_page(g->notebook, N_("look"), NULL);
5500
5501 label = dt_ui_section_label_new(_("tone mapping"));
5502 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
5503
5504 g->contrast = dt_bauhaus_slider_from_params(self, N_("contrast"));
5505 dt_bauhaus_slider_set_soft_range(g->contrast, 0.5, 3.0);
5506 dt_bauhaus_slider_set_digits(g->contrast, 3);
5507 gtk_widget_set_tooltip_text(g->contrast, _("slope of the linear part of the curve\n"
5508 "affects mostly the mid-tones"));
5509
5510 // brightness slider
5511 g->output_power = dt_bauhaus_slider_from_params(self, "output_power");
5512 gtk_widget_set_tooltip_text(g->output_power, _("equivalent to paper grade in analog.\n"
5513 "increase to make highlights brighter and less compressed.\n"
5514 "decrease to mute highlights."));
5515
5516 // default = latitude default (with balance 0, each direct slider equals the latitude
5517 // fraction — see filmic_v3_legacy_to_direct), so double-click reset stays consistent
5518 // with the params defaults (which keep latitude/balance for compatibility)
5519 g->toe = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.0f, 100.0f, 0.0f, 10.0f, 2);
5520 gtk_box_pack_start(GTK_BOX(self->widget), g->toe, FALSE, FALSE, 0);
5521 dt_bauhaus_widget_set_label(g->toe, N_("shadows"));
5522 dt_bauhaus_slider_set_soft_range(g->toe, 0.1f, 90.0f);
5524 gtk_widget_set_tooltip_text(g->toe,
5525 _("distance between middle gray and the start of the shadows roll-off.\n"
5526 "0% keeps the toe at middle gray, 100% pushes it to the point where the\n"
5527 "current slope would hit the output black level."));
5528 g_signal_connect(G_OBJECT(g->toe), "value-changed", G_CALLBACK(toe_shoulder_callback), self);
5529
5530 g->shoulder = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.0f, 100.0f, 0.0f, 10.0f, 2);
5531 gtk_box_pack_start(GTK_BOX(self->widget), g->shoulder, FALSE, FALSE, 0);
5532 dt_bauhaus_widget_set_label(g->shoulder, N_("highlights"));
5533 dt_bauhaus_slider_set_soft_range(g->shoulder, 0.1f, 90.0f);
5534 dt_bauhaus_slider_set_format(g->shoulder, "%");
5535 gtk_widget_set_tooltip_text(g->shoulder,
5536 _("distance between middle gray and the start of the highlights roll-off.\n"
5537 "0% keeps the shoulder at middle gray, 100% pushes it to the point where the\n"
5538 "current slope would hit the output white level."));
5539 g_signal_connect(G_OBJECT(g->shoulder), "value-changed", G_CALLBACK(toe_shoulder_callback), self);
5540
5541 // Curve type
5542 g->highlights = dt_bauhaus_combobox_from_params(self, "highlights");
5543 gtk_widget_set_tooltip_text(g->highlights, _("shape of the highlights roll-off of the curve.\n"
5544 "perceptual (default) is a generalized sigmoid derived from a\n"
5545 "perceptual appearance model: always smooth and monotonic.\n"
5546 "hard/soft/safe are the legacy polynomial/rational segments;\n"
5547 "hard compresses highlights more, soft less."));
5548
5549 g->shadows = dt_bauhaus_combobox_from_params(self, "shadows");
5550 gtk_widget_set_tooltip_text(g->shadows, _("shape of the shadows roll-off of the curve.\n"
5551 "perceptual (default) is a generalized sigmoid derived from a\n"
5552 "perceptual appearance model: always smooth and monotonic.\n"
5553 "hard/soft/safe are the legacy polynomial/rational segments;\n"
5554 "hard compresses shadows more, soft less."));
5555
5556 label = dt_ui_section_label_new(_("color mapping"));
5557 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
5558
5559 g->saturation = dt_bauhaus_slider_from_params(self, "saturation");
5560 dt_bauhaus_slider_set_soft_range(g->saturation, -100.0, 100.0);
5561 dt_bauhaus_slider_set_format(g->saturation, "%");
5562 gtk_widget_set_tooltip_text(g->saturation, _("desaturates the output of the module\n"
5563 "specifically at extreme luminances.\n"
5564 "increase if shadows and/or highlights are under-saturated."));
5565
5566 g->preserve_color = dt_bauhaus_combobox_from_params(self, "preserve_color");
5567 gtk_widget_set_tooltip_text(g->preserve_color, _("ensure the original color are preserved.\n"
5568 "may reinforce chromatic aberrations and chroma noise,\n"
5569 "so ensure they are properly corrected elsewhere.\n"));
5570
5571 label = dt_ui_section_label_new(_("advanced"));
5572 gtk_box_pack_start(GTK_BOX(self->widget), label, FALSE, FALSE, 0);
5573
5574 // Color science
5575 g->version = dt_bauhaus_combobox_from_params(self, "version");
5576 gtk_widget_set_tooltip_text(g->version,
5577 _("v3 is darktable 3.0 desaturation method, same as color balance.\n"
5578 "v4 is a newer desaturation method, based on spectral purity of light.\n"
5579 "v8 tone maps each RGB channel separately in an inset rendering space:\n"
5580 "highlights bleach toward white and hues drift as tonal compression\n"
5581 "increases, with a parametric hue recovery on the saturation slider."));
5582
5583 // Spline node geometry (v1-v3). The segment SHAPE, including the sigmoid, is the
5584 // shadows/highlights curve type, not this control.
5585 g->spline_version = dt_bauhaus_combobox_from_params(self, "spline_version");
5586 gtk_widget_set_tooltip_text(g->spline_version,
5587 _("how the latitude, balance and contrast place the toe and\n"
5588 "shoulder nodes of the curve (not the shape between them —\n"
5589 "that is 'contrast in shadows/highlights').\n"
5590 "v3 (2021) is recommended; v1/v2 are kept for older edits."));
5591
5592
5593 g->auto_hardness = dt_bauhaus_toggle_from_params(self, "auto_hardness");
5594 gtk_widget_set_tooltip_text(
5595 g->auto_hardness, _("enable to auto-set the look hardness depending on the scene white and black points.\n"
5596 "this keeps the middle gray on the identity line and improves fast tuning.\n"
5597 "disable if you want a manual control."));
5598
5599 // Page DISPLAY
5600 self->widget = dt_ui_notebook_page(g->notebook, N_("display"), NULL);
5601
5602 // Black slider
5603 g->black_point_target = dt_bauhaus_slider_from_params(self, "black_point_target");
5604 dt_bauhaus_slider_set_digits(g->black_point_target, 4);
5605 dt_bauhaus_slider_set_format(g->black_point_target, "%");
5606 gtk_widget_set_tooltip_text(g->black_point_target, _("luminance of output pure black, "
5607 "this should be 0%\nexcept if you want a faded look"));
5608
5609 g->grey_point_target = dt_bauhaus_slider_from_params(self, "grey_point_target");
5610 dt_bauhaus_slider_set_digits(g->grey_point_target, 4);
5611 dt_bauhaus_slider_set_format(g->grey_point_target, "%");
5612 gtk_widget_set_tooltip_text(g->grey_point_target,
5613 _("middle gray value of the target display or color space.\n"
5614 "you should never touch that unless you know what you are doing."));
5615
5616 g->white_point_target = dt_bauhaus_slider_from_params(self, "white_point_target");
5617 dt_bauhaus_slider_set_soft_max(g->white_point_target, 100.0);
5618 dt_bauhaus_slider_set_digits(g->white_point_target, 4);
5619 dt_bauhaus_slider_set_format(g->white_point_target, "%");
5620 gtk_widget_set_tooltip_text(g->white_point_target, _("luminance of output pure white, "
5621 "this should be 100%\nexcept if you want a faded look"));
5622
5623 // start building top level widget
5624 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
5625
5626 gtk_box_pack_start(GTK_BOX(self->widget),
5627 dt_ui_resizable_drawing_area(GTK_WIDGET(g->area),
5628 "plugins/darkroom/filmicrgb/graphheight", 230, 100),
5629 FALSE, FALSE, 0);
5630 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->notebook), FALSE, FALSE, 0);
5631}
5632
5633void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
5634{
5637
5638 if(IS_NULL_PTR(w) || w == g->auto_hardness || w == g->security_factor || w == g->grey_point_source
5639 || w == g->black_point_source || w == g->white_point_source)
5640 {
5642
5643 if(w == g->security_factor || w == g->grey_point_source)
5644 {
5645 float prev = *(float *)previous;
5646 if(w == g->security_factor)
5647 {
5648 float ratio = (p->security_factor - prev) / (prev + 100.0f);
5649
5650 float EVmin = p->black_point_source;
5651 EVmin = EVmin + ratio * EVmin;
5652
5653 float EVmax = p->white_point_source;
5654 EVmax = EVmax + ratio * EVmax;
5655
5656 p->white_point_source = EVmax;
5657 p->black_point_source = EVmin;
5658 }
5659 else
5660 {
5661 float grey_var = log2f(prev / p->grey_point_source);
5662 p->black_point_source = p->black_point_source - grey_var;
5663 p->white_point_source = p->white_point_source + grey_var;
5664 }
5665
5666 dt_bauhaus_slider_set(g->white_point_source, p->white_point_source);
5667 dt_bauhaus_slider_set(g->black_point_source, p->black_point_source);
5668 }
5669
5670 if(p->auto_hardness)
5671 p->output_power = logf(p->grey_point_target / 100.0f)
5672 / logf(-p->black_point_source / (p->white_point_source - p->black_point_source));
5673
5674 gtk_widget_set_visible(GTK_WIDGET(g->output_power), !p->auto_hardness);
5675 dt_bauhaus_slider_set(g->output_power, p->output_power);
5676
5678 }
5679
5680 if(IS_NULL_PTR(w) || w == g->version)
5681 {
5682 if(_filmic_is_agx(p->version))
5683 {
5684 dt_bauhaus_widget_set_label(g->saturation, N_("color preservation"));
5685 gtk_widget_set_tooltip_text(g->saturation, _("how much of the per-channel hue drift to keep.\n"
5686 "saturation is not affected: valid diffuse colors\n"
5687 "(skin tones, product colors) keep their saturation and\n"
5688 "strongly compressed colors bleach, at any setting.\n"
5689 "-100%% is pure AgX: full hue drift (the 'film' look).\n"
5690 "0%% (default) removes half the hue drift.\n"
5691 "+100%% restores the original hues exactly."));
5692 gtk_widget_set_visible(GTK_WIDGET(g->preserve_color), FALSE);
5693 }
5694 else if(p->version == DT_FILMIC_COLORSCIENCE_V1 || p->version == DT_FILMIC_COLORSCIENCE_V4)
5695 {
5696 dt_bauhaus_widget_set_label(g->saturation, N_("extreme luminance saturation"));
5697 gtk_widget_set_tooltip_text(g->saturation, _("desaturates the output of the module\n"
5698 "specifically at extreme luminances.\n"
5699 "increase if shadows and/or highlights are under-saturated."));
5700 }
5701 else if(p->version == DT_FILMIC_COLORSCIENCE_V2 || p->version == DT_FILMIC_COLORSCIENCE_V3)
5702 {
5703 dt_bauhaus_widget_set_label(g->saturation, N_("mid-tones saturation"));
5704 gtk_widget_set_tooltip_text(g->saturation, _("desaturates the output of the module\n"
5705 "specifically at medium luminances.\n"
5706 "increase if midtones are under-saturated."));
5707 }
5708 else if(p->version == DT_FILMIC_COLORSCIENCE_V5)
5709 {
5710 dt_bauhaus_widget_set_label(g->saturation, N_("highlights saturation mix"));
5711 gtk_widget_set_tooltip_text(g->saturation, _("Positive values ensure saturation is kept unchanged over the whole range.\n"
5712 "Negative values bleache highlights at constant hue and luminance.\n"
5713 "Zero is an equal mix of both strategies."));
5714 gtk_widget_set_visible(GTK_WIDGET(g->preserve_color), FALSE);
5715 }
5716
5717 // v7 and v8 (all AgX variants) define their own chrominance handling and ignore preserve_color
5718 if(p->version != DT_FILMIC_COLORSCIENCE_V5 && !_filmic_is_agx(p->version))
5719 gtk_widget_set_visible(GTK_WIDGET(g->preserve_color), TRUE);
5720 }
5721
5722 if(IS_NULL_PTR(w) || w == g->reconstruct_bloom_vs_details)
5723 {
5724 if(p->reconstruct_bloom_vs_details == -100.f)
5725 {
5726 // user disabled the reconstruction in favor of full blooming
5727 // so the structure vs. texture setting doesn't make any difference
5728 // make it insensitive to not confuse users
5729 gtk_widget_set_sensitive(g->reconstruct_structure_vs_texture, FALSE);
5730 }
5731 else
5732 {
5733 gtk_widget_set_sensitive(g->reconstruct_structure_vs_texture, TRUE);
5734 }
5735 }
5736
5737 if(IS_NULL_PTR(w) || w == g->custom_grey)
5738 {
5739 gtk_widget_set_visible(g->grey_point_source, p->custom_grey);
5740 gtk_widget_set_visible(g->grey_point_target, p->custom_grey);
5741 }
5742
5744 gtk_widget_queue_draw(GTK_WIDGET(g->area));
5745}
5746
5747// clang-format off
5748// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
5749// vim: shiftwidth=2 expandtab tabstop=2 cindent
5750// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
5751// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1650
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3549
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3486
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1626
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3537
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1656
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1783
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3613
static void set_color(cairo_t *cr, GdkRGBA color)
Definition bauhaus.h:446
#define INNER_PADDING
Definition bauhaus.h:79
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static __DT_CLONE_TARGETS__ void normalize(float *const buffer, const size_t width, const size_t height, const float norm)
Definition blurs.c:347
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:325
#define BSPLINE_FSIZE
Definition bspline.h:30
const dt_colormatrix_t XYZ_D65_to_D50_CAT16
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
return vector dt_simd_set1(valid ?(scaling+NORM_MIN) :NORM_MIN)
static float dt_camera_rgb_luminance(const float4 rgb)
@ IOP_CS_RGB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_AREA
static float4 LMS_to_XYZ(const float4 LMS)
Definition colorspace.h:491
static float4 Yrg_to_LMS(const float4 Yrg)
Definition colorspace.h:550
static float4 LMS_to_Yrg(const float4 LMS)
Definition colorspace.h:534
static float4 XYZ_to_LMS(const float4 XYZ)
Definition colorspace.h:480
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
@ DT_COLORSPACE_NONE
Definition colorspaces.h:82
dt_colorspaces_color_mode_t
@ DT_PROFILE_NORMAL
dt_aligned_pixel_t LMS
static dt_aligned_pixel_t rgb
const float threshold
static const float const float const float min
static dt_aligned_pixel_t XYZ
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
dt_store_simd_aligned(out, dt_mat3x4_mul_vec4(vin, dt_colormatrix_row_to_simd(matrix, 0), dt_colormatrix_row_to_simd(matrix, 1), dt_colormatrix_row_to_simd(matrix, 2)))
static const int row
const float delta
static const dt_colormatrix_t M
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
dt_image_pipe_class_t dt_image_pipe_class(const dt_image_t *img)
const char * dt_image_pipe_class_name(const dt_image_pipe_class_t klass)
float dt_image_get_exposure_bias(const struct dt_image_t *image_storage)
gboolean dt_image_needs_rawprepare(const dt_image_t *img)
int type
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
#define dt_free_align(ptr)
Definition darktable.h:503
static void * dt_calloc_align(size_t size)
Definition darktable.h:510
#define __OMP_SIMD__(...)
Definition darktable.h:274
@ DT_DEBUG_OPENCL
Definition darktable.h:744
@ DT_DEBUG_DEV
Definition darktable.h:739
#define DT_ALIGNED_ARRAY
Definition darktable.h:400
#define dt_gui_freeze_begin()
Definition darktable.h:900
#define dt_gui_freeze_end()
Definition darktable.h:901
#define for_each_channel(_var,...)
Definition darktable.h:684
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:459
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:546
#define dt_free(ptr)
Definition darktable.h:478
void void void gboolean dt_gui_widgets_suppressed(void)
Definition gtk.c:194
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __OMP_DECLARE_SIMD__(...)
Definition darktable.h:275
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define dt_pixelpipe_cache_alloc_align_float(pixels, pipe)
Definition darktable.h:454
#define __DT_CLONE_TARGETS__
Definition darktable.h:379
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:271
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
static float4 dt_noise_generator_simd(const dt_noise_distribution_t distribution, const float4 mu, const float4 param, uint state[4])
static unsigned int splitmix32(const unsigned long seed)
static float xoshiro128plus(uint state[4])
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:42
#define dt_dev_pixelpipe_update_history_main(dev)
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:118
@ DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU
Definition develop.h:136
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:117
static float dt_log_scale_axis(const float x, const float base)
Definition draw.h:186
static void dt_draw_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:143
static void dt_draw_loglog_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom, const float base)
Definition draw.h:191
void dtgtk_cairo_paint_refresh(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_text_label(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void(* DTGTKCairoPaintIconFunc)(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Definition dtgtk/paint.h:75
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
static void toe_shoulder_callback(GtkWidget *slider, gpointer user_data)
Definition filmicrgb.c:4081
static gboolean area_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition filmicrgb.c:5119
static void _filmic_agx_Yrg_to_xyz_D50(const dt_aligned_pixel_t Yrg, dt_aligned_pixel_t xyz_D50)
Definition filmicrgb.c:2294
static gboolean area_scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
Definition filmicrgb.c:5305
const char ** description(struct dt_iop_module_t *self)
Definition filmicrgb.c:403
int default_group()
Definition filmicrgb.c:414
static gboolean area_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition filmicrgb.c:5219
static gboolean _filmic_agx_build_displaced(const dt_iop_order_iccprofile_info_t *const work_profile, const float inset[3], const float rotation[3], dt_colormatrix_t M)
Definition filmicrgb.c:2315
static __DT_CLONE_TARGETS__ void display_mask(const float *const restrict mask, float *const restrict out, const size_t width, const size_t height)
Definition filmicrgb.c:2562
dt_iop_filmic_noise_distribution_t
Definition filmicrgb.c:215
@ DT_FILMIC_NOISE_UNIFORM
Definition filmicrgb.c:216
@ DT_FILMIC_NOISE_POISSONIAN
Definition filmicrgb.c:218
@ DT_FILMIC_NOISE_GAUSSIAN
Definition filmicrgb.c:217
dt_iop_filmicrgb_curve_type_t
Definition filmicrgb.c:136
@ DT_FILMIC_CURVE_RATIONAL
Definition filmicrgb.c:139
@ DT_FILMIC_CURVE_SIGMOID
Definition filmicrgb.c:144
@ DT_FILMIC_CURVE_POLY_4
Definition filmicrgb.c:137
@ DT_FILMIC_CURVE_POLY_3
Definition filmicrgb.c:138
void gui_reset(dt_iop_module_t *self)
Definition filmicrgb.c:4197
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:4231
static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
Definition filmicrgb.c:4271
dt_iop_filmic_rgb_gui_mode_t
Definition filmicrgb.c:205
@ DT_FILMIC_GUI_LOOK
Definition filmicrgb.c:206
@ DT_FILMIC_GUI_RANGES
Definition filmicrgb.c:209
@ DT_FILMIC_GUI_BASECURVE
Definition filmicrgb.c:207
@ DT_FILMIC_GUI_BASECURVE_LOG
Definition filmicrgb.c:208
@ DT_FILMIC_GUI_LAST
Definition filmicrgb.c:210
static __DT_CLONE_TARGETS__ 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:1505
void reload_defaults(dt_iop_module_t *module)
Definition filmicrgb.c:4116
static __DT_CLONE_TARGETS__ 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:1243
static void _filmic_agx_xyz_D50_to_Yrg(const dt_aligned_pixel_t xyz_D50, dt_aligned_pixel_t Yrg)
Definition filmicrgb.c:2285
dt_iop_filmicrgb_colorscience_type_t
Definition filmicrgb.c:149
@ DT_FILMIC_COLORSCIENCE_V1
Definition filmicrgb.c:150
@ DT_FILMIC_COLORSCIENCE_V2
Definition filmicrgb.c:151
@ DT_FILMIC_COLORSCIENCE_V7
Definition filmicrgb.c:156
@ DT_FILMIC_COLORSCIENCE_V9
Definition filmicrgb.c:158
@ DT_FILMIC_COLORSCIENCE_V5
Definition filmicrgb.c:154
@ DT_FILMIC_COLORSCIENCE_V4
Definition filmicrgb.c:153
@ DT_FILMIC_COLORSCIENCE_V6
Definition filmicrgb.c:155
@ DT_FILMIC_COLORSCIENCE_V3
Definition filmicrgb.c:152
@ DT_FILMIC_COLORSCIENCE_V10
Definition filmicrgb.c:159
@ DT_FILMIC_COLORSCIENCE_V8
Definition filmicrgb.c:157
#define LOGBASE
Definition filmicrgb.c:4202
__DT_CLONE_TARGETS__ int process(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid, void *const restrict ovoid)
Definition filmicrgb.c:2649
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition filmicrgb.c:4094
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:4204
static void apply_auto_grey(dt_iop_module_t *self, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition filmicrgb.c:3375
static __DT_CLONE_TARGETS__ 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:1641
static void apply_auto_black(dt_iop_module_t *self, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition filmicrgb.c:3403
static __DT_CLONE_TARGETS__ 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:1295
const char * aliases()
Definition filmicrgb.c:398
static void _mat3_identity(dt_colormatrix_t M)
Definition filmicrgb.c:2303
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition filmicrgb.c:4031
static cl_int reconstruct_highlights_cl(const dt_dev_pixelpipe_t *pipe, cl_mem in, cl_mem mask, cl_mem reconstructed, const dt_iop_filmicrgb_reconstruction_type_t variant, dt_iop_filmicrgb_global_data_t *const gd, const dt_iop_filmicrgb_data_t *const data, const dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *const roi_in)
Definition filmicrgb.c:2826
#define CIE_Y_1931_to_CIE_Y_2006(x)
Definition filmicrgb.c:1794
const char * name()
Definition filmicrgb.c:393
static __DT_CLONE_TARGETS__ 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:2124
static __DT_CLONE_TARGETS__ 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:1587
static __DT_CLONE_TARGETS__ 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:2172
void gui_init(dt_iop_module_t *self)
Definition filmicrgb.c:5311
dt_iop_filmicrgb_methods_type_t
Definition filmicrgb.c:125
@ DT_FILMIC_METHOD_POWER_NORM
Definition filmicrgb.c:129
@ DT_FILMIC_METHOD_EUCLIDEAN_NORM_V1
Definition filmicrgb.c:131
@ DT_FILMIC_METHOD_NONE
Definition filmicrgb.c:126
@ DT_FILMIC_METHOD_MAX_RGB
Definition filmicrgb.c:127
@ DT_FILMIC_METHOD_EUCLIDEAN_NORM_V2
Definition filmicrgb.c:130
@ DT_FILMIC_METHOD_LUMINANCE
Definition filmicrgb.c:128
static __DT_CLONE_TARGETS__ 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:1546
static float log_tonemapping(const float x, const float grey, const float black, const float dynamic_range)
Definition filmicrgb.c:1018
dt_iop_filmicrgb_reconstruction_type_t
Definition filmicrgb.c:188
@ DT_FILMIC_RECONSTRUCT_RATIOS
Definition filmicrgb.c:190
@ DT_FILMIC_RECONSTRUCT_RGB
Definition filmicrgb.c:189
static void filmic_v3_legacy_to_direct(const dt_iop_filmicrgb_params_t *const p, float *const toe, float *const shoulder)
Definition filmicrgb.c:542
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition filmicrgb.c:5633
dt_iop_filmicrgb_gui_button_t
Definition filmicrgb.c:258
@ DT_FILMIC_GUI_BUTTON_LAST
Definition filmicrgb.c:261
@ DT_FILMIC_GUI_BUTTON_LABELS
Definition filmicrgb.c:260
@ DT_FILMIC_GUI_BUTTON_TYPE
Definition filmicrgb.c:259
void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
Definition filmicrgb.c:2629
static __DT_CLONE_TARGETS__ int get_scales(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *roi_in, const dt_dev_pixelpipe_iop_t *const piece)
Definition filmicrgb.c:1381
static int reconstruct_highlights(const dt_dev_pixelpipe_t *const pipe, 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, const 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:1401
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:3926
static void apply_auto_white_point_source(dt_iop_module_t *self, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition filmicrgb.c:3430
#define ORDER_4
Definition filmicrgb.c:3586
static float exp_tonemapping_v2(const float x, const float grey, const float black, const float dynamic_range)
Definition filmicrgb.c:1025
void cleanup_global(dt_iop_module_so_t *module)
Definition filmicrgb.c:4176
static void filmic_prepare_simd_matrices(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, dt_iop_filmicrgb_simd_matrices_t *const simd_matrices)
Definition filmicrgb.c:2051
static gboolean area_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition filmicrgb.c:5206
void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:4061
static __DT_CLONE_TARGETS__ 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:1172
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:424
void input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition filmicrgb.c:429
static gboolean area_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition filmicrgb.c:5231
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:1201
int flags()
Definition filmicrgb.c:419
static void apply_autotune(dt_iop_module_t *self, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition filmicrgb.c:3456
static __DT_CLONE_TARGETS__ void filmic_agx(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:2466
static __DT_CLONE_TARGETS__ 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:2218
static gboolean filmic_v3_compute_geometry(const dt_iop_filmicrgb_params_t *const p, dt_iop_filmicrgb_v3_geometry_t *const geometry)
Definition filmicrgb.c:468
static void show_mask_callback(GtkToggleButton *button, GdkEventButton *event, gpointer user_data)
Definition filmicrgb.c:3565
#define ORDER_3
Definition filmicrgb.c:3587
static const dt_iop_order_iccprofile_info_t * _filmic_get_output_profile(const dt_dev_pixelpipe_t *pipe)
Definition filmicrgb.c:2610
static __DT_CLONE_TARGETS__ 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:1371
static void convert_to_spline_v3(dt_iop_filmicrgb_params_t *n)
Definition filmicrgb.c:594
static __DT_CLONE_TARGETS__ 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:2575
static void filmic_v3_direct_to_legacy(const dt_iop_filmicrgb_params_t *const p, const float toe, const float shoulder, float *const latitude, float *const balance)
Definition filmicrgb.c:560
#define MAX_NUM_SCALES
Definition filmicrgb.c:1170
dt_iop_filmicrgb_spline_version_type_t
Definition filmicrgb.c:175
@ DT_FILMIC_SPLINE_VERSION_V2
Definition filmicrgb.c:177
@ DT_FILMIC_SPLINE_VERSION_V3
Definition filmicrgb.c:178
@ DT_FILMIC_SPLINE_VERSION_V1
Definition filmicrgb.c:176
static float filmic_sigmoid_scale(const float limit_x, const float limit_y, const float transition_x, const float transition_y, const float slope, const float power)
Definition filmicrgb.c:3595
void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:4055
static void filmic_gui_sync_toe_shoulder(dt_iop_module_t *self)
Definition filmicrgb.c:4067
#define INVERSE_SQRT_3
Definition filmicrgb.c:87
void init_global(dt_iop_module_so_t *module)
Definition filmicrgb.c:4151
void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
Definition filmicrgb.c:3495
static void filmic_agx_prepare_bracket(const dt_iop_order_iccprofile_info_t *const work_profile, const dt_iop_filmicrgb_colorscience_type_t variant, dt_colormatrix_t inset, dt_colormatrix_t outset)
Definition filmicrgb.c:2361
static gboolean filmic_v3_compute_nodes_from_legacy(const dt_iop_filmicrgb_params_t *const p, dt_iop_filmicrgb_v3_geometry_t *const geometry, dt_iop_filmicrgb_v3_nodes_t *const nodes)
Definition filmicrgb.c:513
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
Definition filmicrgb.c:3047
static gboolean _filmic_is_agx(const dt_iop_filmicrgb_colorscience_type_t v)
Definition filmicrgb.c:167
static __DT_CLONE_TARGETS__ 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:1355
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:3544
#define SAFETY_MARGIN
Definition filmicrgb.c:88
static float linear_saturation(const float x, const float luminance, const float saturation)
Definition filmicrgb.c:1164
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:3607
static __DT_CLONE_TARGETS__ void restore_ratios(float *const restrict ratios, const float *const restrict norms, const size_t width, const size_t height)
Definition filmicrgb.c:2593
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:657
gboolean runtime_data_hash(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition filmicrgb.c:4045
void default_input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition format.c:57
@ TYPE_FLOAT
Definition format.h:46
static int gauss_solve(double *A, double *b, int n)
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
Definition gtk.c:3068
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition gtk.c:2470
GtkNotebook * dt_ui_notebook_new()
Definition gtk.c:2465
void dt_gui_set_pango_resolution(PangoLayout *layout)
Definition gtk.c:1548
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and relay every "switch_page" as DT_SIGNA...
Definition gtk.c:2500
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:322
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:469
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:479
#define DT_GUI_MODULE(x)
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2221
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:3220
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:131
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:2989
#define dt_omploop_sfence()
Definition imageop.h:747
#define dt_iop_fmt_log(module, fmt,...)
Debug helper to trace a module's input-format-driven decisions on the -d pipe channel (DT_DEBUG_PIPE)...
Definition imageop.h:490
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:196
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:197
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:199
@ IOP_GROUP_TONES
Definition imageop.h:167
#define IOP_GUI_ALLOC(module)
Definition imageop.h:638
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)
GtkWidget * dt_bauhaus_toggle_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void *const ovoid
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_output_profile_info(const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_add_profile_info_to_list(struct dt_develop_t *dev, const dt_colorspaces_color_profile_type_t profile_type, const char *profile_filename, const int intent)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_current_profile_info(dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe)
void dt_ioppr_free_iccprofile_params_cl(dt_colorspaces_iccprofile_info_cl_t **_profile_info_cl, cl_float **_profile_lut_cl, cl_mem *_dev_profile_info, cl_mem *_dev_profile_lut)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
cl_int dt_ioppr_build_iccprofile_params_cl(const dt_iop_order_iccprofile_info_t *const profile_info, const int devid, dt_colorspaces_iccprofile_info_cl_t **_profile_info_cl, cl_float **_profile_lut_cl, cl_mem *_dev_profile_info, cl_mem *_dev_profile_lut)
static const float x
const float *const const float coeff[3]
const float v
@ linear
Definition lightroom.c:368
float *const restrict luminance
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define NORM_MIN
Definition math.h:35
#define CLAMPF(a, mn, mx)
Definition math.h:89
#define M_PI
Definition math.h:45
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:33
static void transpose_3xSSE(const dt_colormatrix_t input, dt_colormatrix_t output)
Definition matrices.h:68
static void pack_3xSSE_to_3x4(const dt_colormatrix_t input, float output[12])
Definition matrices.h:149
static void dt_colormatrix_mul(dt_colormatrix_t dst, const dt_colormatrix_t m1, const dt_colormatrix_t m2)
Definition matrices.h:166
static int mat3SSEinv(dt_colormatrix_t dst, const dt_colormatrix_t src)
Definition matrices.h:36
size_t size
Definition mipmap_cache.c:3
float dt_aligned_pixel_t[4]
int dt_opencl_local_buffer_opt(const int devid, const int kernel, dt_opencl_local_buffer_t *factors)
Definition opencl.c:3286
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2164
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2580
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2504
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2058
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2360
int dt_opencl_write_buffer_to_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2348
int dt_opencl_enqueue_copy_image(const int devid, cl_mem src, cl_mem dst, size_t *orig_src, size_t *orig_dst, size_t *region)
Definition opencl.c:2289
int dt_opencl_read_buffer_from_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2337
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2101
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2155
int dt_opencl_enqueue_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2170
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
#define ROUNDUP(a, n)
Definition opencl.h:78
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
static float fmaxabsf(const float a, const float b)
static float clamp_simd(const float x)
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:39
#define eps
Definition rcd.c:81
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float sigma
const float r
const float const int flip
const float noise
int32_t num_openmp_threads
Definition darktable.h:786
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_colorspaces_t * color_profiles
Definition darktable.h:816
struct dt_bauhaus_t * bauhaus
Definition darktable.h:806
struct dt_develop_t * develop
Definition darktable.h:798
GdkRGBA graph_bg
Definition bauhaus.h:281
GdkRGBA graph_border
Definition bauhaus.h:281
PangoFontDescription * pango_font_desc
Definition bauhaus.h:274
float quad_width
Definition bauhaus.h:273
GdkRGBA graph_fg
Definition bauhaus.h:281
dt_colorspaces_color_profile_type_t softproof_type
dt_colorspaces_color_mode_t mode
char softproof_filename[512]
dt_iop_color_intent_t softproof_intent
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
dt_dev_pixelpipe_type_t type
struct dt_develop_t * dev
int32_t gui_attached
Definition develop.h:162
dt_image_t image_storage
Definition develop.h:259
GList * iop
Definition develop.h:285
gint scroll_mask
Definition gtk.h:230
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
dt_aligned_pixel_t M2
Definition filmicrgb.c:196
dt_aligned_pixel_t M4
Definition filmicrgb.c:196
dt_iop_filmicrgb_curve_type_t type[2]
Definition filmicrgb.c:200
dt_aligned_pixel_t M5
Definition filmicrgb.c:196
dt_aligned_pixel_t M1
Definition filmicrgb.c:196
dt_aligned_pixel_t M3
Definition filmicrgb.c:196
char softproof_filename[512]
Definition filmicrgb.c:370
float reconstruct_structure_vs_texture
Definition filmicrgb.c:347
dt_noise_distribution_t noise_distribution
Definition filmicrgb.c:364
dt_colorspaces_color_profile_type_t softproof_type
Definition filmicrgb.c:369
dt_colorspaces_color_mode_t softproof_mode
Definition filmicrgb.c:368
struct dt_iop_filmic_rgb_spline_t spline DT_ALIGNED_ARRAY
Definition filmicrgb.c:363
DTGTKCairoPaintIconFunc icon
Definition filmicrgb.c:280
GtkWidget * high_quality_reconstruction
Definition filmicrgb.c:310
GtkWidget * autoset_display_gamma
Definition filmicrgb.c:304
GtkWidget * reconstruct_grey_vs_color
Definition filmicrgb.c:290
dt_iop_filmicrgb_gui_button_data_t buttons[DT_FILMIC_GUI_BUTTON_LAST]
Definition filmicrgb.c:322
GtkWidget * reconstruct_bloom_vs_details
Definition filmicrgb.c:290
GtkWidget * reconstruct_threshold
Definition filmicrgb.c:290
GtkWidget * show_highlight_mask
Definition filmicrgb.c:292
GtkDrawingArea * area
Definition filmicrgb.c:314
GtkStyleContext * context
Definition filmicrgb.c:334
dt_iop_filmicrgb_gui_button_t active_button
Definition filmicrgb.c:321
dt_iop_filmic_rgb_gui_mode_t gui_mode
Definition filmicrgb.c:317
GtkWidget * reconstruct_structure_vs_texture
Definition filmicrgb.c:291
GtkWidget * reconstruct_feather
Definition filmicrgb.c:291
struct dt_iop_filmic_rgb_spline_t spline DT_ALIGNED_ARRAY
Definition filmicrgb.c:315
GtkWidget * compensate_icc_black
Definition filmicrgb.c:312
dt_iop_filmicrgb_curve_type_t shadows
Definition filmicrgb.c:248
dt_iop_filmicrgb_spline_version_type_t spline_version
Definition filmicrgb.c:251
dt_iop_filmicrgb_colorscience_type_t version
Definition filmicrgb.c:243
dt_iop_filmic_noise_distribution_t noise_distribution
Definition filmicrgb.c:247
dt_iop_filmicrgb_methods_type_t preserve_color
Definition filmicrgb.c:242
dt_iop_filmicrgb_curve_type_t highlights
Definition filmicrgb.c:249
dt_aligned_pixel_simd_t input[3]
Definition filmicrgb.c:2038
dt_aligned_pixel_simd_t output[3]
Definition filmicrgb.c:2039
dt_aligned_pixel_simd_t export_output[3]
Definition filmicrgb.c:2041
dt_aligned_pixel_simd_t export_input[3]
Definition filmicrgb.c:2040
dt_iop_global_data_t * data
Definition imageop.h:263
GtkDarktableToggleButton * off
Definition imageop.h:376
dt_iop_params_t * default_params
Definition imageop.h:344
GtkWidget * widget
Definition imageop.h:374
struct dt_develop_t * dev
Definition imageop.h:333
dt_iop_gui_data_t * gui_data
Definition imageop.h:348
dt_iop_global_data_t * global_data
Definition imageop.h:351
gboolean workflow_enabled
Definition imageop.h:342
gboolean enabled
Definition imageop.h:335
dt_aligned_pixel_t picked_color_min
Definition imageop.h:309
int request_mask_display
Definition imageop.h:298
dt_aligned_pixel_t picked_color_max
Definition imageop.h:309
dt_aligned_pixel_t picked_color
Definition imageop.h:309
dt_iop_params_t * params
Definition imageop.h:344
dt_colormatrix_t matrix_in_transposed
Definition iop_profile.h:65
Region of interest passed through the pixelpipe.
Definition imageop.h:72
#define E
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_TOGGLEBUTTON(obj)