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