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#pragma once
23
25#include "common/gaussian.h"
26
27/***
28 * DOCUMENTATION
29 *
30 * Exposure-Independent Guided Filter (EIGF)
31 *
32 * This filter is a modification of guided filter to make it exposure independent
33 * As variance depends on the exposure, the original guided filter preserves
34 * much better the edges in the highlights than in the shadows.
35 * In particular doing:
36 * (1) increase exposure by 1EV
37 * (2) guided filtering
38 * (3) decrease exposure by 1EV
39 * is NOT equivalent to doing the guided filtering only.
40 *
41 * To overcome this, instead of using variance directly to determine "a",
42 * we use a ratio:
43 * variance / (pixel_value)^2
44 * we tried also the following ratios:
45 * - variance / average^2
46 * - variance / (pixel_value * average)
47 * we kept variance / (pixel_value)^2 as it seemed to behave a bit better than
48 * the other (dividing by average^2 smoothed too much dark details surrounded
49 * by bright pixels).
50 *
51 * This modification makes the filter exposure-independent.
52 * However, due to the fact that the average advantages the bright pixels
53 * compared to dark pixels if we consider that human eye sees in log,
54 * we get strong bright halos.
55 * These are due to the spatial averaging of "a" and "b" that is performed at
56 * the end of the filter, especially due to the spatial averaging of "b".
57 * We decided to remove this final spatial averaging, as it is very hard
58 * to keep it without having either large unsmoothed regions or halos.
59 * Although the filter may blur a bit less without it, it remains sufficiently
60 * good at smoothing the image, and there are much less halos.
61 *
62 * The implementation EIGF uses downscaling to speed-up the filtering,
63 * just like what is done in fast_guided_filter.h
64**/
65
66/* computes average and variance of guide and mask, and put them in out.
67 * out has 4 channels:
68 * - average of guide
69 * - variance of guide
70 * - average of mask
71 * - covariance of mask and guide. */
73static inline int eigf_variance_analysis(const float *const restrict guide, // I
74 const float *const restrict mask, //p
75 float *const restrict out,
76 const size_t width, const size_t height,
77 const float sigma)
78{
79 // We also use gaussian blurs instead of the square blurs of the guided filter
80 const size_t Ndim = width * height;
81 float *const restrict in = dt_pixelpipe_cache_alloc_align_float_cache(Ndim * 4, 0);
82 dt_gaussian_t *g = NULL;
83 int err = 0;
84 if(IS_NULL_PTR(in))
85 {
86 err = 1;
87 goto error;
88 }
89
90 float ming = 10000000.0f;
91 float maxg = 0.0f;
92 float minm = 10000000.0f;
93 float maxm = 0.0f;
94 float ming2 = 10000000.0f;
95 float maxg2 = 0.0f;
96 float minmg = 10000000.0f;
97 float maxmg = 0.0f;
98 __OMP_PARALLEL_FOR__(reduction(max:maxg, maxm, maxg2, maxmg) reduction(min:ming, minm, ming2, minmg))
99 for(size_t k = 0; k < Ndim; k++)
100 {
101 const float pixelg = guide[k];
102 const float pixelm = mask[k];
103 const float pixelg2 = pixelg * pixelg;
104 const float pixelmg = pixelm * pixelg;
105 in[k * 4] = pixelg;
106 in[k * 4 + 1] = pixelg2;
107 in[k * 4 + 2] = pixelm;
108 in[k * 4 + 3] = pixelmg;
109 ming = MIN(ming,pixelg);
110 maxg = MAX(maxg,pixelg);
111 minm = MIN(minm,pixelm);
112 maxm = MAX(maxm,pixelm);
113 ming2 = MIN(ming2,pixelg2);
114 maxg2 = MAX(maxg2,pixelg2);
115 minmg = MIN(minmg,pixelmg);
116 maxmg = MAX(maxmg,pixelmg);
117 }
118
119 dt_aligned_pixel_t max = {maxg, maxg2, maxm, maxmg};
120 dt_aligned_pixel_t min = {ming, ming2, minm, minmg};
122 if(IS_NULL_PTR(g))
123 {
124 err = 1;
125 goto error;
126 }
128 __OMP_PARALLEL_FOR_SIMD__(aligned(out:64))
129 for(size_t k = 0; k < Ndim; k++)
130 {
131 out[4 * k + 1] -= out[4 * k] * out[4 * k];
132 out[4 * k + 3] -= out[4 * k] * out[4 * k + 2];
133 }
134
135error:
136 if(g) dt_gaussian_free(g);
138 return err;
139}
140
141// same function as above, but specialized for the case where guide == mask
142// for increased performance
144static inline int eigf_variance_analysis_no_mask(const float *const restrict guide, // I
145 float *const restrict out,
146 const size_t width, const size_t height,
147 const float sigma)
148{
149 // We also use gaussian blurs instead of the square blurs of the guided filter
150 const size_t Ndim = width * height;
151 float *const restrict in = dt_pixelpipe_cache_alloc_align_float_cache(Ndim * 2, 0);
152 dt_gaussian_t *g = NULL;
153 int err = 0;
154 if(IS_NULL_PTR(in))
155 {
156 err = 1;
157 goto error;
158 }
159
160 float ming = 10000000.0f;
161 float maxg = 0.0f;
162 float ming2 = 10000000.0f;
163 float maxg2 = 0.0f;
164 __OMP_PARALLEL_FOR__(reduction(max:maxg, maxg2) reduction(min:ming, ming2))
165 for(size_t k = 0; k < Ndim; k++)
166 {
167 const float pixelg = guide[k];
168 const float pixelg2 = pixelg * pixelg;
169 in[2 * k] = pixelg;
170 in[2 * k + 1] = pixelg2;
171 ming = MIN(ming,pixelg);
172 maxg = MAX(maxg,pixelg);
173 ming2 = MIN(ming2,pixelg2);
174 maxg2 = MAX(maxg2,pixelg2);
175 }
176
177 float max[2] = {maxg, maxg2};
178 float min[2] = {ming, ming2};
180 if(IS_NULL_PTR(g))
181 {
182 err = 1;
183 goto error;
184 }
185 dt_gaussian_blur(g, in, out);
186 __OMP_PARALLEL_FOR_SIMD__(aligned(out:64))
187 for(size_t k = 0; k < Ndim; k++)
188 {
189 const float avg = out[2 * k];
190 out[2 * k + 1] -= avg * avg;
191 }
192
193error:
194 if(g) dt_gaussian_free(g);
196 return err;
197}
198
200static inline void eigf_blending(float *const restrict image, const float *const restrict mask,
201 const float *const restrict av, const size_t Ndim,
202 const dt_iop_guided_filter_blending_t filter, const float feathering)
203{
204 __OMP_PARALLEL_FOR_SIMD__(aligned(image, mask, av:64))
205 for(size_t k = 0; k < Ndim; k++)
206 {
207 const float avg_g = av[k * 4];
208 const float avg_m = av[k * 4 + 2];
209 const float var_g = av[k * 4 + 1];
210 const float covar_mg = av[k * 4 + 3];
211 const float norm_g = fmaxf(avg_g * image[k], 1E-6);
212 const float norm_m = fmaxf(avg_m * mask[k], 1E-6);
213 const float normalized_var_guide = var_g / norm_g;
214 const float normalized_covar = covar_mg / sqrtf(norm_g * norm_m);
215 const float a = normalized_covar / (normalized_var_guide + feathering);
216 const float b = avg_m - a * avg_g;
217 if(filter == DT_GF_BLENDING_LINEAR)
218 {
219 image[k] = fmaxf(image[k] * a + b, MIN_FLOAT);
220 }
221 else
222 {
223 // filter == DT_GF_BLENDING_GEOMEAN
224 image[k] *= fmaxf(image[k] * a + b, MIN_FLOAT);
225 image[k] = sqrtf(image[k]);
226 }
227 }
228}
229
230// same function as above, but specialized for the case where guide == mask
231// for increased performance
233static inline void eigf_blending_no_mask(float *const restrict image, const float *const restrict av,
234 const size_t Ndim, const dt_iop_guided_filter_blending_t filter,
235 const float feathering)
236{
237 __OMP_PARALLEL_FOR_SIMD__(aligned(image, av:64))
238 for(size_t k = 0; k < Ndim; k++)
239 {
240 const float avg_g = av[k * 2];
241 const float var_g = av[k * 2 + 1];
242 const float norm_g = fmaxf(avg_g * image[k], 1E-6);
243 const float normalized_var_guide = var_g / norm_g;
244 const float a = normalized_var_guide / (normalized_var_guide + feathering);
245 const float b = avg_g - a * avg_g;
246 if(filter == DT_GF_BLENDING_LINEAR)
247 {
248 image[k] = fmaxf(image[k] * a + b, MIN_FLOAT);
249 }
250 else
251 {
252 // filter == DT_GF_BLENDING_GEOMEAN
253 image[k] *= fmaxf(image[k] * a + b, MIN_FLOAT);
254 image[k] = sqrtf(image[k]);
255 }
256 }
257}
258
260static inline int fast_eigf_surface_blur(float *const restrict image,
261 const size_t width, const size_t height,
262 const float sigma, float feathering, const int iterations,
263 const dt_iop_guided_filter_blending_t filter, const float scale,
264 const float quantization, const float quantize_min, const float quantize_max)
265{
266 // Works in-place on a grey image
267 // mostly similar with fast_surface_blur from fast_guided_filter.h
268
269 int err = 0;
270
271 // A down-scaling of 4 seems empirically safe and consistent no matter the image zoom level
272 // see reference paper above for proof.
273 const float scaling = fmaxf(fminf(sigma, 4.0f), 1.0f);
274 const float ds_sigma = fmaxf(sigma / scaling, 1.0f);
275
276 const size_t ds_height = height / scaling;
277 const size_t ds_width = width / scaling;
278
279 const size_t num_elem_ds = ds_width * ds_height;
280 const size_t num_elem = width * height;
281
282 float *const restrict mask = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem), 0);
283 float *const restrict ds_image = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem_ds), 0);
284 float *const restrict ds_mask = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem_ds), 0);
285 // average - variance arrays: store the guide and mask averages and variances
286 float *const restrict ds_av = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem_ds * 4), 0);
287 float *const restrict av = dt_pixelpipe_cache_alloc_align_float_cache(dt_round_size_sse(num_elem * 4), 0);
288
289 if(IS_NULL_PTR(ds_image) || IS_NULL_PTR(ds_mask) || IS_NULL_PTR(ds_av) || IS_NULL_PTR(av) || IS_NULL_PTR(mask))
290 {
291 err = 1;
292 goto error;
293 }
294
295 // Iterations of filter models the diffusion, sort of
296 for(int i = 0; i < iterations; i++)
297 {
298 // blend linear for all intermediate images
300 // use filter for last iteration
301 if(i == iterations - 1)
302 blend = filter;
303
304 interpolate_bilinear(image, width, height, ds_image, ds_width, ds_height, 1);
305 if(quantization != 0.0f)
306 {
307 // (Re)build the mask from the quantized image to help guiding
308 quantize(image, mask, width * height, quantization, quantize_min, quantize_max);
309 // Downsample the image for speed-up
310 interpolate_bilinear(mask, width, height, ds_mask, ds_width, ds_height, 1);
311 if(eigf_variance_analysis(ds_mask, ds_image, ds_av, ds_width, ds_height, ds_sigma) != 0)
312 {
313 err = 1;
314 goto error;
315 }
316 // Upsample the variances and averages
317 interpolate_bilinear(ds_av, ds_width, ds_height, av, width, height, 4);
318 // Blend the guided image
319 eigf_blending(image, mask, av, num_elem, blend, feathering);
320 }
321 else
322 {
323 // no need to build a mask.
324 if(eigf_variance_analysis_no_mask(ds_image, ds_av, ds_width, ds_height, ds_sigma) != 0)
325 {
326 err = 1;
327 goto error;
328 }
329 // Upsample the variances and averages
330 interpolate_bilinear(ds_av, ds_width, ds_height, av, width, height, 2);
331 // Blend the guided image
332 eigf_blending_no_mask(image, av, num_elem, blend, feathering);
333 }
334 }
335
336error:
342 return err;
343}
344// clang-format off
345// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
346// vim: shiftwidth=2 expandtab tabstop=2 cindent
347// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
348// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const float scaling
Definition chromatic_adaptation.h:293
const float i
Definition colorspaces_inline_conversions.h:440
const float g
Definition colorspaces_inline_conversions.h:674
static const float const float const float min
Definition colorspaces_inline_conversions.h:438
const float max
Definition colorspaces_inline_conversions.h:490
const dt_colormatrix_t dt_aligned_pixel_t out
Definition colorspaces_inline_conversions.h:42
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
static size_t dt_round_size_sse(const size_t size)
Definition darktable.h:403
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:259
#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 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:73
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
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:144
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:233
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:200
dt_iop_guided_filter_blending_t
Definition fast_guided_filter.h:44
@ DT_GF_BLENDING_LINEAR
Definition fast_guided_filter.h:45
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)
Definition fast_guided_filter.h:236
#define MIN_FLOAT
Definition fast_guided_filter.h:40
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)
Definition fast_guided_filter.h:94
void dt_gaussian_free(dt_gaussian_t *g)
Definition gaussian.c:330
__DT_CLONE_TARGETS__ void dt_gaussian_blur(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:171
void dt_gaussian_blur_4c(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:325
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:122
float *const restrict const size_t k
Definition luminance_mask.h:78
float dt_aligned_pixel_t[4]
Definition noiseprofile.c:28
const float sigma
Definition src/develop/noise_generator.h:71
Definition gaussian.h:40
#define E
Definition test_filmicrgb.c:61
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29