Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
defringe.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013-2014 Dennis Gnad.
4 Copyright (C) 2014-2016 Roman Lebedev.
5 Copyright (C) 2014, 2016, 2019 Tobias Ellinghaus.
6 Copyright (C) 2015 Pedro Côrte-Real.
7 Copyright (C) 2016 johannes hanika.
8 Copyright (C) 2017 Heiko Bauke.
9 Copyright (C) 2017-2019 luzpaz.
10 Copyright (C) 2017 Ulrich Pegelow.
11 Copyright (C) 2018, 2020, 2023, 2025-2026 Aurélien PIERRE.
12 Copyright (C) 2018 Edgardo Hoszowski.
13 Copyright (C) 2018 Maurizio Paglia.
14 Copyright (C) 2018-2022 Pascal Obry.
15 Copyright (C) 2018 rawfiner.
16 Copyright (C) 2019 Andreas Schneider.
17 Copyright (C) 2019 Diederik ter Rahe.
18 Copyright (C) 2020 Aldric Renaudin.
19 Copyright (C) 2020 Chris Elston.
20 Copyright (C) 2020 Diederik Ter Rahe.
21 Copyright (C) 2020-2021 Hubert Kowalski.
22 Copyright (C) 2020-2021 Ralf Brown.
23 Copyright (C) 2022 Martin Bařinka.
24 Copyright (C) 2022 Philipp Lutz.
25
26 darktable is free software: you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation, either version 3 of the License, or
29 (at your option) any later version.
30
31 darktable is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License
37 along with darktable. If not, see <http://www.gnu.org/licenses/>.
38*/
39#ifdef HAVE_CONFIG_H
40#include "config.h"
41#endif
42#include "bauhaus/bauhaus.h"
43#include "common/darktable.h"
44#include "common/gaussian.h"
45#include "common/imagebuf.h"
46#include "common/math.h"
47#include "develop/develop.h"
48#include "develop/imageop.h"
50#include "develop/imageop_gui.h"
51#include "gui/gtk.h"
52
53#include "iop/iop_api.h"
54#include <gtk/gtk.h>
55#include <stdlib.h>
56
58
60{
61 MODE_GLOBAL_AVERAGE = 0, // $DESCRIPTION: "global average (fast)"
62 MODE_LOCAL_AVERAGE = 1, // $DESCRIPTION: "local average (slow)"
63 MODE_STATIC = 2 // $DESCRIPTION: "static threshold (fast)"
65
67{
68 float radius; // $MIN: 0.5 $MAX: 20.0 $DEFAULT: 4.0 $DESCRIPTION: "edge detection radius"
69 float thresh; // $MIN: 0.5 $MAX: 128.0 $DEFAULT: 20.0 $DESCRIPTION: "threshold"
70 dt_iop_defringe_mode_t op_mode; // $DEFAULT: MODE_GLOBAL_AVERAGE $DESCRIPTION: "operation mode"
72
74
81
82// would be nice to be able to precompute this only once for an image
83// typedef struct dt_iop_defringe_global_data_t
84//{
85// float avg_edge_chroma;
86//}
87// dt_iop_defringe_global_data_t;
88
89
90const char *name()
91{
92 return _("defringe");
93}
94
95const char *aliases()
96{
97 return _("chromatic aberrations");
98}
99
100const char **description(struct dt_iop_module_t *self)
101{
102 return dt_iop_set_description(self, _("attenuate chromatic aberration by desaturating edges"),
103 _("corrective"),
104 _("linear or non-linear, Lab, display-referred"),
105 _("non-linear, Lab"),
106 _("non-linear, Lab, display-referred"));
107}
108
110{
111 return IOP_GROUP_REPAIR;
112}
113
114int flags()
115{
116 // a second instance might help to reduce artifacts when thick fringe needs to be removed
118}
119
120const char *deprecated_msg()
121{
122 return _("this module is deprecated. please use the chromatic aberration module instead.");
123}
124
126{
127 return IOP_CS_LAB;
128}
129
130// Verify before actually using this
131/*
132void tiling_callback (dt_iop_module_t *module, dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_in,
133const dt_iop_roi_t *roi_out, dt_develop_tiling_t *tiling)
134{
135 dt_iop_defringe_data_t *p = (dt_iop_defringe_data_t *)piece->data;
136
137 const int width = roi_in->width;
138 const int height = roi_in->height;
139 const int channels = piece->dsc_in.channels;
140 const size_t basebuffer = width*height*channels*sizeof(float);
141
142 tiling->factor = 2.0f + (float)dt_gaussian_memory_use(width, height, channels)/basebuffer;
143#ifdef HAVE_OPENCL
144 tiling->factor_cl = 2.0f + (float)dt_gaussian_memory_use_cl(width, height, channels)/basebuffer;
145#endif
146 tiling->maxbuf = fmax(1.0f, (float)dt_gaussian_singlebuffer_size(width, height, channels)/basebuffer);
147 tiling->overhead = 0;
148 tiling->overlap = p->window;
149 tiling->xalign = 1;
150 tiling->yalign = 1;
151 return;
152}
153*/
154
155// fibonacci lattice to select surrounding pixels for different cases
156static const float fib[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 };
157// 0,1,2,3,4,5,6, 7, 8, 9,10,11, 12, 13
158
159static inline void fib_latt(int *const x, int *const y, float radius, int step, int idx)
160{
161 // idx < 1 because division by zero is also a problem in the following line
162 if(idx >= sizeof(fib) / sizeof(float) - 1 || idx < 1)
163 {
164 *x = 0;
165 *y = 0;
166 fprintf(stderr, "Fibonacci lattice index wrong/out of bounds in: defringe module\n");
167 return;
168 }
169 float px = step / fib[idx], py = step * (fib[idx + 1] / fib[idx]);
170 py -= (int)py;
171 float dx = px * radius, dy = py * radius;
172 *x = round(dx - radius / 2.0);
173 *y = round(dy - radius / 2.0);
174}
175
176#define MAGIC_THRESHOLD_COEFF 33.0
177
178// the basis of how the following algorithm works comes from rawtherapee (http://rawtherapee.com/)
179// defringe -- thanks to Emil Martinec <ejmartin@uchicago.edu> for that
180// quite some modifications were done though:
181// 1. use a fibonacci lattice instead of full window, to speed things up
182// 2. option for local averaging or static (RT used the global/region one)
183// 3. additional condition to reduce sharp edged artifacts, by blurring pixels near pixels over threshold,
184// this really helps improving the filter with thick fringes
185// -----------------------------------------------------------------------------------------
186// in the following you will also see some more "magic numbers",
187// most are chosen arbitrarily and/or by experiment/trial+error ... I am sorry ;-)
188// and having everything user-defineable would be just too much
189// -----------------------------------------------------------------------------------------
190
192{
194 piece->data_size = sizeof(dt_iop_defringe_data_t);
195}
196
198{
199 dt_free_align(piece->data);
200 piece->data = NULL;
201}
202
204int process(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
205 const void *const i,
206 void *const o)
207{
208 (void)pipe;
209 const dt_iop_roi_t *const roi_in = &piece->roi_in;
210 const dt_iop_roi_t *const roi_out = &piece->roi_out;
212
213 const int order = 1; // 0,1,2
214 int err = 0;
215 const float sigma = fmax(0.1f, fabs(d->radius)) * roi_in->scale;
216 const float Labmax[] = { 100.0f, 128.0f, 128.0f, 1.0f };
217 const float Labmin[] = { 0.0f, -128.0f, -128.0f, 0.0f };
218 const int ch = 4;
219 const int radius = ceil(2.0 * ceilf(sigma));
220
221 // save the fibonacci lattices in them later
222 int *xy_avg = NULL;
223 int *xy_small = NULL;
224
225 if(roi_out->width < 2 * radius + 1 || roi_out->height < 2 * radius + 1) goto ERROR_EXIT;
226
227 float avg_edge_chroma = 0.0;
228
229 const float *const restrict in = (float *const)i;
230 float *const restrict out = (float *const)o;
231 const int width = roi_in->width;
232 const int height = roi_in->height;
233
234 dt_gaussian_t *gauss = NULL;
235 gauss = dt_gaussian_init(width, height, 4, Labmax, Labmin, sigma, order);
236 if(IS_NULL_PTR(gauss))
237 {
238 fprintf(stderr, "Error allocating memory for gaussian blur in: defringe module\n");
239 err = 1;
240 goto ERROR_EXIT;
241 }
242 dt_gaussian_blur_4c(gauss, in, out);
243 dt_gaussian_free(gauss);
244
245 const int samples_wish = radius * radius;
246 int sampleidx_avg;
247 // select samples by fibonacci number
248 if(samples_wish > 89)
249 {
250 sampleidx_avg = 12; // 144 samples
251 }
252 else if(samples_wish > 55)
253 {
254 sampleidx_avg = 11; // 89 samples
255 }
256 else if(samples_wish > 34)
257 {
258 sampleidx_avg = 10; // ..you get the idea
259 }
260 else if(samples_wish > 21)
261 {
262 sampleidx_avg = 9;
263 }
264 else if(samples_wish > 13)
265 {
266 sampleidx_avg = 8;
267 }
268 else
269 { // don't use less than 13 samples
270 sampleidx_avg = 7;
271 }
272 const int sampleidx_small = sampleidx_avg - 1;
273
274 const int small_radius = MAX(radius, 3);
275 const int avg_radius = 24 + radius * 4;
276
277 const int samples_small = fib[sampleidx_small];
278 const int samples_avg = fib[sampleidx_avg];
279
280 // Pre-Compute Fibonacci Lattices
281
282 xy_avg = malloc(sizeof(int) * 2 * samples_avg);
283 xy_small = malloc(sizeof(int) * 2 * samples_small);
284 if(IS_NULL_PTR(xy_avg) || IS_NULL_PTR(xy_small))
285 {
286 fprintf(stderr, "Error allocating memory for fibonacci lattice in: defringe module\n");
287 err = 1;
288 goto ERROR_EXIT;
289 }
290
291 // precompute all required fibonacci lattices:
292 for(int u = 0; u < samples_avg; u++)
293 {
294 int dx, dy;
295 fib_latt(&dx, &dy, avg_radius, u, sampleidx_avg);
296 xy_avg[2*u] = dx;
297 xy_avg[2*u+1] = dy;
298 }
299 for(int u = 0; u < samples_small; u++)
300 {
301 int dx, dy;
302 fib_latt(&dx, &dy, small_radius, u, sampleidx_small);
303 xy_small[2*u] = dx;
304 xy_small[2*u+1] = dy;
305 }
306
307 const float use_global_average = MODE_GLOBAL_AVERAGE == d->op_mode;
308 __OMP_PARALLEL_FOR_SIMD__(reduction(+ : avg_edge_chroma))
309 for(size_t j = 0; j < (size_t)height * width * 4; j += 4)
310 {
311 // edge-detect on color channels
312 // method: difference of original to gaussian blurred image:
313 const float a = in[j + 1] - out[j + 1];
314 const float b = in[j + 2] - out[j + 2];
315 const float edge = (a * a + b * b); // range up to 2*(256)^2 -> approx. 0 to 131072
316
317 // save local edge chroma in out[.. +3] , this is later compared with threshold
318 out[j + 3] = edge;
319 // the average chroma of the edge-layer in the roi
320 avg_edge_chroma += edge * use_global_average;
321 }
322
323 float thresh;
324 if(use_global_average)
325 {
326 avg_edge_chroma = avg_edge_chroma / (width * height) + 10.0 * FLT_EPSILON;
327 thresh = fmax(0.1f, 4.0 * d->thresh * avg_edge_chroma / MAGIC_THRESHOLD_COEFF);
328 }
329 else
330 {
331 // this fixed value will later be changed when doing local averaging, or kept as-is in "static" mode
332 avg_edge_chroma = MAGIC_THRESHOLD_COEFF;
333 thresh = fmax(0.1f, d->thresh);
334 }
335
336#ifdef _OPENMP
337// dynamically/guided scheduled due to possible uneven edge-chroma distribution (thanks to rawtherapee code
338// for this hint!)
339#pragma omp parallel for default(firstprivate) \
340 schedule(dynamic,3)
341#endif
342 for(int v = 0; v < height; v++)
343 {
344 const size_t row_above = (size_t)MAX(0, (v-1)) * width * ch;
345 const size_t curr_row = (size_t)v * width * ch;
346 const size_t row_below = (size_t)MIN((height-1), (v+1)) * width * ch;
347 for(int t = 0; t < width; t++)
348 {
349 const size_t index = ch * ((size_t)v * width + t);
350 float local_thresh = thresh;
351 // think of compiler setting "-funswitch-loops" to maybe improve these things:
352 if(MODE_LOCAL_AVERAGE == d->op_mode && out[index + 3] > thresh)
353 {
354 float local_avg = 0.0;
355 // use some and not all values from the neighbourhood to speed things up:
356 for(int u = 0; u < samples_avg; u++)
357 {
358 const int dx = xy_avg[2*u];
359 const int dy = xy_avg[2*u+1];
360 const int x = CLAMP(t + dx, 0, width - 1);
361 const int y = CLAMP(v + dy, 0, height - 1);
362 local_avg += out[((size_t)y * width + x) * ch + 3];
363 }
364 avg_edge_chroma = fmax(0.01f, (float)local_avg / samples_avg);
365 local_thresh = fmax(0.1f, 4.0 * d->thresh * avg_edge_chroma / MAGIC_THRESHOLD_COEFF);
366 }
367
368 if(out[index + 3] > local_thresh
369 // reduces artifacts ("region growing by 1 pixel"):
370 || out[row_above + MAX(0, (t - 1)) * ch + 3] > local_thresh
371 || out[row_above + t * ch + 3] > local_thresh
372 || out[row_above + MIN(width - 1, (t + 1)) * ch + 3] > local_thresh
373 || out[curr_row + MAX(0, (t - 1)) * ch + 3] > local_thresh
374 || out[curr_row + MIN(width - 1, (t + 1)) * ch + 3] > local_thresh
375 || out[row_below + MAX(0, (t - 1)) * ch + 3] > local_thresh
376 || out[row_below + t * ch + 3] > local_thresh
377 || out[row_below + MIN(width - 1, (t + 1)) * ch + 3] > local_thresh)
378 {
379 float atot = 0, btot = 0;
380 float norm = 0;
381 float weight;
382 // it seems better to use only some pixels from a larger window instead of all pixels from a smaller
383 // window
384 // we use a fibonacci lattice for that, samples amount need to be a fibonacci number, this can then be
385 // scaled to
386 // a certain radius
387
388 // use some neighbourhood pixels for lowest chroma average
389 for(int u = 0; u < samples_small; u++)
390 {
391 const int dx = xy_small[2*u];
392 const int dy = xy_small[2*u+1];
393 const int x = CLAMP(t + dx, 0, width - 1);
394 const int y = CLAMP(v + dy, 0, height - 1);
395 const size_t idx = ch * ((size_t)y * width + x);
396 // inverse chroma weighted average of neighbouring pixels inside window
397 // also taking average edge chromaticity into account (either global or local average)
398 weight = 1.0 / (out[idx + 3] + avg_edge_chroma);
399 atot += weight * in[idx + 1];
400 btot += weight * in[idx + 2];
401 norm += weight;
402 }
403 // here we could try using a "balance" between original and changed value, this could be used to
404 // reduce artifacts
405 // but on first tries, results weren't very convincing, and there are blend settings available anyway
406 // in dt
407 // float balance = (out[v*width*ch +t*ch +3]-thresh)/out[v*width*ch +t*ch +3];
408 double a = (atot / norm); // *balance + in[v*width*ch + t*ch +1]*(1.0-balance);
409 double b = (btot / norm); // *balance + in[v*width*ch + t*ch +2]*(1.0-balance);
410 out[index] = in[index];
411 out[index + 1] = a;
412 out[index + 2] = b;
413 }
414 else
415 {
416 __OMP_SIMD__(aligned(in, out))
417 // we can't copy the alpha channel here because it contains info needed by neighboring pixels!
418 for(int c = 0; c < 3; c++)
419 {
420 out[index+c] = in[index+c];
421 }
422 }
423 }
424 }
425
427 dt_iop_alpha_copy(i, o, roi_out->width, roi_out->height);
428
429 goto FINISH_PROCESS;
430
431ERROR_EXIT:
432 dt_iop_image_copy_by_size(o, i, roi_out->width, roi_out->height, ch);
433
434FINISH_PROCESS:
435 dt_free(xy_small);
436 dt_free(xy_avg);
437 return err;
438}
439
441{
443
444 g->mode_select = dt_bauhaus_combobox_from_params(self, "op_mode");
445 gtk_widget_set_tooltip_text(g->mode_select,
446 _("method for color protection:\n - global average: fast, might show slightly wrong previews in high "
447 "magnification; might sometimes protect saturation too much or too low in comparison to local "
448 "average\n - local average: slower, might protect saturation better than global average by using "
449 "near pixels as color reference, so it can still allow for more desaturation where required\n - "
450 "static: fast, only uses the threshold as a static limit"));
451
452 g->radius_scale = dt_bauhaus_slider_from_params(self, "radius");
453 gtk_widget_set_tooltip_text(g->radius_scale, _("radius for detecting fringe"));
454
455 g->thresh_scale = dt_bauhaus_slider_from_params(self, "thresh");
456 gtk_widget_set_tooltip_text(g->thresh_scale, _("threshold for defringe, higher values mean less defringing"));
457}
458
460{
463 dt_bauhaus_combobox_set(g->mode_select, p->op_mode);
464 dt_bauhaus_slider_set(g->radius_scale, p->radius);
465 dt_bauhaus_slider_set(g->thresh_scale, p->thresh);
466}
467
468// clang-format off
469// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
470// vim: shiftwidth=2 expandtab tabstop=2 cindent
471// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
472// clang-format on
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3506
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
@ IOP_CS_LAB
const dt_colormatrix_t dt_aligned_pixel_t out
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
#define __OMP_SIMD__(...)
Definition darktable.h:262
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#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
const char ** description(struct dt_iop_module_t *self)
Definition defringe.c:100
int default_group()
Definition defringe.c:109
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const i, void *const o)
Definition defringe.c:204
static void fib_latt(int *const x, int *const y, float radius, int step, int idx)
Definition defringe.c:159
dt_iop_defringe_mode_t
Definition defringe.c:60
@ MODE_LOCAL_AVERAGE
Definition defringe.c:62
@ MODE_GLOBAL_AVERAGE
Definition defringe.c:61
@ MODE_STATIC
Definition defringe.c:63
const char * aliases()
Definition defringe.c:95
void gui_update(dt_iop_module_t *module)
Definition defringe.c:459
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition defringe.c:191
dt_iop_defringe_params_t dt_iop_defringe_data_t
Definition defringe.c:73
const char * name()
Definition defringe.c:90
void gui_init(dt_iop_module_t *self)
Definition defringe.c:440
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition defringe.c:125
int flags()
Definition defringe.c:114
static const float fib[]
Definition defringe.c:156
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition defringe.c:197
const char * deprecated_msg()
Definition defringe.c:120
#define MAGIC_THRESHOLD_COEFF
Definition defringe.c:176
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:118
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
void dt_gaussian_free(dt_gaussian_t *g)
Definition gaussian.c:330
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
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_DEPRECATED
Definition imageop.h:168
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ 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
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
static const float x
const int t
const float v
float *const restrict const size_t const size_t ch
struct _GtkWidget GtkWidget
Definition splash.h:29
const float sigma
struct dt_iop_module_t *void * data
dt_iop_defringe_mode_t op_mode
Definition defringe.c:70
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29