Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
bilateral.cc
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011 Bruce Guenter.
4 Copyright (C) 2010-2011 Henrik Andersson.
5 Copyright (C) 2010-2013, 2016 johannes hanika.
6 Copyright (C) 2010 Pascal de Bruijn.
7 Copyright (C) 2010 Stuart Henderson.
8 Copyright (C) 2011 Antony Dovgal.
9 Copyright (C) 2011 Jérémy Rosen.
10 Copyright (C) 2011 Olivier Tribout.
11 Copyright (C) 2011 Robert Bieber.
12 Copyright (C) 2011-2014, 2016, 2019 Tobias Ellinghaus.
13 Copyright (C) 2011-2012, 2014 Ulrich Pegelow.
14 Copyright (C) 2012 Edouard Gomez.
15 Copyright (C) 2012 Richard Wonka.
16 Copyright (C) 2014-2016 Roman Lebedev.
17 Copyright (C) 2017 Heiko Bauke.
18 Copyright (C) 2018, 2020, 2023, 2025-2026 Aurélien PIERRE.
19 Copyright (C) 2018 Edgardo Hoszowski.
20 Copyright (C) 2018 Maurizio Paglia.
21 Copyright (C) 2018 PaoloAst.
22 Copyright (C) 2018, 2020-2022 Pascal Obry.
23 Copyright (C) 2018-2019 rawfiner.
24 Copyright (C) 2019 Andreas Schneider.
25 Copyright (C) 2020 Aldric Renaudin.
26 Copyright (C) 2020, 2022 Diederik Ter Rahe.
27 Copyright (C) 2020-2021 Hubert Kowalski.
28 Copyright (C) 2020-2021 Ralf Brown.
29 Copyright (C) 2022 Martin Bařinka.
30 Copyright (C) 2022 Philipp Lutz.
31 Copyright (C) 2025 Alynx Zhou.
32
33 darktable is free software: you can redistribute it and/or modify
34 it under the terms of the GNU General Public License as published by
35 the Free Software Foundation, either version 3 of the License, or
36 (at your option) any later version.
37
38 darktable is distributed in the hope that it will be useful,
39 but WITHOUT ANY WARRANTY; without even the implied warranty of
40 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 GNU General Public License for more details.
42
43 You should have received a copy of the GNU General Public License
44 along with darktable. If not, see <http://www.gnu.org/licenses/>.
45*/
46
47#define __STDC_FORMAT_MACROS
48
49#include "glib.h"
50
51#ifdef HAVE_CONFIG_H
52#include "config.h"
53#endif
54#include "bauhaus/bauhaus.h"
55#include "common/imagebuf.h"
56#include "control/control.h"
57#include "develop/develop.h"
58#include "develop/imageop.h"
60#include "develop/imageop_gui.h"
61#include "develop/tiling.h"
62
63#include "gui/gtk.h"
64#include "iop/iop_api.h"
65#include <assert.h>
66#include <math.h>
67#include <stdlib.h>
68#include <string.h>
69
70#include "iop/Permutohedral.h"
71
72#include <gtk/gtk.h>
73#include <inttypes.h>
74
75extern "C" {
82
84{
85 // standard deviations of the gauss to use for blurring in the dimensions x,y,r,g,b (or L*,a*,b*)
86 float radius; // $MIN: 1.0 $MAX: 50.0 $DEFAULT: 15.0
87 float reserved; // $DEFAULT: 15.0
88 float red, green, blue; // $MIN: 0.0001 $MAX: 1.0 $DEFAULT: 0.005
90
95
100
101const char *name()
102{
103 return _("surface blur");
104}
105
106const char *aliases()
107{
108 return _("denoise (bilateral filter)");
109}
110
112{
113 return IOP_GROUP_REPAIR;
114}
115
120
122{
123 return IOP_CS_RGB;
124}
125
126const char **description(struct dt_iop_module_t *self)
127{
128 return dt_iop_set_description(self, _("apply edge-aware surface blur to denoise or smoothen textures"),
129 _("corrective and creative"),
130 _("linear, RGB, scene-referred"),
131 _("linear, RGB"),
132 _("linear, RGB, scene-referred"));
133}
134
136int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
137 void *const ovoid)
138{
139 const dt_iop_roi_t *const roi_in = &piece->roi_in;
140 const dt_iop_roi_t *const roi_out = &piece->roi_out;
142
143 const int ch = piece->dsc_in.channels;
144 float sigma[5];
145 sigma[0] = data->sigma[0] * roi_in->scale;
146 sigma[1] = data->sigma[1] * roi_in->scale;
147 sigma[2] = data->sigma[2];
148 sigma[3] = data->sigma[3];
149 sigma[4] = data->sigma[4];
150 if(fmaxf(sigma[0], sigma[1]) < .1)
151 {
152 dt_iop_image_copy_by_size((float*)ovoid, (float*)ivoid, roi_out->width, roi_out->height, ch);
153 return 0;
154 }
155
156 // if rad <= 6 use naive version!
157 const int rad = (int)(3.0 * fmaxf(sigma[0], sigma[1]) + 1.0);
158 if(rad <= 6 && (pipe->type == DT_DEV_PIXELPIPE_THUMBNAIL))
159 {
160 // no use denoising the thumbnail. takes ages without permutohedral
161 dt_iop_image_copy_by_size((float*)ovoid, (float*)ivoid, roi_out->width, roi_out->height, ch);
162 }
163 else if(rad <= 6)
164 {
165 static const size_t weights_size = 2 * (6 + 1) * 2 * (6 + 1);
166 float mat[weights_size];
167 const int wd = 2 * rad + 1;
168 float *m = mat + rad * wd + rad;
169 float weight = 0.0f;
170 const float isig2col[3] = { 1.f / (2.0f * sigma[2] * sigma[2]), 1.f / (2.0f * sigma[3] * sigma[3]),
171 1.f / (2.0f * sigma[4] * sigma[4]) };
172 // init gaussian kernel
173 for(int l = -rad; l <= rad; l++)
174 for(int k = -rad; k <= rad; k++)
175 weight += m[l * wd + k] = expf(-(l * l + k * k) / (2.f * sigma[0] * sigma[0]));
176 for(int l = -rad; l <= rad; l++)
177 for(int k = -rad; k <= rad; k++) m[l * wd + k] /= weight;
178
179 size_t padded_weights_size;
180 float *const weights_buf = dt_pixelpipe_cache_alloc_perthread_float(weights_size, &padded_weights_size);
181 if(IS_NULL_PTR(weights_buf)) return 1;
182 __OMP_PARALLEL_FOR_CPP__(firstprivate(isig2col, ivoid, ovoid, roi_in, roi_out, rad, ch, m, wd, weights_buf, padded_weights_size))
183 for(int j = rad; j < roi_out->height - rad; j++)
184 {
185 const float *in = ((float *)ivoid) + ch * ((size_t)j * roi_in->width + rad);
186 float *out = ((float *)ovoid) + ch * ((size_t)j * roi_out->width + rad);
187 float *weights = (float*)dt_get_perthread(weights_buf, padded_weights_size);
188 float *w = weights + rad * wd + rad;
189 float sumw;
190 for(int i = rad; i < roi_out->width - rad; i++)
191 {
192 sumw = 0.0f;
193 for(int l = -rad; l <= rad; l++)
194 for(int k = -rad; k <= rad; k++)
195 {
196 const float *inp = in + ch * (l * roi_in->width + k);
197 sumw += w[l * wd + k] = m[l * wd + k]
198 * expf(-((in[0] - inp[0]) * (in[0] - inp[0]) * isig2col[0]
199 + (in[1] - inp[1]) * (in[1] - inp[1]) * isig2col[1]
200 + (in[2] - inp[2]) * (in[2] - inp[2]) * isig2col[2]));
201 }
202 for(int l = -rad; l <= rad; l++)
203 for(int k = -rad; k <= rad; k++) w[l * wd + k] /= sumw;
204 for_each_channel(c) out[c] = 0.0f;
205 for(int l = -rad; l <= rad; l++)
206 for(int k = -rad; k <= rad; k++)
207 {
208 const float *inp = in + ch * ((size_t)l * roi_in->width + k);
209 float pix_weight = w[(size_t)l * wd + k];
210 for_each_channel(c) out[c] += inp[c] * pix_weight;
211 }
212 out += ch;
213 in += ch;
214 }
215 }
216
217
219
220 // fill unprocessed border
221 for(int j = 0; j < rad; j++)
222 memcpy(((float *)ovoid) + (size_t)ch * j * roi_out->width,
223 ((float *)ivoid) + (size_t)ch * j * roi_in->width, (size_t)ch * sizeof(float) * roi_out->width);
224 for(int j = roi_out->height - rad; j < roi_out->height; j++)
225 memcpy(((float *)ovoid) + (size_t)ch * j * roi_out->width,
226 ((float *)ivoid) + (size_t)ch * j * roi_in->width, (size_t)ch * sizeof(float) * roi_out->width);
227 for(int j = rad; j < roi_out->height - rad; j++)
228 {
229 const float *in = ((float *)ivoid) + (size_t)ch * roi_out->width * j;
230 float *out = ((float *)ovoid) + (size_t)ch * roi_out->width * j;
231 for(int i = 0; i < rad; i++)
232 for_each_channel(c) out[(size_t)ch * i + c] = in[(size_t)ch * i + c];
233 for(int i = roi_out->width - rad; i < roi_out->width; i++)
234 for_each_channel(c) out[(size_t)ch * i + c] = in[(size_t)ch * i + c];
235 }
236 }
237 else
238 {
239 for(int k = 0; k < 5; k++) sigma[k] = 1.0f / sigma[k];
240 PermutohedralLattice<5, 4> lattice((size_t)roi_in->width * roi_in->height, darktable.num_openmp_threads);
241
242// splat into the lattice
243#ifdef _OPENMP
244#pragma omp parallel for
245#endif
246 for(int j = 0; j < roi_in->height; j++)
247 {
248 const float *in = (const float *)ivoid + (size_t)j * roi_in->width * ch;
249 const int thread = dt_get_thread_num();
250 size_t index = (size_t)j * roi_in->width;
251 for(int i = 0; i < roi_in->width; i++, index++)
252 {
253 float pos[5] = { i * sigma[0], j * sigma[1], in[0] * sigma[2], in[1] * sigma[3], in[2] * sigma[4] };
254 float DT_ALIGNED_PIXEL val[4] = { in[0], in[1], in[2], 1.0 };
255 lattice.splat(pos, val, index, thread);
256 in += ch;
257 }
258 }
259
260 lattice.merge_splat_threads();
261
262 // blur the lattice
263 lattice.blur();
264
265// slice from the lattice
266#ifdef _OPENMP
267#pragma omp parallel for
268#endif
269 for(int j = 0; j < roi_in->height; j++)
270 {
271 float *const out = (float *)ovoid + (size_t)j * roi_in->width * ch;
272 size_t index = (size_t)j * roi_in->width;
273 for(int i = 0; i < roi_in->width; i++, index++)
274 {
275 float DT_ALIGNED_PIXEL val[4];
276 lattice.slice(val, index);
278 out[(size_t)ch*i + k] = val[k] / val[3];
279 }
280 }
281 }
282
283 if(pipe->mask_display) dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
284 return 0;
285}
286
289{
292 d->sigma[0] = p->radius;
293 d->sigma[1] = p->radius;
294 d->sigma[2] = p->red;
295 d->sigma[3] = p->green;
296 d->sigma[4] = p->blue;
297}
298
300{
302
303 piece->data_size = sizeof(dt_iop_bilateral_data_t);}
304
306{
307 dt_free_align(piece->data);
308 piece->data = NULL;
309}
310
311void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
312{
313 const dt_iop_roi_t *const roi_in = &piece->roi_in;
315 float sigma[5];
316 sigma[0] = data->sigma[0] * roi_in->scale;
317 sigma[1] = data->sigma[1] * roi_in->scale;
318 const int rad = (int)(3.0 * fmaxf(sigma[0], sigma[1]) + 1.0);
319 tiling->factor = 2.0 /*input+output*/ + 80.0/16/*worst-case hashtable*/ + 52.0/16/*replay buffer*/;
320 tiling->overhead = 0;
321 tiling->overlap = rad;
322 tiling->xalign = 1;
323 tiling->yalign = 1;
324 return;
325}
326
328{
330
331 g->radius = dt_bauhaus_slider_from_params(self, N_("radius"));
332 gtk_widget_set_tooltip_text(g->radius, _("spatial extent of the gaussian"));
333 dt_bauhaus_slider_set_soft_range(g->radius, 1.0, 30.0);
334
335 g->red = dt_bauhaus_slider_from_params(self, N_("red"));
336 gtk_widget_set_tooltip_text(g->red, _("how much to blur red"));
339
340 g->green = dt_bauhaus_slider_from_params(self, N_("green"));
341 gtk_widget_set_tooltip_text(g->green, _("how much to blur green"));
344
345 g->blue = dt_bauhaus_slider_from_params(self, N_("blue"));
346 gtk_widget_set_tooltip_text(g->blue, _("how much to blur blue"));
349}
350}
351
352// clang-format off
353// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
354// vim: shiftwidth=2 expandtab tabstop=2 cindent
355// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
356// clang-format on
#define m
Definition basecurve.c:278
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1647
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3534
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1624
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition bilateral.cc:287
const char ** description(struct dt_iop_module_t *self)
Definition bilateral.cc:126
int default_group()
Definition bilateral.cc:111
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition bilateral.cc:136
const char * aliases()
Definition bilateral.cc:106
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition bilateral.cc:299
const char * name()
Definition bilateral.cc:101
void gui_init(dt_iop_module_t *self)
Definition bilateral.cc:327
void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
Definition bilateral.cc:311
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition bilateral.cc:121
int flags()
Definition bilateral.cc:116
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition bilateral.cc:305
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
void slice(float *col, size_t replay_index) const
void splat(float *position, float *value, size_t replay_index, int thread_index=0) const
@ IOP_CS_RGB
const dt_colormatrix_t dt_aligned_pixel_t out
darktable_t darktable
Definition darktable.c:181
#define DT_ALIGNED_PIXEL
Definition darktable.h:389
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
#define for_each_channel(_var,...)
Definition darktable.h:662
static int dt_get_thread_num()
Definition darktable.h:291
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __OMP_PARALLEL_FOR_CPP__(...)
Definition darktable.h:265
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define dt_get_perthread(buf, padsize)
Definition darktable.h:1035
#define dt_pixelpipe_cache_alloc_perthread_float(n, padded_size)
Definition darktable.h:1030
#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
void dt_iop_params_t
Definition dev_history.h:41
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:87
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:169
@ IOP_GROUP_REPAIR
Definition imageop.h:140
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
void *const ovoid
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
@ DT_DEV_PIXELPIPE_THUMBNAIL
Definition pixelpipe.h:41
struct _GtkWidget GtkWidget
Definition splash.h:29
const float sigma
int32_t num_openmp_threads
Definition darktable.h:758
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
dt_dev_pixelpipe_type_t type
unsigned int channels
Definition format.h:54
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74