Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
eigf.h
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020 rawfiner.
4 Copyright (C) 2021 Ralf Brown.
5 Copyright (C) 2022 Martin Bařinka.
6 Copyright (C) 2025-2026 Aurélien PIERRE.
7
8 darktable is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 darktable is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with darktable. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#ifndef DT_PIXEL_EIGF_H
23#define DT_PIXEL_EIGF_H
24
27#include "pixel/gaussian.h"
28
29/***
30 * DOCUMENTATION
31 *
32 * Exposure-Independent Guided Filter (EIGF)
33 *
34 * This filter is a modification of guided filter to make it exposure independent
35 * As variance depends on the exposure, the original guided filter preserves
36 * much better the edges in the highlights than in the shadows.
37 * In particular doing:
38 * (1) increase exposure by 1EV
39 * (2) guided filtering
40 * (3) decrease exposure by 1EV
41 * is NOT equivalent to doing the guided filtering only.
42 *
43 * To overcome this, instead of using variance directly to determine "a",
44 * we use a ratio:
45 * variance / (pixel_value)^2
46 * we tried also the following ratios:
47 * - variance / average^2
48 * - variance / (pixel_value * average)
49 * we kept variance / (pixel_value)^2 as it seemed to behave a bit better than
50 * the other (dividing by average^2 smoothed too much dark details surrounded
51 * by bright pixels).
52 *
53 * This modification makes the filter exposure-independent.
54 * However, due to the fact that the average advantages the bright pixels
55 * compared to dark pixels if we consider that human eye sees in log,
56 * we get strong bright halos.
57 * These are due to the spatial averaging of "a" and "b" that is performed at
58 * the end of the filter, especially due to the spatial averaging of "b".
59 * We decided to remove this final spatial averaging, as it is very hard
60 * to keep it without having either large unsmoothed regions or halos.
61 * Although the filter may blur a bit less without it, it remains sufficiently
62 * good at smoothing the image, and there are much less halos.
63 *
64 * The implementation EIGF uses downscaling to speed-up the filtering,
65 * just like what is done in fast_guided_filter.h
66**/
67
68/* computes average and variance of guide and mask, and put them in out.
69 * out has 4 channels:
70 * - average of guide
71 * - variance of guide
72 * - average of mask
73 * - covariance of mask and guide. */
75static inline int eigf_variance_analysis(const float *const restrict guide, // I
76 const float *const restrict mask, //p
77 float *const restrict out,
78 const size_t width, const size_t height,
79 const float sigma)
80{
81 // We also use gaussian blurs instead of the square blurs of the guided filter
82 const size_t Ndim = width * height;
83 float *const restrict in = dt_pixelpipe_cache_alloc_align_float_cache(Ndim * 4, 0);
84 dt_gaussian_t *g = NULL;
85 int err = 0;
86 if(IS_NULL_PTR(in))
87 {
88 err = 1;
89 goto error;
90 }
91
92 float ming = 10000000.0f;
93 float maxg = 0.0f;
94 float minm = 10000000.0f;
95 float maxm = 0.0f;
96 float ming2 = 10000000.0f;
97 float maxg2 = 0.0f;
98 float minmg = 10000000.0f;
99 float maxmg = 0.0f;
100 __OMP_PARALLEL_FOR__(reduction(max:maxg, maxm, maxg2, maxmg) reduction(min:ming, minm, ming2, minmg))
101 for(size_t k = 0; k < Ndim; k++)
102 {
103 const float pixelg = guide[k];
104 const float pixelm = mask[k];
105 const float pixelg2 = pixelg * pixelg;
106 const float pixelmg = pixelm * pixelg;
107 in[k * 4] = pixelg;
108 in[k * 4 + 1] = pixelg2;
109 in[k * 4 + 2] = pixelm;
110 in[k * 4 + 3] = pixelmg;
111 ming = MIN(ming,pixelg);
112 maxg = MAX(maxg,pixelg);
113 minm = MIN(minm,pixelm);
114 maxm = MAX(maxm,pixelm);
115 ming2 = MIN(ming2,pixelg2);
116 maxg2 = MAX(maxg2,pixelg2);
117 minmg = MIN(minmg,pixelmg);
118 maxmg = MAX(maxmg,pixelmg);
119 }
120
121 dt_aligned_pixel_t max = {maxg, maxg2, maxm, maxmg};
122 dt_aligned_pixel_t min = {ming, ming2, minm, minmg};
124 if(IS_NULL_PTR(g))
125 {
126 err = 1;
127 goto error;
128 }
130 __OMP_PARALLEL_FOR_SIMD__(aligned(out:64))
131 for(size_t k = 0; k < Ndim; k++)
132 {
133 out[4 * k + 1] -= out[4 * k] * out[4 * k];
134 out[4 * k + 3] -= out[4 * k] * out[4 * k + 2];
135 }
136
137error:
138 if(g) dt_gaussian_free(g);
140 return err;
141}
142
143// same function as above, but specialized for the case where guide == mask
144// for increased performance
146static inline int eigf_variance_analysis_no_mask(const float *const restrict guide, // I
147 float *const restrict out,
148 const size_t width, const size_t height,
149 const float sigma)
150{
151 // We also use gaussian blurs instead of the square blurs of the guided filter
152 const size_t Ndim = width * height;
153 float *const restrict in = dt_pixelpipe_cache_alloc_align_float_cache(Ndim * 2, 0);
154 dt_gaussian_t *g = NULL;
155 int err = 0;
156 if(IS_NULL_PTR(in))
157 {
158 err = 1;
159 goto error;
160 }
161
162 float ming = 10000000.0f;
163 float maxg = 0.0f;
164 float ming2 = 10000000.0f;
165 float maxg2 = 0.0f;
166 __OMP_PARALLEL_FOR__(reduction(max:maxg, maxg2) reduction(min:ming, ming2))
167 for(size_t k = 0; k < Ndim; k++)
168 {
169 const float pixelg = guide[k];
170 const float pixelg2 = pixelg * pixelg;
171 in[2 * k] = pixelg;
172 in[2 * k + 1] = pixelg2;
173 ming = MIN(ming,pixelg);
174 maxg = MAX(maxg,pixelg);
175 ming2 = MIN(ming2,pixelg2);
176 maxg2 = MAX(maxg2,pixelg2);
177 }
178
179 float max[2] = {maxg, maxg2};
180 float min[2] = {ming, ming2};
182 if(IS_NULL_PTR(g))
183 {
184 err = 1;
185 goto error;
186 }
187 dt_gaussian_blur(g, in, out);
188 __OMP_PARALLEL_FOR_SIMD__(aligned(out:64))
189 for(size_t k = 0; k < Ndim; k++)
190 {
191 const float avg = out[2 * k];
192 out[2 * k + 1] -= avg * avg;
193 }
194
195error:
196 if(g) dt_gaussian_free(g);
198 return err;
199}
200
202static inline void eigf_blending(float *const restrict image, const float *const restrict mask,
203 const float *const restrict av, const size_t Ndim,
204 const dt_iop_guided_filter_blending_t filter, const float feathering)
205{
206 __OMP_PARALLEL_FOR_SIMD__(aligned(image, mask, av:64))
207 for(size_t k = 0; k < Ndim; k++)
208 {
209 const float avg_g = av[k * 4];
210 const float avg_m = av[k * 4 + 2];
211 const float var_g = av[k * 4 + 1];
212 const float covar_mg = av[k * 4 + 3];
213 const float norm_g = fmaxf(avg_g * image[k], 1E-6);
214 const float norm_m = fmaxf(avg_m * mask[k], 1E-6);
215 const float normalized_var_guide = var_g / norm_g;
216 const float normalized_covar = covar_mg / sqrtf(norm_g * norm_m);
217 const float a = normalized_covar / (normalized_var_guide + feathering);
218 const float b = avg_m - a * avg_g;
219 if(filter == DT_GF_BLENDING_LINEAR)
220 {
221 image[k] = fmaxf(image[k] * a + b, MIN_FLOAT);
222 }
223 else
224 {
225 // filter == DT_GF_BLENDING_GEOMEAN
226 image[k] *= fmaxf(image[k] * a + b, MIN_FLOAT);
227 image[k] = sqrtf(image[k]);
228 }
229 }
230}
231
232// same function as above, but specialized for the case where guide == mask
233// for increased performance
235static inline void eigf_blending_no_mask(float *const restrict image, const float *const restrict av,
236 const size_t Ndim, const dt_iop_guided_filter_blending_t filter,
237 const float feathering)
238{
239 __OMP_PARALLEL_FOR_SIMD__(aligned(image, av:64))
240 for(size_t k = 0; k < Ndim; k++)
241 {
242 const float avg_g = av[k * 2];
243 const float var_g = av[k * 2 + 1];
244 const float norm_g = fmaxf(avg_g * image[k], 1E-6);
245 const float normalized_var_guide = var_g / norm_g;
246 const float a = normalized_var_guide / (normalized_var_guide + feathering);
247 const float b = avg_g - a * avg_g;
248 if(filter == DT_GF_BLENDING_LINEAR)
249 {
250 image[k] = fmaxf(image[k] * a + b, MIN_FLOAT);
251 }
252 else
253 {
254 // filter == DT_GF_BLENDING_GEOMEAN
255 image[k] *= fmaxf(image[k] * a + b, MIN_FLOAT);
256 image[k] = sqrtf(image[k]);
257 }
258 }
259}
260
262static inline int fast_eigf_surface_blur(float *const restrict image,
263 const size_t width, const size_t height,
264 const float sigma, float feathering, const int iterations,
265 const dt_iop_guided_filter_blending_t filter, const float scale,
266 const float quantization, const float quantize_min, const float quantize_max)
267{
268 // Works in-place on a grey image
269 // mostly similar with fast_surface_blur from fast_guided_filter.h
270
271 int err = 0;
272
273 // A down-scaling of 4 seems empirically safe and consistent no matter the image zoom level
274 // see reference paper above for proof.
275 const float scaling = fmaxf(fminf(sigma, 4.0f), 1.0f);
276 const float ds_sigma = fmaxf(sigma / scaling, 1.0f);
277
278 const size_t ds_height = height / scaling;
279 const size_t ds_width = width / scaling;
280
281 const size_t num_elem_ds = ds_width * ds_height;
282 const size_t num_elem = width * height;
283
284 float *const restrict mask = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem), 0);
285 float *const restrict ds_image = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem_ds), 0);
286 float *const restrict ds_mask = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem_ds), 0);
287 // average - variance arrays: store the guide and mask averages and variances
288 float *const restrict ds_av = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem_ds * 4), 0);
289 float *const restrict av = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem * 4), 0);
290
291 if(IS_NULL_PTR(ds_image) || IS_NULL_PTR(ds_mask) || IS_NULL_PTR(ds_av) || IS_NULL_PTR(av) || IS_NULL_PTR(mask))
292 {
293 err = 1;
294 goto error;
295 }
296
297 // Iterations of filter models the diffusion, sort of
298 for(int i = 0; i < iterations; i++)
299 {
300 // blend linear for all intermediate images
302 // use filter for last iteration
303 if(i == iterations - 1)
304 blend = filter;
305
306 interpolate_bilinear(image, width, height, ds_image, ds_width, ds_height, 1);
307 if(quantization != 0.0f)
308 {
309 // (Re)build the mask from the quantized image to help guiding
310 quantize(image, mask, width * height, quantization, quantize_min, quantize_max);
311 // Downsample the image for speed-up
312 interpolate_bilinear(mask, width, height, ds_mask, ds_width, ds_height, 1);
313 if(eigf_variance_analysis(ds_mask, ds_image, ds_av, ds_width, ds_height, ds_sigma) != 0)
314 {
315 err = 1;
316 goto error;
317 }
318 // Upsample the variances and averages
319 interpolate_bilinear(ds_av, ds_width, ds_height, av, width, height, 4);
320 // Blend the guided image
321 eigf_blending(image, mask, av, num_elem, blend, feathering);
322 }
323 else
324 {
325 // no need to build a mask.
326 if(eigf_variance_analysis_no_mask(ds_image, ds_av, ds_width, ds_height, ds_sigma) != 0)
327 {
328 err = 1;
329 goto error;
330 }
331 // Upsample the variances and averages
332 interpolate_bilinear(ds_av, ds_width, ds_height, av, width, height, 2);
333 // Blend the guided image
334 eigf_blending_no_mask(image, av, num_elem, blend, feathering);
335 }
336 }
337
338error:
344 return err;
345}
346
347#endif // DT_PIXEL_EIGF_H
348
349// clang-format off
350// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
351// vim: shiftwidth=2 expandtab tabstop=2 cindent
352// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
353// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
static const float scaling
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static __DT_CLONE_TARGETS__ int eigf_variance_analysis(const float *const restrict guide, const float *const restrict mask, float *const restrict out, const size_t width, const size_t height, const float sigma)
Definition eigf.h:75
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
static __DT_CLONE_TARGETS__ int eigf_variance_analysis_no_mask(const float *const restrict guide, float *const restrict out, const size_t width, const size_t height, const float sigma)
Definition eigf.h:146
static __DT_CLONE_TARGETS__ void eigf_blending_no_mask(float *const restrict image, const float *const restrict av, const size_t Ndim, const dt_iop_guided_filter_blending_t filter, const float feathering)
Definition eigf.h:235
static __DT_CLONE_TARGETS__ void eigf_blending(float *const restrict image, const float *const restrict mask, const float *const restrict av, const size_t Ndim, const dt_iop_guided_filter_blending_t filter, const float feathering)
Definition eigf.h:202
dt_iop_guided_filter_blending_t
@ DT_GF_BLENDING_LINEAR
static __DT_CLONE_TARGETS__ void quantize(const float *const restrict image, float *const restrict out, const size_t num_elem, const float sampling, const float clip_min, const float clip_max)
static __DT_CLONE_TARGETS__ void interpolate_bilinear(const float *const restrict in, const size_t width_in, const size_t height_in, float *const restrict out, const size_t width_out, const size_t height_out, const size_t ch)
void dt_gaussian_free(dt_gaussian_t *g)
Definition gaussian.c:335
__DT_CLONE_TARGETS__ void dt_gaussian_blur(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:176
void dt_gaussian_blur_4c(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:330
dt_gaussian_t * dt_gaussian_init(const int width, const int height, const int channels, const float *max, const float *min, const float sigma, const int order)
Definition gaussian.c:127
float *const restrict const size_t k
#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
static size_t dt_round_size_sse(const size_t size)
Round size up to the next multiple of 64.
Definition mem_alloc.h:111
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float sigma
#define __DT_CLONE_TARGETS__
#define E
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
#define MIN_FLOAT
Definition toneequal.c:161