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