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 "common/eigf.h"
29
31static inline float uint8_to_float(const uint8_t i)
32{
33 return (float)i / 255.0f;
34}
35
36int dt_focuspeaking(cairo_t *cr,
37 uint8_t *const restrict image,
38 const int buf_width, const int buf_height,
39 gboolean draw,
40 float *x, float *y)
41{
42 float *const restrict luma = dt_pixelpipe_cache_alloc_align_float_cache((size_t)buf_width * buf_height, 0);
43 float *const restrict luma_ds = dt_pixelpipe_cache_alloc_align_float_cache((size_t)buf_width * buf_height, 0);
44 uint8_t *restrict focus_peaking = NULL;
45 int err = 0;
46 if(IS_NULL_PTR(luma_ds) || IS_NULL_PTR(luma))
47 {
48 err = 1;
49 goto error_early;
50 }
51
52 const size_t npixels = (size_t)buf_height * buf_width;
53 // Create a luma buffer as the euclidian norm of RGB channels
54 __OMP_PARALLEL_FOR_SIMD__(aligned(image, luma:64))
55 for(size_t index = 0; index < npixels; index++)
56 {
57 const size_t index_RGB = index * 4;
58
59 // remove gamma 2.2 and take the square is equivalent to this:
60 const float exponent = 2.0f * 2.2f;
61
62 luma[index] = sqrtf( powf(uint8_to_float(image[index_RGB]), exponent) +
63 powf(uint8_to_float(image[index_RGB + 1]), exponent) +
64 powf(uint8_to_float(image[index_RGB + 2]), exponent) );
65 }
66
67 // Prefilter noise
68 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)
69 {
70 err = 1;
71 goto error_early;
72 }
73
74 // Compute the laplacian of a gaussian
75 float mass = 0.f;
76 float x_integral = 0.f;
77 float y_integral = 0.f;
78 __OMP_PARALLEL_FOR__(collapse(2) reduction(+:mass, x_integral, y_integral))
79 for(size_t i = 0; i < buf_height; ++i)
80 for(size_t j = 0; j < buf_width; ++j)
81 {
82 size_t index = i * buf_width + j;
83 if(i < 8 || i >= buf_height - 8 || j < 8 || j > buf_width - 8)
84 {
85 // ensure defined value for borders
86 luma_ds[index] = 0.0f;
87 }
88 else
89 {
90 // Laplacian of a Gaussian kernel with sigma = 1.05
91 static const float kernel[7][7]
92 = { { 0.00053449f, 0.00352729f, 0.00992912f, 0.01362207f, 0.00992912f, 0.00352729f, 0.00053449f },
93 { 0.00352729f, 0.01828379f, 0.03437727f, 0.03474665f, 0.03437727f, 0.01828379f, 0.00352729f },
94 { 0.00992912f, 0.03437727f, -0.00982925f, -0.09093110f, -0.00982925f, 0.03437727f, 0.00992912f },
95 { 0.01362207f, 0.03474665f, -0.09093110f, -0.26187433f, -0.09093110f, 0.03474665f, 0.01362207f },
96 { 0.00992912f, 0.03437727f, -0.00982925f, -0.09093110f, -0.00982925f, 0.03437727f, 0.00992912f },
97 { 0.00352729f, 0.01828379f, 0.03437727f, 0.03474665f, 0.03437727f, 0.01828379f, 0.00352729f },
98 { 0.00053449f, 0.00352729f, 0.00992912f, 0.01362207f, 0.00992912f, 0.00352729f, 0.00053449f } };
99
100 // The close laplacian is the local-local contrast
101 // The far laplacian is the far local contrast, sampled 2 times farther in an a-trous fashion.
102 // If far / 2 = close, we are on a slowly-varying gradient, aka on a contrasted edge that is not sharp.
103 float laplacian_close = 0.f;
104 float laplacian_far = 0.f;
105
106 for(int ii = 0; ii < 7; ii++)
107 for(int jj = 0; jj < 7; jj++)
108 {
109 laplacian_close += luma[(i - 3 + ii) * buf_width + (j - 3 + jj)] * kernel[ii][jj];
110 laplacian_far += luma[(i + (-3 + ii) * 2) * buf_width + (j + (-3 + jj) * 2)] * kernel[ii][jj];
111 }
112
113 // gradient on principal directions
114 const float gradient_1_y = (luma[(i - 2) * buf_width + (j)] - luma[(i + 2) * buf_width + (j)]) / 4.f;
115 const float gradient_1_x = (luma[(i) * buf_width + (j - 2)] - luma[(i) * buf_width + (j + 2)]) / 4.f;
116 const float TV_1 = dt_fast_hypotf(gradient_1_x, gradient_1_y);
117
118 // gradient on diagonals
119 const float gradient_2_y = (luma[(i - 2) * buf_width + (j - 2)] - luma[(i + 2) * buf_width + (j + 2)]) / (2.f * sqrtf(2.f));
120 const float gradient_2_x = (luma[(i - 2) * buf_width + (j + 2)] - luma[(i + 2) * buf_width + (j - 2)]) / (2.f * sqrtf(2.f));
121 const float TV_2 = dt_fast_hypotf(gradient_2_x, gradient_2_y);
122
123 // gradient on principal directions
124 const float gradient_3_y = (luma[(i - 1) * buf_width + (j)] - luma[(i + 1) * buf_width + (j)]) / 2.f;
125 const float gradient_3_x = (luma[(i) * buf_width + (j - 1)] - luma[(i) * buf_width + (j + 1)]) / 2.f;
126 const float TV_3 = dt_fast_hypotf(gradient_3_x, gradient_3_y);
127
128 // gradient on diagonals
129 const float gradient_4_y = (luma[(i - 1) * buf_width + (j - 1)] - luma[(i + 1) * buf_width + (j + 1)]) / (sqrtf(2.f));
130 const float gradient_4_x = (luma[(i - 1) * buf_width + (j + 1)] - luma[(i + 1) * buf_width + (j - 1)]) / (sqrtf(2.f));
131 const float TV_4 = dt_fast_hypotf(gradient_4_x, gradient_4_y);
132
133 // Total Variation = norm(grad_x, grad_y). We use it as a metric of global contrast since it doesn't use the current pixel.
134 // Laplacian = div(grad). We use it as a metric of local contrast, aka difference with current pixel and local average value.
135 // The ratio of both is meant to catch local contrast NOT correlated with global contrast, aka sharp edges.
136 // The TV is averaged from both directions, its coeff is made-up to balance local contrast detection.
137 const float TV = 100.f * (TV_1 + TV_2 + TV_3 + TV_4) / 4.f;
138 luma_ds[index] = (laplacian_close > 1e-15f) ? fmaxf(fabsf(laplacian_close) - 0.5f * fabsf(laplacian_far), 0.f) / (TV + 1.f) : 0.f;
139
140 // Compute the mass and integrals over x and y
141 mass += luma_ds[index];
142 x_integral += ((float)j) * luma_ds[index];
143 y_integral += ((float)i) * luma_ds[index];
144 }
145 }
146
147 // Compute the coordinates of the details barycenter
148 if(x) *x = CLAMP(x_integral / mass, 0, buf_height);
149 if(y) *y = CLAMP(y_integral / mass, 0, buf_height);
150
151 // Stop there if no drawing is requested
152 if(!draw)
153 {
156 return 0;
157 }
158
160 sizeof(uint8_t) * buf_width * buf_height * 4,
161 0);
162 if(IS_NULL_PTR(focus_peaking))
163 {
164 err = 1;
165 goto error;
166 }
167
168 // Dilate the mask to improve connectivity
169 __OMP_PARALLEL_FOR__(collapse(2))
170 for(size_t i = 0; i < buf_height; ++i)
171 for(size_t j = 0; j < buf_width; ++j)
172 {
173 size_t index = i * buf_width + j;
174 if(i < 8 || i >= buf_height - 8 || j < 8 || j > buf_width - 8)
175 {
176 // ensure defined value for borders
177 luma[index] = 0.0f;
178 }
179 else
180 {
181 // Dilating kernel
182 static const float kernel[3][3] = { { 1.f } };
183 luma[index] = 0.f;
184 for(int ii = 0; ii < 3; ii++)
185 for(int jj = 0; jj < 3; jj++)
186 luma[index] += luma_ds[(i - 1 + ii) * buf_width + (j - 1 + jj)] * kernel[ii][jj];
187 }
188 }
189
190 // Anti-aliasing
191 if(dt_box_mean(luma, buf_height, buf_width, 1, 3, 1) != 0)
192 {
193 err = 1;
194 goto error;
195 }
196
197 // Postfilter to connect isolated dots and draw lines
198 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)
199 {
200 err = 1;
201 goto error;
202 }
203
204 // Compute the laplacian mean over the picture
205 float TV_sum = 0.0f;
206 __OMP_PARALLEL_FOR_SIMD__(collapse(2) aligned(luma:64) reduction(+:TV_sum))
207 for(size_t i = 8; i < buf_height - 8; ++i)
208 for(size_t j = 8; j < buf_width - 8; ++j)
209 TV_sum += luma[i * buf_width + j] / ((float)(buf_height - 16) * (float)(buf_width - 16));
210
211 // Compute the standard deviation
212 float sigma = 0.0f;
213 __OMP_PARALLEL_FOR_SIMD__(collapse(2) aligned(focus_peaking, luma:64) reduction(+:sigma))
214 for(size_t i = 8; i < buf_height - 8; ++i)
215 for(size_t j = 8; j < buf_width - 8; ++j)
216 sigma += sqf(luma[i * buf_width + j] - TV_sum) / ((float)(buf_height - 16) * (float)(buf_width - 16));
217
218 sigma = sqrtf(sigma);
219
220 // Set the sharpness thresholds
221 const float six_sigma = TV_sum + 4.f * sigma;
222 const float four_sigma = TV_sum + 3.f * sigma;
223 const float two_sigma = TV_sum + 2.f * sigma;
224
225 // Prepare the focus-peaking image overlay
226 __OMP_PARALLEL_FOR__(collapse(2))
227 for(size_t i = 0; i < buf_height; ++i)
228 for(size_t j = 0; j < buf_width; ++j)
229 {
230 static const uint8_t yellow[4] = { 0, 255, 255, 255 };
231 static const uint8_t green[4] = { 0, 255, 0, 255 };
232 static const uint8_t blue[4] = { 255, 0, 0, 255 };
233
234 const size_t index = (i * buf_width + j) * 4;
235 const float TV = luma[(i * buf_width + j)];
236
237 if(TV > six_sigma)
238 {
239 // Very sharp : paint yellow, BGR = (0, 255, 255)
240 for_four_channels(c) focus_peaking[index + c] = yellow[c];
241 }
242 else if(TV > four_sigma)
243 {
244 // Mediun sharp : paint green, BGR = (0, 255, 0)
245 for_four_channels(c) focus_peaking[index + c] = green[c];
246 }
247 else if(TV > two_sigma)
248 {
249 // Little sharp : paint blue, BGR = (255, 0, 0)
250 for_four_channels(c) focus_peaking[index + c] = blue[c];
251 }
252 else
253 {
254 // Not sharp enough : paint 0
255 for_four_channels(c) focus_peaking[index + c] = 0;
256 }
257 }
258
259 // draw the focus peaking overlay
260 cairo_save(cr);
261 cairo_rectangle(cr, 0, 0, buf_width, buf_height);
262 cairo_surface_t *surface = cairo_image_surface_create_for_data((unsigned char *)focus_peaking,
263 CAIRO_FORMAT_ARGB32,
264 buf_width, buf_height,
265 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, buf_width));
266 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
267 cairo_set_source_surface(cr, surface, 0.0, 0.0);
268 cairo_pattern_set_filter(cairo_get_source (cr), darktable.gui->filter_image);
269 cairo_fill(cr);
270 cairo_restore(cr);
271
272 // cleanup
273 cairo_surface_destroy(surface);
274
275error:
276 dt_pixelpipe_cache_free_align(focus_peaking);
277error_early:
280 return err;
281}
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)
darktable_t darktable
Definition darktable.c:181
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
#define dt_pixelpipe_cache_alloc_align_cache(size, id)
Definition darktable.h:433
#define __OMP_DECLARE_SIMD__(...)
Definition darktable.h:263
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define for_four_channels(_var,...)
Definition darktable.h:664
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:259
static const dt_aligned_pixel_simd_t exponent
Definition darktable.h:560
#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
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:260
@ 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)
static const float x
const float sigma
struct dt_gui_gtk_t * gui
Definition darktable.h:775
cairo_filter_t filter_image
Definition gtk.h:230