Skip to content

Commit 8df9feb

Browse files
authored
Merge pull request #19525 from kofa73/agx-opencl
agx OpenCL implementation
2 parents 499cf5a + f9ff0b9 commit 8df9feb

File tree

3 files changed

+394
-7
lines changed

3 files changed

+394
-7
lines changed

data/kernels/agx.cl

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
This file is part of darktable,
3+
Copyright (C) 2025 darktable developers.
4+
5+
darktable is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
darktable is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with darktable. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "common.h"
20+
#include "colorspace.h"
21+
22+
#define _epsilon 1E-6f
23+
24+
// Must match tone_mapping_params_t in C code
25+
typedef struct dt_iop_agx_tone_mapping_params_t
26+
{
27+
float min_ev;
28+
float max_ev;
29+
float range_in_ev;
30+
float curve_gamma;
31+
float pivot_x;
32+
float pivot_y;
33+
float target_black;
34+
float toe_power;
35+
float toe_transition_x;
36+
float toe_transition_y;
37+
float toe_scale;
38+
int need_convex_toe;
39+
float toe_fallback_coefficient;
40+
float toe_fallback_power;
41+
float slope;
42+
float intercept;
43+
float target_white;
44+
float shoulder_power;
45+
float shoulder_transition_x;
46+
float shoulder_transition_y;
47+
float shoulder_scale;
48+
int need_concave_shoulder;
49+
float shoulder_fallback_coefficient;
50+
float shoulder_fallback_power;
51+
float look_offset;
52+
float look_slope;
53+
float look_power;
54+
float look_saturation;
55+
float look_original_hue_mix_ratio;
56+
int look_tuned;
57+
int restore_hue;
58+
} dt_iop_agx_tone_mapping_params_t;
59+
60+
static inline void _agx_compress_into_gamut(float4 *pixel)
61+
{
62+
const float luminance_coeffs[] = { 0.2658180370250449f, 0.59846986045365f, 0.1357121025213052f };
63+
const float input_y = pixel->x * luminance_coeffs[0] + pixel->y * luminance_coeffs[1] + pixel->z * luminance_coeffs[2];
64+
const float max_rgb = fmax(pixel->x, fmax(pixel->y, pixel->z));
65+
66+
float4 opponent_rgb = max_rgb - (*pixel);
67+
const float opponent_y = opponent_rgb.x * luminance_coeffs[0] + opponent_rgb.y * luminance_coeffs[1] + opponent_rgb.z * luminance_coeffs[2];
68+
const float max_opponent = fmax(opponent_rgb.x, fmax(opponent_rgb.y, opponent_rgb.z));
69+
const float y_compensate_negative = max_opponent - opponent_y + input_y;
70+
71+
const float min_rgb = fmin(pixel->x, fmin(pixel->y, pixel->z));
72+
const float offset = fmax(-min_rgb, 0.0f);
73+
float4 rgb_offset = (*pixel) + (float4)(offset, offset, offset, 0.0f);
74+
75+
const float max_of_rgb_offset = fmax(rgb_offset.x, fmax(rgb_offset.y, rgb_offset.z));
76+
float4 opponent_rgb_offset = max_of_rgb_offset - rgb_offset;
77+
78+
const float max_inverse_rgb_offset = fmax(opponent_rgb_offset.x, fmax(opponent_rgb_offset.y, opponent_rgb_offset.z));
79+
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];
80+
float y_new = rgb_offset.x * luminance_coeffs[0] + rgb_offset.y * luminance_coeffs[1] + rgb_offset.z * luminance_coeffs[2];
81+
y_new = max_inverse_rgb_offset - y_inverse_rgb_offset + y_new;
82+
83+
const float luminance_ratio =
84+
(y_new > y_compensate_negative && y_new > _epsilon)
85+
? y_compensate_negative / y_new
86+
: 1.f;
87+
*pixel = luminance_ratio * rgb_offset;
88+
}
89+
90+
static inline float _agx_apply_log_encoding(const float x, const float range_in_ev, const float min_ev)
91+
{
92+
const float x_relative = fmax(_epsilon, x / 0.18f);
93+
const float mapped = (log2(fmax(x_relative, 0.0f)) - min_ev) / range_in_ev;
94+
return clipf(mapped);
95+
}
96+
97+
static inline float _agx_sigmoid(const float x, const float power)
98+
{
99+
return x / dtcl_pow(1.0f + dtcl_pow(x, power), 1.0f / power);
100+
}
101+
102+
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)
103+
{
104+
return scale * _agx_sigmoid(slope * (x - transition_x) / scale, power) + transition_y;
105+
}
106+
107+
static inline float _agx_fallback_toe(const float x, const dt_iop_agx_tone_mapping_params_t *params)
108+
{
109+
return x < 0.0f ? params->target_black : params->target_black + fmax(0.0f, params->toe_fallback_coefficient * dtcl_pow(x, params->toe_fallback_power));
110+
}
111+
112+
static inline float _agx_fallback_shoulder(const float x, const dt_iop_agx_tone_mapping_params_t *params)
113+
{
114+
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));
115+
}
116+
117+
static inline float _agx_apply_curve(const float x, const dt_iop_agx_tone_mapping_params_t *params)
118+
{
119+
float result = 0.0f;
120+
if(x < params->toe_transition_x)
121+
{
122+
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);
123+
}
124+
else if(x <= params->shoulder_transition_x)
125+
{
126+
result = params->slope * x + params->intercept;
127+
}
128+
else
129+
{
130+
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);
131+
}
132+
return clamp(result, params->target_black, params->target_white);
133+
}
134+
135+
static inline float _agx_apply_slope_offset(const float x, const float slope, const float offset)
136+
{
137+
const float m = slope / (1.0f + offset);
138+
const float b = offset * m;
139+
return m * x + b;
140+
}
141+
142+
static inline float _agx_luminance_from_matrix(const float4 pixel, constant float *rendering_to_xyz)
143+
{
144+
float4 xyz = matrix_product_float4(pixel, rendering_to_xyz);
145+
return xyz.y;
146+
}
147+
148+
static inline void _agx_look(float4 *pixel, const dt_iop_agx_tone_mapping_params_t *params, constant float *rendering_to_xyz)
149+
{
150+
const float slope = params->look_slope;
151+
const float offset = params->look_offset;
152+
const float power = params->look_power;
153+
const float sat = params->look_saturation;
154+
155+
float4 temp;
156+
temp.x = _agx_apply_slope_offset(pixel->x, slope, offset);
157+
temp.y = _agx_apply_slope_offset(pixel->y, slope, offset);
158+
temp.z = _agx_apply_slope_offset(pixel->z, slope, offset);
159+
160+
pixel->x = temp.x > 0.0f ? dtcl_pow(temp.x, power) : temp.x;
161+
pixel->y = temp.y > 0.0f ? dtcl_pow(temp.y, power) : temp.y;
162+
pixel->z = temp.z > 0.0f ? dtcl_pow(temp.z, power) : temp.z;
163+
164+
const float luma = _agx_luminance_from_matrix(*pixel, rendering_to_xyz);
165+
166+
pixel->x = luma + sat * (pixel->x - luma);
167+
pixel->y = luma + sat * (pixel->y - luma);
168+
pixel->z = luma + sat * (pixel->z - luma);
169+
}
170+
171+
static inline float _agx_lerp_hue(const float original_hue, const float processed_hue, const float mix)
172+
{
173+
const float shortest_distance = processed_hue - original_hue - rint(processed_hue - original_hue);
174+
const float mixed_hue = (1.0f - mix) * shortest_distance + original_hue;
175+
return mixed_hue - floor(mixed_hue);
176+
}
177+
178+
static inline void _agx_tone_mapping(float4 *rgb_in_out, const dt_iop_agx_tone_mapping_params_t *params, constant float *rendering_to_xyz)
179+
{
180+
float4 hsv_pixel = (float4)(0.0f, 0.0f, 0.0f, 0.0f);
181+
if(params->restore_hue)
182+
{
183+
hsv_pixel = RGB_2_HSV(*rgb_in_out);
184+
}
185+
const float h_before = hsv_pixel.x;
186+
187+
float4 transformed_pixel;
188+
transformed_pixel.x = _agx_apply_curve(_agx_apply_log_encoding(rgb_in_out->x, params->range_in_ev, params->min_ev), params);
189+
transformed_pixel.y = _agx_apply_curve(_agx_apply_log_encoding(rgb_in_out->y, params->range_in_ev, params->min_ev), params);
190+
transformed_pixel.z = _agx_apply_curve(_agx_apply_log_encoding(rgb_in_out->z, params->range_in_ev, params->min_ev), params);
191+
transformed_pixel.w = rgb_in_out->w;
192+
193+
if(params->look_tuned)
194+
{
195+
_agx_look(&transformed_pixel, params, rendering_to_xyz);
196+
}
197+
198+
transformed_pixel.x = dtcl_pow(fmax(0.0f, transformed_pixel.x), params->curve_gamma);
199+
transformed_pixel.y = dtcl_pow(fmax(0.0f, transformed_pixel.y), params->curve_gamma);
200+
transformed_pixel.z = dtcl_pow(fmax(0.0f, transformed_pixel.z), params->curve_gamma);
201+
202+
if(params->restore_hue)
203+
{
204+
hsv_pixel = RGB_2_HSV(transformed_pixel);
205+
float h_after = hsv_pixel.x;
206+
h_after = _agx_lerp_hue(h_before, h_after, params->look_original_hue_mix_ratio);
207+
hsv_pixel.x = h_after;
208+
*rgb_in_out = HSV_2_RGB(hsv_pixel);
209+
}
210+
else
211+
{
212+
*rgb_in_out = transformed_pixel;
213+
}
214+
}
215+
216+
__kernel void kernel_agx(
217+
read_only image2d_t input,
218+
write_only image2d_t output,
219+
const int width,
220+
const int height,
221+
const dt_iop_agx_tone_mapping_params_t params,
222+
constant float *pipe_to_base,
223+
constant float *base_to_rendering,
224+
constant float *rendering_to_pipe,
225+
constant float *rendering_to_xyz,
226+
const int base_working_same_profile
227+
)
228+
{
229+
const int i = get_global_id(0);
230+
const int j = get_global_id(1);
231+
if(i >= width || j >= height) return;
232+
233+
const int2 pos = (int2)(i, j);
234+
float4 in_pixel = read_imagef(input, sampleri, pos);
235+
236+
// sanitize input range and get rid of NaNs
237+
in_pixel = select(clamp(in_pixel, -1e6f, 1e6f), (float4)(0.0f), isnan(in_pixel));
238+
239+
float4 base_rgb;
240+
if(base_working_same_profile)
241+
{
242+
base_rgb = in_pixel;
243+
}
244+
else
245+
{
246+
base_rgb = matrix_product_float4(in_pixel, pipe_to_base);
247+
}
248+
249+
_agx_compress_into_gamut(&base_rgb);
250+
251+
float4 rendering_rgb = matrix_product_float4(base_rgb, base_to_rendering);
252+
253+
_agx_tone_mapping(&rendering_rgb, &params, rendering_to_xyz);
254+
255+
float4 out_pixel = matrix_product_float4(rendering_rgb, rendering_to_pipe);
256+
257+
out_pixel.w = in_pixel.w;
258+
write_imagef(output, pos, out_pixel);
259+
}

data/kernels/programs.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ bspline.cl 35
3939
sigmoid.cl 36
4040
colorequal.cl 37
4141
capture.cl 38
42+
agx.cl 39

0 commit comments

Comments
 (0)