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