Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
bilateral.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2012-2013 johannes hanika.
4 Copyright (C) 2012 Moritz Lipp.
5 Copyright (C) 2012-2014, 2016 Tobias Ellinghaus.
6 Copyright (C) 2012, 2014, 2016 Ulrich Pegelow.
7 Copyright (C) 2015-2016 Roman Lebedev.
8 Copyright (C) 2019 Andreas Schneider.
9 Copyright (C) 2019, 2025-2026 Aurélien PIERRE.
10 Copyright (C) 2019-2021 Pascal Obry.
11 Copyright (C) 2020-2021 Hubert Kowalski.
12 Copyright (C) 2020-2021 Ralf Brown.
13 Copyright (C) 2021 Hanno Schwalm.
14 Copyright (C) 2022 Martin Bařinka.
15 Copyright (C) 2022 Miloš Komarčević.
16
17 darktable is free software: you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation, either version 3 of the License, or
20 (at your option) any later version.
21
22 darktable is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License
28 along with darktable. If not, see <http://www.gnu.org/licenses/>.
29*/
30
31#include "pixel/bilateral.h"
32#include "system/macros.h"
33#include "system/openmp.h"
35#include "system/mem_alloc.h"
36#include "system/simd.h"
37#include "common/logging.h"
38#include "caches/pixelpipe_cache_alloc.h" // dt_pixelpipe_cache_alloc_align_float_cache
39#include "math/math.h" // for CLAMPS, roundf
40#include <glib.h> // for MIN, MAX
41#include <stdlib.h> // for size_t, free, malloc, NULL
42#include <string.h> // for memset
43#include <stdio.h> // fprintf
44
45// These limits clamp away insane memory requirements. They should reasonably faithfully represent the full
46// precision though, so tiling will help reduce the memory footprint and export will look the same as darkroom
47// mode (only 1mpix there).
48#define DT_COMMON_BILATERAL_MAX_RES_S 3000
49#define DT_COMMON_BILATERAL_MAX_RES_R 50
50
51void dt_bilateral_grid_size(dt_bilateral_t *b, const int width, const int height, const float L_range,
52 float sigma_s, const float sigma_r)
53{
54 // Callers adjust sigma_s to account for image scaling to make the bilateral filter scale-invariant. As a
55 // result, if the user sets a small enough value for sigma, we can get sigma_s substantially below 1.0.
56 // Values < 1 generate a bilateral grid with spatial dimensions larger than the (scaled) image pixel
57 // dimensions; for sigma_s < 0.5, there is at least one unused grid point between any two used points, and
58 // thus the gaussian blur will have little effect. So we force sigma_s to be at least 0.5 to avoid an
59 // excessively large grid.
60 if (sigma_s < 0.5) sigma_s = 0.5;
61
62 // compute an initial grid size, clamping away insanely large grids
63 float _x = CLAMPS((int)roundf(width / sigma_s), 4, DT_COMMON_BILATERAL_MAX_RES_S);
64 float _y = CLAMPS((int)roundf(height / sigma_s), 4, DT_COMMON_BILATERAL_MAX_RES_S);
65 float _z = CLAMPS((int)roundf(L_range / sigma_r), 4, DT_COMMON_BILATERAL_MAX_RES_R);
66 // If we clamped the X or Y dimensions, the sigma_s for that dimension changes. Since we need to use the
67 // same value in both dimensions, compute the effective sigma_s for the grid.
68 b->sigma_s = MAX(height / _y, width / _x);
69 b->sigma_r = L_range / _z;
70 // Compute the grid size in light of the actual adjusted values for sigma_s and sigma_r
71 b->size_x = (int)ceilf(width / b->sigma_s) + 1;
72 b->size_y = (int)ceilf(height / b->sigma_s) + 1;
73 b->size_z = (int)ceilf(L_range / b->sigma_r) + 1;
74#if 0
75 if (b->sigma_s != sigma_s) fprintf(stderr, "[bilateral] clamped sigma_s (%g -> %g)!\n",sigma_s,b->sigma_s);
76 if (b->sigma_r != sigma_r) fprintf(stderr, "[bilateral] clamped sigma_r (%g -> %g)!\n",sigma_r,b->sigma_r);
77#endif
78}
79
80size_t dt_bilateral_memory_use(const int width, // width of input image
81 const int height, // height of input image
82 const float sigma_s, // spatial sigma (blur pixel coords)
83 const float sigma_r) // range sigma (blur luma values)
84{
87 size_t grid_size = b.size_x * b.size_y * b.size_z;
88#ifdef HAVE_OPENCL
89 // OpenCL path needs two buffers
90 return 2 * grid_size * sizeof(float);
91#else
92 return (grid_size + 3 * dt_get_num_openmp_threads() * b.size_x * b.size_z) * sizeof(float);
93#endif /* HAVE_OPENCL */
94}
95
96#ifndef HAVE_OPENCL
97// for the CPU path this is just an alias as no additional temp buffer is needed
98// when compiling with OpenCL, version in bilateralcl.c takes precedence
99size_t dt_bilateral_memory_use2(const int width,
100 const int height,
101 const float sigma_s,
102 const float sigma_r)
103{
105}
106#endif /* !HAVE_OPENCL */
107
108size_t dt_bilateral_singlebuffer_size(const int width, // width of input image
109 const int height, // height of input image
110 const float sigma_s, // spatial sigma (blur pixel coords)
111 const float sigma_r) // range sigma (blur luma values)
112{
115 size_t grid_size = b.size_x * b.size_y * b.size_z;
116 return (grid_size + 3 * dt_get_num_openmp_threads() * b.size_x * b.size_z) * sizeof(float);
117}
118
119#ifndef HAVE_OPENCL
120// for the CPU path this is just an alias as no additional temp buffer is needed
121// when compiling with OpenCL, version in bilateralcl.c takes precedence
123 const int height,
124 const float sigma_s,
125 const float sigma_r)
126{
128}
129#endif /* !HAVE_OPENCL */
130
131static inline __attribute__((always_inline)) size_t image_to_grid(const dt_bilateral_t *const b, const int i, const int j, const float L,
132 float *xf, float *yf, float *zf)
133{
134 float x = CLAMPS(i / b->sigma_s, 0, b->size_x - 1);
135 float y = CLAMPS(j / b->sigma_s, 0, b->size_y - 1);
136 float z = CLAMPS(L / b->sigma_r, 0, b->size_z - 1);
137 const int xi = MIN((int)x, b->size_x - 2);
138 const int yi = MIN((int)y, b->size_y - 2);
139 const int zi = MIN((int)z, b->size_z - 2);
140 *xf = x - xi;
141 *yf = y - yi;
142 *zf = z - zi;
143 return ((xi + yi * b->size_x) * b->size_z) + zi;
144}
145
146static inline __attribute__((always_inline)) size_t image_to_relgrid(const dt_bilateral_t *const b, const int i, const float L, float *xf, float *zf)
147{
148 float x = CLAMPS(i / b->sigma_s, 0, b->size_x - 1);
149 float z = CLAMPS(L / b->sigma_r, 0, b->size_z - 1);
150 const int xi = MIN((int)x, b->size_x - 2);
151 const int zi = MIN((int)z, b->size_z - 2);
152 *xf = x - xi;
153 *zf = z - zi;
154 return (xi * b->size_z) + zi;
155}
156
157dt_bilateral_t *dt_bilateral_init(const int width, // width of input image
158 const int height, // height of input image
159 const float sigma_s, // spatial sigma (blur pixel coords)
160 const float sigma_r) // range sigma (blur luma values)
161{
162 dt_bilateral_t *b = (dt_bilateral_t *)malloc(sizeof(dt_bilateral_t));
163 if(IS_NULL_PTR(b)) return NULL;
165 b->width = width;
166 b->height = height;
167 b->numslices = dt_get_num_openmp_threads();
168 b->sliceheight = (height + b->numslices - 1) / b->numslices;
169 b->slicerows = (b->size_y + b->numslices - 1) / b->numslices + 2;
170 b->buf = dt_pixelpipe_cache_alloc_align_float_cache(b->size_x * b->size_z * b->numslices * b->slicerows, 0);
171 if(b->buf) memset(b->buf, 0, sizeof(float) * b->size_x * b->size_z * b->numslices * b->slicerows);
172 if (IS_NULL_PTR(b->buf))
173 {
174 fprintf(stderr,"[bilateral] unable to allocate buffer for %" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT "x%" G_GSIZE_FORMAT " grid\n",b->size_x,b->size_y,b->size_z);
175 dt_free(b);
176 return NULL;
177 }
178 dt_print(DT_DEBUG_DEV, "[bilateral] created grid [%" G_GSIZE_FORMAT " %" G_GSIZE_FORMAT " %" G_GSIZE_FORMAT "] with sigma (%f %f) (%f %f)\n",
179 b->size_x, b->size_y, b->size_z, b->sigma_s, sigma_s, b->sigma_r, sigma_r);
180 return b;
181}
183void dt_bilateral_splat(const dt_bilateral_t *b, const float *const in)
184{
185 const int ox = b->size_z;
186 const int oy = b->size_x * b->size_z;
187 const int oz = 1;
188 const float sigma_s = b->sigma_s * b->sigma_s;
189 float *const buf = b->buf;
190
191 if (IS_NULL_PTR(buf)) return;
192 // splat into downsampled grid
193 const int nthreads = dt_get_num_openmp_threads();
194 const size_t offsets[8] =
195 {
196 0,
197 ox,
198 oy,
199 ox + oy,
200 oz,
201 oz + ox,
202 oz + oy,
203 oz + oy + ox
204 };
206 for(int slice = 0; slice < b->numslices; slice++)
207 {
208 const int firstrow = slice * b->sliceheight;
209 const int lastrow = MIN((slice+1)*b->sliceheight,b->height);
210 // compute the first row of the final grid which this slice splats, and subtract that from the first
211 // row the current thread should use to get an offset
212 const int slice_offset = slice * b->slicerows - (int)(firstrow / b->sigma_s);
213 // now iterate over the rows of the current horizontal slice
214 for(int j = firstrow; j < lastrow; j++)
215 {
216 float y = CLAMPS(j / b->sigma_s, 0, b->size_y - 1);
217 const int yi = MIN((int)y, b->size_y - 2);
218 const float yf = y - yi;
219 const size_t base = (size_t)(yi + slice_offset) * oy;
220 for(int i = 0; i < b->width; i++)
221 {
222 size_t index = 4 * (j * b->width + i);
223 float xf, zf;
224 const float L = in[index];
225 // nearest neighbour splatting:
226 const size_t grid_index = base + image_to_relgrid(b, i, L, &xf, &zf);
227 // sum up payload here
228 const dt_aligned_pixel_t contrib =
229 {
230 (1.0f - xf) * (1.0f - yf) * 100.0f / sigma_s, // precompute the contributions along the first two dimensions
231 xf * (1.0f - yf) * 100.0f / sigma_s,
232 (1.0f - xf) * yf * 100.0f / sigma_s,
233 xf * yf * 100.0f / sigma_s
234 };
235 __OMP_SIMD__(aligned(buf:64))
236 for(int k = 0; k < 4; k++)
237 {
238 buf[grid_index + offsets[k]] += (contrib[k] * (1.0f - zf));
239 buf[grid_index + offsets[k+4]] += (contrib[k] * zf);
240 }
241 }
242 }
243 }
244
245 // merge the per-thread results into the final result
246 for (int slice = 1 ; slice < nthreads; slice++)
247 {
248 // compute the first row of the final grid which this slice splats
249 const int destrow = (int)(slice * b->sliceheight / b->sigma_s);
250 float *dest = buf + destrow * oy;
251 // now iterate over the grid rows splatted for this slice
252 for(int j = slice * b->slicerows; j < (slice+1)*b->slicerows; j++)
253 {
254 float *src = buf + j * oy;
255 for(int i = 0; i < oy; i++)
256 {
257 dest[i] += src[i];
258 }
259 dest += oy;
260 // clear elements in the part of the buffer which holds the final result now that we've read the partial result,
261 // since we'll be adding to those locations later
262 if (j < b->size_y)
263 memset(buf + j*oy, '\0', sizeof(float) * oy);
264 }
265 }
266}
268static void blur_line_z(float *buf, const int offset1, const int offset2, const int offset3, const int size1,
269 const int size2, const int size3)
270{
271 const float w1 = 4.f / 16.f;
272 const float w2 = 2.f / 16.f;
274 for(int k = 0; k < size1; k++)
275 {
276 size_t index = (size_t)k * offset1;
277 for(int j = 0; j < size2; j++)
278 {
279 float tmp1 = buf[index];
280 buf[index] = w1 * buf[index + offset3] + w2 * buf[index + 2 * offset3];
281 index += offset3;
282 float tmp2 = buf[index];
283 buf[index] = w1 * (buf[index + offset3] - tmp1) + w2 * buf[index + 2 * offset3];
284 index += offset3;
285 for(int i = 2; i < size3 - 2; i++)
286 {
287 const float tmp3 = buf[index];
288 buf[index] = +w1 * (buf[index + offset3] - tmp2) + w2 * (buf[index + 2 * offset3] - tmp1);
289 index += offset3;
290 tmp1 = tmp2;
291 tmp2 = tmp3;
292 }
293 const float tmp3 = buf[index];
294 buf[index] = w1 * (buf[index + offset3] - tmp2) - w2 * tmp1;
295 index += offset3;
296 buf[index] = -w1 * tmp3 - w2 * tmp2;
297 index += offset3;
298 index += offset2 - offset3 * size3;
299 }
300 }
301}
303static void blur_line(float *buf, const int offset1, const int offset2, const int offset3, const int size1,
304 const int size2, const int size3)
305{
306 const float w0 = 6.f / 16.f;
307 const float w1 = 4.f / 16.f;
308 const float w2 = 1.f / 16.f;
310 for(int k = 0; k < size1; k++)
311 {
312 size_t index = (size_t)k * offset1;
313 for(int j = 0; j < size2; j++)
314 {
315 float tmp1 = buf[index];
316 buf[index] = buf[index] * w0 + w1 * buf[index + offset3] + w2 * buf[index + 2 * offset3];
317 index += offset3;
318 float tmp2 = buf[index];
319 buf[index] = buf[index] * w0 + w1 * (buf[index + offset3] + tmp1) + w2 * buf[index + 2 * offset3];
320 index += offset3;
321 for(int i = 2; i < size3 - 2; i++)
322 {
323 const float tmp3 = buf[index];
324 buf[index]
325 = buf[index] * w0 + w1 * (buf[index + offset3] + tmp2) + w2 * (buf[index + 2 * offset3] + tmp1);
326 index += offset3;
327 tmp1 = tmp2;
328 tmp2 = tmp3;
329 }
330 const float tmp3 = buf[index];
331 buf[index] = buf[index] * w0 + w1 * (buf[index + offset3] + tmp2) + w2 * tmp1;
332 index += offset3;
333 buf[index] = buf[index] * w0 + w1 * tmp3 + w2 * tmp2;
334 index += offset3;
335 index += offset2 - offset3 * size3;
336 }
337 }
338}
339
340
342{
343 if (IS_NULL_PTR(b) || IS_NULL_PTR(b->buf))
344 return;
345 const int ox = b->size_z;
346 const int oy = b->size_x * b->size_z;
347 const int oz = 1;
348 // gaussian up to 3 sigma
349 blur_line(b->buf, oz, oy, ox, b->size_z, b->size_y, b->size_x);
350 // gaussian up to 3 sigma
351 blur_line(b->buf, oz, ox, oy, b->size_z, b->size_x, b->size_y);
352 // -2 derivative of the gaussian up to 3 sigma: x*exp(-x*x)
353 blur_line_z(b->buf, ox, oy, oz, b->size_x, b->size_y, b->size_z);
354}
356void dt_bilateral_slice(const dt_bilateral_t *const b, const float *const in, float *out, const float detail)
357{
358 // detail: 0 is leave as is, -1 is bilateral filtered, +1 is contrast boost
359 const float norm = -detail * b->sigma_r * 0.04f;
360 const int ox = b->size_z;
361 const int oy = b->size_x * b->size_z;
362 const int oz = 1;
363 float *const buf = b->buf;
364 const int width = b->width;
365 const int height = b->height;
366
367 if (IS_NULL_PTR(buf)) return;
368 __OMP_PARALLEL_FOR__(collapse(2))
369 for(int j = 0; j < height; j++)
370 {
371 for(int i = 0; i < width; i++)
372 {
373 size_t index = 4 * (j * width + i);
374 float xf, yf, zf;
375 const float L = in[index];
376 // trilinear lookup:
377 const size_t gi = image_to_grid(b, i, j, L, &xf, &yf, &zf);
378 const float Lout = fmaxf( 0.0f, L
379 + norm * (buf[gi] * (1.0f - xf) * (1.0f - yf) * (1.0f - zf)
380 + buf[gi + ox] * (xf) * (1.0f - yf) * (1.0f - zf)
381 + buf[gi + oy] * (1.0f - xf) * (yf) * (1.0f - zf)
382 + buf[gi + ox + oy] * (xf) * (yf) * (1.0f - zf)
383 + buf[gi + oz] * (1.0f - xf) * (1.0f - yf) * (zf)
384 + buf[gi + ox + oz] * (xf) * (1.0f - yf) * (zf)
385 + buf[gi + oy + oz] * (1.0f - xf) * (yf) * (zf)
386 + buf[gi + ox + oy + oz] * (xf) * (yf) * (zf)));
387 out[index] = Lout;
388 // and copy color and mask
389 out[index + 1] = in[index + 1];
390 out[index + 2] = in[index + 2];
391 out[index + 3] = in[index + 3];
392 }
393 }
394}
396void dt_bilateral_slice_to_output(const dt_bilateral_t *const b, const float *const in, float *out,
397 const float detail)
398{
399 // detail: 0 is leave as is, -1 is bilateral filtered, +1 is contrast boost
400 const float norm = -detail * b->sigma_r * 0.04f;
401 const int ox = b->size_z;
402 const int oy = b->size_x * b->size_z;
403 const int oz = 1;
404 float *const buf = b->buf;
405 const int width = b->width;
406 const int height = b->height;
407
408 if (IS_NULL_PTR(buf)) return;
409 __OMP_PARALLEL_FOR__(collapse(2))
410 for(int j = 0; j < height; j++)
411 {
412 for(int i = 0; i < width; i++)
413 {
414 size_t index = 4 * (j * width + i);
415 float xf, yf, zf;
416 const float L = in[index];
417 // trilinear lookup:
418 const size_t gi = image_to_grid(b, i, j, L, &xf, &yf, &zf);
419 const float Lout = norm * (buf[gi] * (1.0f - xf) * (1.0f - yf) * (1.0f - zf)
420 + buf[gi + ox] * (xf) * (1.0f - yf) * (1.0f - zf)
421 + buf[gi + oy] * (1.0f - xf) * (yf) * (1.0f - zf)
422 + buf[gi + ox + oy] * (xf) * (yf) * (1.0f - zf)
423 + buf[gi + oz] * (1.0f - xf) * (1.0f - yf) * (zf)
424 + buf[gi + ox + oz] * (xf) * (1.0f - yf) * (zf)
425 + buf[gi + oy + oz] * (1.0f - xf) * (yf) * (zf)
426 + buf[gi + ox + oy + oz] * (xf) * (yf) * (zf));
427 out[index] = MAX(0.0f, out[index] + Lout);
428 }
429 }
430}
431
433{
434 if(IS_NULL_PTR(b)) return;
436 dt_free(b);
437}
438
439#undef DT_COMMON_BILATERAL_MAX_RES_S
440#undef DT_COMMON_BILATERAL_MAX_RES_R
441
442// clang-format off
443// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
444// vim: shiftwidth=2 expandtab tabstop=2 cindent
445// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
446// clang-format on
void dt_bilateral_free(dt_bilateral_t *b)
Definition bilateral.c:432
static __DT_CLONE_TARGETS__ void blur_line(float *buf, const int offset1, const int offset2, const int offset3, const int size1, const int size2, const int size3)
Definition bilateral.c:303
static __DT_CLONE_TARGETS__ void blur_line_z(float *buf, const int offset1, const int offset2, const int offset3, const int size1, const int size2, const int size3)
Definition bilateral.c:268
__DT_CLONE_TARGETS__ void dt_bilateral_splat(const dt_bilateral_t *b, const float *const in)
Definition bilateral.c:183
void dt_bilateral_grid_size(dt_bilateral_t *b, const int width, const int height, const float L_range, float sigma_s, const float sigma_r)
Definition bilateral.c:51
size_t dt_bilateral_memory_use(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:80
#define DT_COMMON_BILATERAL_MAX_RES_S
Definition bilateral.c:48
dt_bilateral_t * dt_bilateral_init(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:157
#define DT_COMMON_BILATERAL_MAX_RES_R
Definition bilateral.c:49
__DT_CLONE_TARGETS__ void dt_bilateral_slice_to_output(const dt_bilateral_t *const b, const float *const in, float *out, const float detail)
Definition bilateral.c:396
size_t dt_bilateral_singlebuffer_size(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:108
__DT_CLONE_TARGETS__ void dt_bilateral_slice(const dt_bilateral_t *const b, const float *const in, float *out, const float detail)
Definition bilateral.c:356
void dt_bilateral_blur(const dt_bilateral_t *b)
Definition bilateral.c:341
size_t dt_bilateral_memory_use2(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateralcl.c:72
size_t size_y
Definition bilateral.h:0
float sigma_s
Definition bilateral.h:3
size_t dt_bilateral_singlebuffer_size2(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateralcl.c:82
float sigma_r
Definition bilateral.h:3
static const float x
static void image_to_grid(const dt_iop_colorreconstruct_bilateral_t *const b, const float i, const float j, const float L, float *x, float *y, float *z)
const dt_colormatrix_t dt_aligned_pixel_t out
int dt_get_num_openmp_threads(void)
Number of OpenMP threads the application decided to use.
Definition darktable.c:518
#define w2
Definition lmmse.c:60
#define w1
Definition lmmse.c:59
@ DT_DEBUG_DEV
Definition logging.h:53
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
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
#define CLAMPS(A, L, H)
Definition math.h:78
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define __OMP_SIMD__(...)
Definition openmp.h:99
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#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
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29