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"
51
52#ifdef HAVE_CONFIG_H
53#include "config.h"
54#endif
55#include "widgets/bauhaus.h"
56#include "system/macros.h"
57#include "system/openmp.h"
59#include "system/mem_alloc.h"
60#include "system/simd.h"
62#include "common/imagebuf.h"
63#include "develop/develop.h"
64#include "develop/imageop.h"
66#include "develop/imageop_gui.h"
67#include "develop/tiling.h"
68
69#include "iop/iop_api.h"
70#include <assert.h>
71#include <math.h>
72#include <stdlib.h>
73#include <string.h>
74
75#include "iop/Permutohedral.h"
76
77#include <gtk/gtk.h>
78#include <inttypes.h>
79
80extern "C" {
87
89{
90 // standard deviations of the gauss to use for blurring in the dimensions x,y,r,g,b (or L*,a*,b*)
91 float radius; // $MIN: 1.0 $MAX: 50.0 $DEFAULT: 15.0
92 float reserved; // $DEFAULT: 15.0
93 float red, green, blue; // $MIN: 0.0001 $MAX: 1.0 $DEFAULT: 0.005
95
100
105
106const char *name()
107{
108 return _("surface blur");
109}
110
111const char *aliases()
112{
113 return _("denoise (bilateral filter)");
114}
115
117{
118 return IOP_GROUP_REPAIR;
119}
120
125
127{
128 return IOP_CS_RGB;
129}
130
131const char **description(struct dt_iop_module_t *self)
132{
133 return dt_iop_set_description(self, _("apply edge-aware surface blur to denoise or smoothen textures"),
134 _("corrective and creative"),
135 _("linear, RGB, scene-referred"),
136 _("linear, RGB"),
137 _("linear, RGB, scene-referred"));
138}
139
141int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
142 void *const ovoid)
143{
144 const dt_iop_roi_t *const roi_in = &piece->roi_in;
145 const dt_iop_roi_t *const roi_out = &piece->roi_out;
147
148 const int ch = piece->dsc_in.channels;
149 float sigma[5];
150 sigma[0] = data->sigma[0] * roi_in->scale;
151 sigma[1] = data->sigma[1] * roi_in->scale;
152 sigma[2] = data->sigma[2];
153 sigma[3] = data->sigma[3];
154 sigma[4] = data->sigma[4];
155 if(fmaxf(sigma[0], sigma[1]) < .1)
156 {
157 dt_iop_image_copy_by_size((float*)ovoid, (float*)ivoid, roi_out->width, roi_out->height, ch);
158 return 0;
159 }
160
161 // if rad <= 6 use naive version!
162 const int rad = (int)(3.0 * fmaxf(sigma[0], sigma[1]) + 1.0);
163 if(rad <= 6 && (pipe->type == DT_DEV_PIXELPIPE_THUMBNAIL))
164 {
165 // no use denoising the thumbnail. takes ages without permutohedral
166 dt_iop_image_copy_by_size((float*)ovoid, (float*)ivoid, roi_out->width, roi_out->height, ch);
167 }
168 else if(rad <= 6)
169 {
170 static const size_t weights_size = 2 * (6 + 1) * 2 * (6 + 1);
171 float mat[weights_size];
172 const int wd = 2 * rad + 1;
173 float *m = mat + rad * wd + rad;
174 float weight = 0.0f;
175 const float isig2col[3] = { 1.f / (2.0f * sigma[2] * sigma[2]), 1.f / (2.0f * sigma[3] * sigma[3]),
176 1.f / (2.0f * sigma[4] * sigma[4]) };
177 // init gaussian kernel
178 for(int l = -rad; l <= rad; l++)
179 for(int k = -rad; k <= rad; k++)
180 weight += m[l * wd + k] = expf(-(l * l + k * k) / (2.f * sigma[0] * sigma[0]));
181 for(int l = -rad; l <= rad; l++)
182 for(int k = -rad; k <= rad; k++) m[l * wd + k] /= weight;
183
184 size_t padded_weights_size;
185 float *const weights_buf = dt_pixelpipe_cache_alloc_perthread_float(weights_size, &padded_weights_size);
186 if(IS_NULL_PTR(weights_buf)) return 1;
187 __OMP_PARALLEL_FOR_CPP__(firstprivate(isig2col, ivoid, ovoid, roi_in, roi_out, rad, ch, m, wd, weights_buf, padded_weights_size))
188 for(int j = rad; j < roi_out->height - rad; j++)
189 {
190 const float *in = ((float *)ivoid) + ch * ((size_t)j * roi_in->width + rad);
191 float *out = ((float *)ovoid) + ch * ((size_t)j * roi_out->width + rad);
192 float *weights = (float*)dt_get_perthread(weights_buf, padded_weights_size);
193 float *w = weights + rad * wd + rad;
194 float sumw;
195 for(int i = rad; i < roi_out->width - rad; i++)
196 {
197 sumw = 0.0f;
198 for(int l = -rad; l <= rad; l++)
199 for(int k = -rad; k <= rad; k++)
200 {
201 const float *inp = in + ch * (l * roi_in->width + k);
202 sumw += w[l * wd + k] = m[l * wd + k]
203 * expf(-((in[0] - inp[0]) * (in[0] - inp[0]) * isig2col[0]
204 + (in[1] - inp[1]) * (in[1] - inp[1]) * isig2col[1]
205 + (in[2] - inp[2]) * (in[2] - inp[2]) * isig2col[2]));
206 }
207 for(int l = -rad; l <= rad; l++)
208 for(int k = -rad; k <= rad; k++) w[l * wd + k] /= sumw;
209 for_each_channel(c) out[c] = 0.0f;
210 for(int l = -rad; l <= rad; l++)
211 for(int k = -rad; k <= rad; k++)
212 {
213 const float *inp = in + ch * ((size_t)l * roi_in->width + k);
214 float pix_weight = w[(size_t)l * wd + k];
215 for_each_channel(c) out[c] += inp[c] * pix_weight;
216 }
217 out += ch;
218 in += ch;
219 }
220 }
221
222
224
225 // fill unprocessed border
226 for(int j = 0; j < rad; j++)
227 memcpy(((float *)ovoid) + (size_t)ch * j * roi_out->width,
228 ((float *)ivoid) + (size_t)ch * j * roi_in->width, (size_t)ch * sizeof(float) * roi_out->width);
229 for(int j = roi_out->height - rad; j < roi_out->height; j++)
230 memcpy(((float *)ovoid) + (size_t)ch * j * roi_out->width,
231 ((float *)ivoid) + (size_t)ch * j * roi_in->width, (size_t)ch * sizeof(float) * roi_out->width);
232 for(int j = rad; j < roi_out->height - rad; j++)
233 {
234 const float *in = ((float *)ivoid) + (size_t)ch * roi_out->width * j;
235 float *out = ((float *)ovoid) + (size_t)ch * roi_out->width * j;
236 for(int i = 0; i < rad; i++)
237 for_each_channel(c) out[(size_t)ch * i + c] = in[(size_t)ch * i + c];
238 for(int i = roi_out->width - rad; i < roi_out->width; i++)
239 for_each_channel(c) out[(size_t)ch * i + c] = in[(size_t)ch * i + c];
240 }
241 }
242 else
243 {
244 for(int k = 0; k < 5; k++) sigma[k] = 1.0f / sigma[k];
245 PermutohedralLattice<5, 4> lattice((size_t)roi_in->width * roi_in->height, dt_get_num_openmp_threads());
246
247// splat into the lattice
248#ifdef _OPENMP
249#pragma omp parallel for
250#endif
251 for(int j = 0; j < roi_in->height; j++)
252 {
253 const float *in = (const float *)ivoid + (size_t)j * roi_in->width * ch;
254 const int thread = dt_get_thread_num();
255 size_t index = (size_t)j * roi_in->width;
256 for(int i = 0; i < roi_in->width; i++, index++)
257 {
258 float pos[5] = { i * sigma[0], j * sigma[1], in[0] * sigma[2], in[1] * sigma[3], in[2] * sigma[4] };
259 float DT_ALIGNED_PIXEL val[4] = { in[0], in[1], in[2], 1.0 };
260 lattice.splat(pos, val, index, thread);
261 in += ch;
262 }
263 }
264
265 lattice.merge_splat_threads();
266
267 // blur the lattice
268 lattice.blur();
269
270// slice from the lattice
271#ifdef _OPENMP
272#pragma omp parallel for
273#endif
274 for(int j = 0; j < roi_in->height; j++)
275 {
276 float *const out = (float *)ovoid + (size_t)j * roi_in->width * ch;
277 size_t index = (size_t)j * roi_in->width;
278 for(int i = 0; i < roi_in->width; i++, index++)
279 {
280 float DT_ALIGNED_PIXEL val[4];
281 lattice.slice(val, index);
283 out[(size_t)ch*i + k] = val[k] / val[3];
284 }
285 }
286 }
287
288 if(pipe->mask_display) dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
289 return 0;
290}
291
294{
297 d->sigma[0] = p->radius;
298 d->sigma[1] = p->radius;
299 d->sigma[2] = p->red;
300 d->sigma[3] = p->green;
301 d->sigma[4] = p->blue;
302}
303
305{
307
308 piece->data_size = sizeof(dt_iop_bilateral_data_t);}
309
311{
312 dt_free_align(piece->data);
313 piece->data = NULL;
314}
315
316void 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)
317{
318 const dt_iop_roi_t *const roi_in = &piece->roi_in;
320 float sigma[5];
321 sigma[0] = data->sigma[0] * roi_in->scale;
322 sigma[1] = data->sigma[1] * roi_in->scale;
323 const int rad = (int)(3.0 * fmaxf(sigma[0], sigma[1]) + 1.0);
324 tiling->factor = 2.0 /*input+output*/ + 80.0/16/*worst-case hashtable*/ + 52.0/16/*replay buffer*/;
325 tiling->overhead = 0;
326 tiling->overlap = rad;
327 tiling->xalign = 1;
328 tiling->yalign = 1;
329 return;
330}
331
333{
335
336 g->radius = dt_bauhaus_slider_from_params(self, N_("radius"));
337 gtk_widget_set_tooltip_text(g->radius, _("spatial extent of the gaussian"));
338 dt_bauhaus_slider_set_soft_range(g->radius, 1.0, 30.0);
339
340 g->red = dt_bauhaus_slider_from_params(self, N_("red"));
341 gtk_widget_set_tooltip_text(g->red, _("how much to blur red"));
344
345 g->green = dt_bauhaus_slider_from_params(self, N_("green"));
346 gtk_widget_set_tooltip_text(g->green, _("how much to blur green"));
349
350 g->blue = dt_bauhaus_slider_from_params(self, N_("blue"));
351 gtk_widget_set_tooltip_text(g->blue, _("how much to blur blue"));
354}
355}
356
357// clang-format off
358// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
359// vim: shiftwidth=2 expandtab tabstop=2 cindent
360// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
361// clang-format on
#define m
Definition basecurve.c:283
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1498
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3343
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1474
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:292
const char ** description(struct dt_iop_module_t *self)
Definition bilateral.cc:131
int default_group()
Definition bilateral.cc:116
__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:141
const char * aliases()
Definition bilateral.cc:111
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition bilateral.cc:304
const char * name()
Definition bilateral.cc:106
void gui_init(dt_iop_module_t *self)
Definition bilateral.cc:332
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:316
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition bilateral.cc:126
int flags()
Definition bilateral.cc:121
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition bilateral.cc:310
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
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
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
void dt_iop_params_t
Definition dev_history.h:43
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
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:91
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:1893
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_GROUP_REPAIR
Definition imageop.h:159
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#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 DT_ALIGNED_PIXEL
Align a 4-float pixel on 16 bytes, enough for SSE. Same struct-member caveat as DT_ALIGNED_ARRAY,...
Definition mem_alloc.h:85
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
static int dt_get_thread_num()
Index of the calling thread within its parallel region, 0 outside one.
Definition openmp.h:129
#define __OMP_PARALLEL_FOR_CPP__(...)
Definition openmp.h:102
@ DT_DEV_PIXELPIPE_THUMBNAIL
Definition pixelpipe.h:45
#define dt_pixelpipe_cache_free_align(mem)
#define dt_get_perthread(buf, padsize)
#define dt_pixelpipe_cache_alloc_perthread_float(n, padded_size)
#define for_each_channel(_var,...)
Definition simd.h:87
const float sigma
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:83
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__