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