File: agx.cl

package info (click to toggle)
darktable 5.4.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 64,644 kB
  • sloc: ansic: 367,957; cpp: 102,838; xml: 20,091; lisp: 15,092; sh: 3,767; javascript: 3,264; perl: 1,925; python: 1,551; ruby: 975; makefile: 543; asm: 46; sql: 38; awk: 21
file content (259 lines) | stat: -rw-r--r-- 9,786 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*
    This file is part of darktable,
    Copyright (C) 2025 darktable developers.

    darktable is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    darktable is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "common.h"
#include "colorspace.h"

#define _epsilon 1E-6f

// Must match tone_mapping_params_t in C code
typedef struct dt_iop_agx_tone_mapping_params_t
{
  float min_ev;
  float max_ev;
  float range_in_ev;
  float curve_gamma;
  float pivot_x;
  float pivot_y;
  float target_black;
  float toe_power;
  float toe_transition_x;
  float toe_transition_y;
  float toe_scale;
  int need_convex_toe;
  float toe_fallback_coefficient;
  float toe_fallback_power;
  float slope;
  float intercept;
  float target_white;
  float shoulder_power;
  float shoulder_transition_x;
  float shoulder_transition_y;
  float shoulder_scale;
  int need_concave_shoulder;
  float shoulder_fallback_coefficient;
  float shoulder_fallback_power;
  float look_offset;
  float look_slope;
  float look_power;
  float look_saturation;
  float look_original_hue_mix_ratio;
  int look_tuned;
  int restore_hue;
} dt_iop_agx_tone_mapping_params_t;

static inline void _agx_compress_into_gamut(float4 *pixel)
{
  const float luminance_coeffs[] = { 0.2658180370250449f, 0.59846986045365f, 0.1357121025213052f };
  const float input_y = pixel->x * luminance_coeffs[0] + pixel->y * luminance_coeffs[1] + pixel->z * luminance_coeffs[2];
  const float max_rgb = fmax(pixel->x, fmax(pixel->y, pixel->z));

  float4 opponent_rgb = max_rgb - (*pixel);
  const float opponent_y = opponent_rgb.x * luminance_coeffs[0] + opponent_rgb.y * luminance_coeffs[1] + opponent_rgb.z * luminance_coeffs[2];
  const float max_opponent = fmax(opponent_rgb.x, fmax(opponent_rgb.y, opponent_rgb.z));
  const float y_compensate_negative = max_opponent - opponent_y + input_y;

  const float min_rgb = fmin(pixel->x, fmin(pixel->y, pixel->z));
  const float offset = fmax(-min_rgb, 0.0f);
  float4 rgb_offset = (*pixel) + (float4)(offset, offset, offset, 0.0f);

  const float max_of_rgb_offset = fmax(rgb_offset.x, fmax(rgb_offset.y, rgb_offset.z));
  float4 opponent_rgb_offset = max_of_rgb_offset - rgb_offset;

  const float max_inverse_rgb_offset = fmax(opponent_rgb_offset.x, fmax(opponent_rgb_offset.y, opponent_rgb_offset.z));
  const float y_inverse_rgb_offset = opponent_rgb_offset.x * luminance_coeffs[0] + opponent_rgb_offset.y * luminance_coeffs[1] + opponent_rgb_offset.z * luminance_coeffs[2];
  float y_new = rgb_offset.x * luminance_coeffs[0] + rgb_offset.y * luminance_coeffs[1] + rgb_offset.z * luminance_coeffs[2];
  y_new = max_inverse_rgb_offset - y_inverse_rgb_offset + y_new;

    const float luminance_ratio =
      (y_new > y_compensate_negative && y_new > _epsilon)
      ? y_compensate_negative / y_new
      : 1.f;
  *pixel = luminance_ratio * rgb_offset;
}

static inline float _agx_apply_log_encoding(const float x, const float range_in_ev, const float min_ev)
{
  const float x_relative = fmax(_epsilon, x / 0.18f);
  const float mapped = (log2(fmax(x_relative, 0.0f)) - min_ev) / range_in_ev;
  return clipf(mapped);
}

static inline float _agx_sigmoid(const float x, const float power)
{
  return x / dtcl_pow(1.0f + dtcl_pow(x, power), 1.0f / power);
}

static inline float _agx_scaled_sigmoid(const float x, const float scale, const float slope, const float power, const float transition_x, const float transition_y)
{
  return scale * _agx_sigmoid(slope * (x - transition_x) / scale, power) + transition_y;
}

static inline float _agx_fallback_toe(const float x, const dt_iop_agx_tone_mapping_params_t *params)
{
    return x < 0.0f ? params->target_black : params->target_black + fmax(0.0f, params->toe_fallback_coefficient * dtcl_pow(x, params->toe_fallback_power));
}

static inline float _agx_fallback_shoulder(const float x, const dt_iop_agx_tone_mapping_params_t *params)
{
    return x >= 1.0f ? params->target_white : params->target_white - fmax(0.0f, params->shoulder_fallback_coefficient * dtcl_pow(1.0f - x, params->shoulder_fallback_power));
}

static inline float _agx_apply_curve(const float x, const dt_iop_agx_tone_mapping_params_t *params)
{
  float result = 0.0f;
  if(x < params->toe_transition_x)
  {
    result = params->need_convex_toe ? _agx_fallback_toe(x, params) : _agx_scaled_sigmoid(x, params->toe_scale, params->slope, params->toe_power, params->toe_transition_x, params->toe_transition_y);
  }
  else if(x <= params->shoulder_transition_x)
  {
    result = params->slope * x + params->intercept;
  }
  else
  {
    result = params->need_concave_shoulder ? _agx_fallback_shoulder(x, params) : _agx_scaled_sigmoid(x, params->shoulder_scale, params->slope, params->shoulder_power, params->shoulder_transition_x, params->shoulder_transition_y);
  }
  return clamp(result, params->target_black, params->target_white);
}

static inline float _agx_apply_slope_offset(const float x, const float slope, const float offset)
{
  const float m = slope / (1.0f + offset);
  const float b = offset * m;
  return m * x + b;
}

static inline float _agx_luminance_from_matrix(const float4 pixel, constant float *rendering_to_xyz)
{
    float4 xyz = matrix_product_float4(pixel, rendering_to_xyz);
    return xyz.y;
}

static inline void _agx_look(float4 *pixel, const dt_iop_agx_tone_mapping_params_t *params, constant float *rendering_to_xyz)
{
    const float slope = params->look_slope;
    const float offset = params->look_offset;
    const float power = params->look_power;
    const float sat = params->look_saturation;

    float4 temp;
    temp.x = _agx_apply_slope_offset(pixel->x, slope, offset);
    temp.y = _agx_apply_slope_offset(pixel->y, slope, offset);
    temp.z = _agx_apply_slope_offset(pixel->z, slope, offset);

    pixel->x = temp.x > 0.0f ? dtcl_pow(temp.x, power) : temp.x;
    pixel->y = temp.y > 0.0f ? dtcl_pow(temp.y, power) : temp.y;
    pixel->z = temp.z > 0.0f ? dtcl_pow(temp.z, power) : temp.z;

    const float luma = _agx_luminance_from_matrix(*pixel, rendering_to_xyz);

    pixel->x = luma + sat * (pixel->x - luma);
    pixel->y = luma + sat * (pixel->y - luma);
    pixel->z = luma + sat * (pixel->z - luma);
}

static inline float _agx_lerp_hue(const float original_hue, const float processed_hue, const float mix)
{
    const float shortest_distance = processed_hue - original_hue - rint(processed_hue - original_hue);
    const float mixed_hue = (1.0f - mix) * shortest_distance + original_hue;
    return mixed_hue - floor(mixed_hue);
}

static inline void _agx_tone_mapping(float4 *rgb_in_out, const dt_iop_agx_tone_mapping_params_t *params, constant float *rendering_to_xyz)
{
    float4 hsv_pixel = (float4)(0.0f, 0.0f, 0.0f, 0.0f);
    if(params->restore_hue)
    {
        hsv_pixel = RGB_2_HSV(*rgb_in_out);
    }
    const float h_before = hsv_pixel.x;

    float4 transformed_pixel;
    transformed_pixel.x = _agx_apply_curve(_agx_apply_log_encoding(rgb_in_out->x, params->range_in_ev, params->min_ev), params);
    transformed_pixel.y = _agx_apply_curve(_agx_apply_log_encoding(rgb_in_out->y, params->range_in_ev, params->min_ev), params);
    transformed_pixel.z = _agx_apply_curve(_agx_apply_log_encoding(rgb_in_out->z, params->range_in_ev, params->min_ev), params);
    transformed_pixel.w = rgb_in_out->w;

    if(params->look_tuned)
    {
        _agx_look(&transformed_pixel, params, rendering_to_xyz);
    }

    transformed_pixel.x = dtcl_pow(fmax(0.0f, transformed_pixel.x), params->curve_gamma);
    transformed_pixel.y = dtcl_pow(fmax(0.0f, transformed_pixel.y), params->curve_gamma);
    transformed_pixel.z = dtcl_pow(fmax(0.0f, transformed_pixel.z), params->curve_gamma);

    if(params->restore_hue)
    {
        hsv_pixel = RGB_2_HSV(transformed_pixel);
        float h_after = hsv_pixel.x;
        h_after = _agx_lerp_hue(h_before, h_after, params->look_original_hue_mix_ratio);
        hsv_pixel.x = h_after;
        *rgb_in_out = HSV_2_RGB(hsv_pixel);
    }
    else
    {
        *rgb_in_out = transformed_pixel;
    }
}

__kernel void kernel_agx(
    read_only image2d_t input,
    write_only image2d_t output,
    const int width,
    const int height,
    const dt_iop_agx_tone_mapping_params_t params,
    constant float *pipe_to_base,
    constant float *base_to_rendering,
    constant float *rendering_to_pipe,
    constant float *rendering_to_xyz,
    const int base_working_same_profile
)
{
    const int i = get_global_id(0);
    const int j = get_global_id(1);
    if(i >= width || j >= height) return;

    const int2 pos = (int2)(i, j);
    float4 in_pixel = read_imagef(input, sampleri, pos);

    // sanitize input range and get rid of NaNs
    in_pixel = select(clamp(in_pixel, -1e6f, 1e6f), (float4)(0.0f), isnan(in_pixel));

    float4 base_rgb;
    if(base_working_same_profile)
    {
        base_rgb = in_pixel;
    }
    else
    {
        base_rgb = matrix_product_float4(in_pixel, pipe_to_base);
    }

    _agx_compress_into_gamut(&base_rgb);

    float4 rendering_rgb = matrix_product_float4(base_rgb, base_to_rendering);

    _agx_tone_mapping(&rendering_rgb, &params, rendering_to_xyz);

    float4 out_pixel = matrix_product_float4(rendering_rgb, rendering_to_pipe);

    out_pixel.w = in_pixel.w;
    write_imagef(output, pos, out_pixel);
}