Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
focus_peaking.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2019-2020, 2023, 2025-2026 Aurélien PIERRE.
4 Copyright (C) 2020-2021 Hubert Kowalski.
5 Copyright (C) 2020-2021 Pascal Obry.
6 Copyright (C) 2020-2021 Ralf Brown.
7 Copyright (C) 2021 luzpaz.
8 Copyright (C) 2022 Martin Bařinka.
9 Copyright (C) 2022 Sakari Kapanen.
10 Copyright (C) 2023 Luca Zulberti.
11 Copyright (C) 2024 Alynx Zhou.
12
13 darktable is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 darktable is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with darktable. If not, see <http://www.gnu.org/licenses/>.
25*/
26
27#include "system/macros.h"
28#include <math.h>
30#include "system/openmp.h"
31#include "system/simd.h"
32
33#include "pixel/eigf.h"
34#include "system/mem_alloc.h"
35#include "math/openmp_maths.h"
36
38static inline float uint8_to_float(const uint8_t i)
39{
40 return (float)i / 255.0f;
41}
42
43int dt_focuspeaking(cairo_t *cr,
44 uint8_t *const restrict image,
45 const int buf_width, const int buf_height,
46 gboolean draw,
47 float *x, float *y)
48{
49 float *const restrict luma = dt_alloc_align(sizeof(float) * (size_t)buf_width * buf_height);
50 float *const restrict luma_ds = dt_alloc_align(sizeof(float) * (size_t)buf_width * buf_height);
51 uint8_t *restrict focus_peaking = NULL;
52 int err = 0;
53 if(IS_NULL_PTR(luma_ds) || IS_NULL_PTR(luma))
54 {
55 err = 1;
56 goto error_early;
57 }
58
59 const size_t npixels = (size_t)buf_height * buf_width;
60 // Create a luma buffer as the euclidian norm of RGB channels
61 __OMP_PARALLEL_FOR_SIMD__(aligned(image, luma:64))
62 for(size_t index = 0; index < npixels; index++)
63 {
64 const size_t index_RGB = index * 4;
65
66 // remove gamma 2.2 and take the square is equivalent to this:
67 const float exponent = 2.0f * 2.2f;
68
69 luma[index] = sqrtf( powf(uint8_to_float(image[index_RGB]), exponent) +
70 powf(uint8_to_float(image[index_RGB + 1]), exponent) +
71 powf(uint8_to_float(image[index_RGB + 2]), exponent) );
72 }
73
74 // Prefilter noise
75 if(fast_eigf_surface_blur(luma, buf_width, buf_height, 12, 0.00005f, 4, DT_GF_BLENDING_LINEAR, 1, 0.0f, exp2f(-8.0f), 1.0f) != 0)
76 {
77 err = 1;
78 goto error_early;
79 }
80
81 // Compute the laplacian of a gaussian
82 float mass = 0.f;
83 float x_integral = 0.f;
84 float y_integral = 0.f;
85 __OMP_PARALLEL_FOR__(collapse(2) reduction(+:mass, x_integral, y_integral))
86 for(size_t i = 0; i < buf_height; ++i)
87 for(size_t j = 0; j < buf_width; ++j)
88 {
89 size_t index = i * buf_width + j;
90 if(i < 8 || i >= buf_height - 8 || j < 8 || j > buf_width - 8)
91 {
92 // ensure defined value for borders
93 luma_ds[index] = 0.0f;
94 }
95 else
96 {
97 // Laplacian of a Gaussian kernel with sigma = 1.05
98 static const float kernel[7][7]
99 = { { 0.00053449f, 0.00352729f, 0.00992912f, 0.01362207f, 0.00992912f, 0.00352729f, 0.00053449f },
100 { 0.00352729f, 0.01828379f, 0.03437727f, 0.03474665f, 0.03437727f, 0.01828379f, 0.00352729f },
101 { 0.00992912f, 0.03437727f, -0.00982925f, -0.09093110f, -0.00982925f, 0.03437727f, 0.00992912f },
102 { 0.01362207f, 0.03474665f, -0.09093110f, -0.26187433f, -0.09093110f, 0.03474665f, 0.01362207f },
103 { 0.00992912f, 0.03437727f, -0.00982925f, -0.09093110f, -0.00982925f, 0.03437727f, 0.00992912f },
104 { 0.00352729f, 0.01828379f, 0.03437727f, 0.03474665f, 0.03437727f, 0.01828379f, 0.00352729f },
105 { 0.00053449f, 0.00352729f, 0.00992912f, 0.01362207f, 0.00992912f, 0.00352729f, 0.00053449f } };
106
107 // The close laplacian is the local-local contrast
108 // The far laplacian is the far local contrast, sampled 2 times farther in an a-trous fashion.
109 // If far / 2 = close, we are on a slowly-varying gradient, aka on a contrasted edge that is not sharp.
110 float laplacian_close = 0.f;
111 float laplacian_far = 0.f;
112
113 for(int ii = 0; ii < 7; ii++)
114 for(int jj = 0; jj < 7; jj++)
115 {
116 laplacian_close += luma[(i - 3 + ii) * buf_width + (j - 3 + jj)] * kernel[ii][jj];
117 laplacian_far += luma[(i + (-3 + ii) * 2) * buf_width + (j + (-3 + jj) * 2)] * kernel[ii][jj];
118 }
119
120 // gradient on principal directions
121 const float gradient_1_y = (luma[(i - 2) * buf_width + (j)] - luma[(i + 2) * buf_width + (j)]) / 4.f;
122 const float gradient_1_x = (luma[(i) * buf_width + (j - 2)] - luma[(i) * buf_width + (j + 2)]) / 4.f;
123 const float TV_1 = dt_fast_hypotf(gradient_1_x, gradient_1_y);
124
125 // gradient on diagonals
126 const float gradient_2_y = (luma[(i - 2) * buf_width + (j - 2)] - luma[(i + 2) * buf_width + (j + 2)]) / (2.f * sqrtf(2.f));
127 const float gradient_2_x = (luma[(i - 2) * buf_width + (j + 2)] - luma[(i + 2) * buf_width + (j - 2)]) / (2.f * sqrtf(2.f));
128 const float TV_2 = dt_fast_hypotf(gradient_2_x, gradient_2_y);
129
130 // gradient on principal directions
131 const float gradient_3_y = (luma[(i - 1) * buf_width + (j)] - luma[(i + 1) * buf_width + (j)]) / 2.f;
132 const float gradient_3_x = (luma[(i) * buf_width + (j - 1)] - luma[(i) * buf_width + (j + 1)]) / 2.f;
133 const float TV_3 = dt_fast_hypotf(gradient_3_x, gradient_3_y);
134
135 // gradient on diagonals
136 const float gradient_4_y = (luma[(i - 1) * buf_width + (j - 1)] - luma[(i + 1) * buf_width + (j + 1)]) / (sqrtf(2.f));
137 const float gradient_4_x = (luma[(i - 1) * buf_width + (j + 1)] - luma[(i + 1) * buf_width + (j - 1)]) / (sqrtf(2.f));
138 const float TV_4 = dt_fast_hypotf(gradient_4_x, gradient_4_y);
139
140 // Total Variation = norm(grad_x, grad_y). We use it as a metric of global contrast since it doesn't use the current pixel.
141 // Laplacian = div(grad). We use it as a metric of local contrast, aka difference with current pixel and local average value.
142 // The ratio of both is meant to catch local contrast NOT correlated with global contrast, aka sharp edges.
143 // The TV is averaged from both directions, its coeff is made-up to balance local contrast detection.
144 const float TV = 100.f * (TV_1 + TV_2 + TV_3 + TV_4) / 4.f;
145 luma_ds[index] = (laplacian_close > 1e-15f) ? fmaxf(fabsf(laplacian_close) - 0.5f * fabsf(laplacian_far), 0.f) / (TV + 1.f) : 0.f;
146
147 // Compute the mass and integrals over x and y
148 mass += luma_ds[index];
149 x_integral += ((float)j) * luma_ds[index];
150 y_integral += ((float)i) * luma_ds[index];
151 }
152 }
153
154 // Compute the coordinates of the details barycenter
155 if(x) *x = CLAMP(x_integral / mass, 0, buf_height);
156 if(y) *y = CLAMP(y_integral / mass, 0, buf_height);
157
158 // Stop there if no drawing is requested
159 if(!draw)
160 {
161 dt_free_align(luma);
162 dt_free_align(luma_ds);
163 return 0;
164 }
165
166 // Plain aligned allocation: this is a transient GUI overlay buffer, not pipeline memory,
167 // so it has no business being charged against the pixelpipe cache budget.
168 focus_peaking = dt_alloc_align(sizeof(uint8_t) * buf_width * buf_height * 4);
169 if(IS_NULL_PTR(focus_peaking))
170 {
171 err = 1;
172 goto error;
173 }
174
175 // Dilate the mask to improve connectivity
176 __OMP_PARALLEL_FOR__(collapse(2))
177 for(size_t i = 0; i < buf_height; ++i)
178 for(size_t j = 0; j < buf_width; ++j)
179 {
180 size_t index = i * buf_width + j;
181 if(i < 8 || i >= buf_height - 8 || j < 8 || j > buf_width - 8)
182 {
183 // ensure defined value for borders
184 luma[index] = 0.0f;
185 }
186 else
187 {
188 // Dilating kernel
189 static const float kernel[3][3] = { { 1.f } };
190 luma[index] = 0.f;
191 for(int ii = 0; ii < 3; ii++)
192 for(int jj = 0; jj < 3; jj++)
193 luma[index] += luma_ds[(i - 1 + ii) * buf_width + (j - 1 + jj)] * kernel[ii][jj];
194 }
195 }
196
197 // Anti-aliasing
198 if(dt_box_mean(luma, buf_height, buf_width, 1, 3, 1) != 0)
199 {
200 err = 1;
201 goto error;
202 }
203
204 // Postfilter to connect isolated dots and draw lines
205 if(fast_eigf_surface_blur(luma, buf_width, buf_height, 12, 0.000005f, 1, DT_GF_BLENDING_LINEAR, 1, 0.0f, exp2f(-8.0f), 1.0f) != 0)
206 {
207 err = 1;
208 goto error;
209 }
210
211 // Compute the laplacian mean over the picture
212 float TV_sum = 0.0f;
213 __OMP_PARALLEL_FOR_SIMD__(collapse(2) aligned(luma:64) reduction(+:TV_sum))
214 for(size_t i = 8; i < buf_height - 8; ++i)
215 for(size_t j = 8; j < buf_width - 8; ++j)
216 TV_sum += luma[i * buf_width + j] / ((float)(buf_height - 16) * (float)(buf_width - 16));
217
218 // Compute the standard deviation
219 float sigma = 0.0f;
220 __OMP_PARALLEL_FOR_SIMD__(collapse(2) aligned(focus_peaking, luma:64) reduction(+:sigma))
221 for(size_t i = 8; i < buf_height - 8; ++i)
222 for(size_t j = 8; j < buf_width - 8; ++j)
223 sigma += sqf(luma[i * buf_width + j] - TV_sum) / ((float)(buf_height - 16) * (float)(buf_width - 16));
224
225 sigma = sqrtf(sigma);
226
227 // Set the sharpness thresholds
228 const float six_sigma = TV_sum + 4.f * sigma;
229 const float four_sigma = TV_sum + 3.f * sigma;
230 const float two_sigma = TV_sum + 2.f * sigma;
231
232 // Prepare the focus-peaking image overlay
233 __OMP_PARALLEL_FOR__(collapse(2))
234 for(size_t i = 0; i < buf_height; ++i)
235 for(size_t j = 0; j < buf_width; ++j)
236 {
237 static const uint8_t yellow[4] = { 0, 255, 255, 255 };
238 static const uint8_t green[4] = { 0, 255, 0, 255 };
239 static const uint8_t blue[4] = { 255, 0, 0, 255 };
240
241 const size_t index = (i * buf_width + j) * 4;
242 const float TV = luma[(i * buf_width + j)];
243
244 if(TV > six_sigma)
245 {
246 // Very sharp : paint yellow, BGR = (0, 255, 255)
247 for_four_channels(c) focus_peaking[index + c] = yellow[c];
248 }
249 else if(TV > four_sigma)
250 {
251 // Mediun sharp : paint green, BGR = (0, 255, 0)
252 for_four_channels(c) focus_peaking[index + c] = green[c];
253 }
254 else if(TV > two_sigma)
255 {
256 // Little sharp : paint blue, BGR = (255, 0, 0)
257 for_four_channels(c) focus_peaking[index + c] = blue[c];
258 }
259 else
260 {
261 // Not sharp enough : paint 0
262 for_four_channels(c) focus_peaking[index + c] = 0;
263 }
264 }
265
266 // draw the focus peaking overlay
267 cairo_save(cr);
268 cairo_rectangle(cr, 0, 0, buf_width, buf_height);
269 cairo_surface_t *surface = cairo_image_surface_create_for_data((unsigned char *)focus_peaking,
270 CAIRO_FORMAT_ARGB32,
271 buf_width, buf_height,
272 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, buf_width));
273 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
274 cairo_set_source_surface(cr, surface, 0.0, 0.0);
275 cairo_pattern_set_filter(cairo_get_source (cr), dt_widget_image_filter());
276 cairo_fill(cr);
277 cairo_restore(cr);
278
279 // cleanup
280 cairo_surface_destroy(surface);
281
282error:
283 dt_free_align(focus_peaking);
284error_early:
285 dt_free_align(luma);
286 dt_free_align(luma_ds);
287 return err;
288}
static void error(char *msg)
Definition ashift_lsd.c:202
int dt_box_mean(float *const buf, const size_t height, const size_t width, const int ch, const int radius, const unsigned iterations)
static const float x
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
static __DT_CLONE_TARGETS__ int fast_eigf_surface_blur(float *const restrict image, const size_t width, const size_t height, const float sigma, float feathering, const int iterations, const dt_iop_guided_filter_blending_t filter, const float scale, const float quantization, const float quantize_min, const float quantize_max)
Definition eigf.h:262
@ DT_GF_BLENDING_LINEAR
int dt_focuspeaking(cairo_t *cr, uint8_t *const restrict image, const int buf_width, const int buf_height, gboolean draw, float *x, float *y)
static float uint8_to_float(const uint8_t i)
static float kernel(const float *x, const float *y)
#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 macros.h:96
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:100
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
#define for_four_channels(_var,...)
Definition simd.h:89
static const dt_aligned_pixel_simd_t exponent
Definition simd.h:127
const float sigma
cairo_filter_t dt_widget_image_filter(void)